code-gauge 10.2.4 → 10.3.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 +46 -4
- package/dist/allowDirectives.cjs +2 -0
- package/dist/allowDirectives.cjs.map +1 -0
- package/dist/allowDirectives.d.ts +45 -0
- package/dist/allowDirectives.js +2 -0
- package/dist/allowDirectives.js.map +1 -0
- package/dist/checkCommand.cjs +3 -3
- package/dist/checkCommand.cjs.map +1 -1
- package/dist/checkCommand.d.ts +1 -1
- package/dist/checkCommand.js +3 -3
- package/dist/checkCommand.js.map +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.js.map +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.js.map +1 -1
- package/dist/crossFileNearMiss.cjs +1 -1
- package/dist/crossFileNearMiss.cjs.map +1 -1
- package/dist/crossFileNearMiss.js +1 -1
- package/dist/crossFileNearMiss.js.map +1 -1
- package/dist/duplicateSelection.cjs.map +1 -1
- package/dist/duplicateSelection.js.map +1 -1
- package/dist/duplication.cjs +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.js +1 -1
- package/dist/duplication.js.map +1 -1
- package/dist/exclusion.cjs.map +1 -1
- package/dist/exclusion.js.map +1 -1
- package/dist/git.cjs +2 -1
- package/dist/git.cjs.map +1 -1
- package/dist/git.d.ts +7 -0
- package/dist/git.js +2 -1
- package/dist/git.js.map +1 -1
- package/dist/languages.cjs.map +1 -1
- package/dist/languages.js.map +1 -1
- package/dist/metrics.cjs.map +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/scan.cjs +1 -1
- package/dist/scan.cjs.map +1 -1
- package/dist/scan.js +1 -1
- package/dist/scan.js.map +1 -1
- package/dist/thresholdCheck.cjs +1 -1
- package/dist/thresholdCheck.cjs.map +1 -1
- package/dist/thresholdCheck.d.ts +15 -1
- package/dist/thresholdCheck.js +1 -1
- package/dist/thresholdCheck.js.map +1 -1
- package/dist/thresholds.cjs.map +1 -1
- package/dist/thresholds.js.map +1 -1
- package/dist/wasmBinding.cjs.map +1 -1
- package/dist/wasmBinding.js.map +1 -1
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +39 -43
package/dist/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","names":["execFileAsync","promisify","execFile","resolveRepoRoot","runGit","directory","isIgnored","path","repoRoot","error","resolveMergeBase","baseRef","listChangedFiles","diffOutput","untrackedOutput","mergeBase","files","parseNameStatusEntries","parts","output","index","kind","pathCount","changed","toChangedFile","first","second","paths","listRepositoryFiles","listed","deleted","listLineHunks","basePath","headPath","file","hunks","match","headStart","headCount","readLinguistAttributes","input","runGitWithInput","attributesByPath","fields","attribute","value","enabled","toAttributeFlag","attributes","child","spawn","args","stdout","stderr","chunk","reject","code","resolve","message","describeCommand","start"],"sources":["../src/git.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\n/** Large enough for whole-file blobs; execFile's 1 MiB default truncates real sources. */\nconst maxOutputBytes = 512 * 1024 * 1024;\n\n/** The head side of one region of a line diff: lines [headStart, headStart + headCount); a pure deletion has headCount 0. */\nexport interface LineHunk {\n headStart: number;\n headCount: number;\n}\n\n/** One entry of the working tree's diff against the merge-base commit. */\nexport interface ChangedFile {\n status: 'added' | 'modified' | 'deleted' | 'renamed';\n /** Path at the merge-base, absent for added files; differs from headPath only for renames. */\n basePath?: string;\n /** Path in the working tree; for deleted files, the (now missing) base path. */\n headPath: string;\n}\n\nexport async function resolveRepoRoot(directory: string): Promise<string> {\n const output = await runGit(directory, ['rev-parse', '--show-toplevel']);\n return output.trim();\n}\n\n/** Whether git ignores the path or a directory above it (`git check-ignore` exits 1 otherwise). */\nexport async function isIgnored(repoRoot: string, path: string): Promise<boolean> {\n try {\n await execFileAsync('git', ['check-ignore', '--quiet', '--', path], { cwd: repoRoot });\n return true;\n } catch (error) {\n if ((error as { code?: unknown }).code === 1) return false;\n throw error;\n }\n}\n\nexport async function resolveMergeBase(repoRoot: string, baseRef: string): Promise<string> {\n const output = await runGit(repoRoot, ['merge-base', baseRef, 'HEAD']);\n return output.trim();\n}\n\n/**\n * Files whose working-tree content differs from the merge-base commit (staged or not), with\n * rename detection, plus untracked (non-ignored) files as additions.\n */\nexport async function listChangedFiles(repoRoot: string, mergeBase: string): Promise<ChangedFile[]> {\n const [diffOutput, untrackedOutput] = await Promise.all([\n runGit(repoRoot, ['diff', '--name-status', '--find-renames', '-z', mergeBase]),\n runGit(repoRoot, ['ls-files', '--others', '--exclude-standard', '-z']),\n ]);\n\n const files = parseNameStatusEntries(diffOutput);\n for (const path of untrackedOutput.split('\\0')) {\n if (path !== '') {\n files.push({ status: 'added', headPath: path });\n }\n }\n return files;\n}\n\n/** Parses `git diff --name-status -z` output: `<status>\\0<path>\\0`, with two paths for R/C. */\nfunction parseNameStatusEntries(output: string): ChangedFile[] {\n const files: ChangedFile[] = [];\n const parts = output.split('\\0');\n let index = 0;\n while (index < parts.length - 1) {\n const kind = (parts[index] as string).charAt(0);\n const pathCount = kind === 'R' || kind === 'C' ? 2 : 1;\n const changed = toChangedFile(kind, parts.slice(index + 1, index + 1 + pathCount));\n if (changed) {\n files.push(changed);\n }\n index += 1 + pathCount;\n }\n return files;\n}\n\n/** Undefined for unmerged (U) and unknown statuses, which have nothing to check. */\nfunction toChangedFile(kind: string, paths: (string | undefined)[]): ChangedFile | undefined {\n const [first, second] = paths;\n if (first === undefined) {\n return undefined;\n }\n switch (kind) {\n case 'R': {\n return second === undefined ? undefined : { status: 'renamed', basePath: first, headPath: second };\n }\n // A copy is an addition: every line of the new path is new.\n case 'C': {\n return second === undefined ? undefined : { status: 'added', headPath: second };\n }\n case 'A': {\n return { status: 'added', headPath: first };\n }\n case 'D': {\n return { status: 'deleted', basePath: first, headPath: first };\n }\n case 'M':\n case 'T': {\n return { status: 'modified', basePath: first, headPath: first };\n }\n default: {\n return undefined;\n }\n }\n}\n\n/**\n * Repository-relative paths git considers part of the project AND that exist in the working tree:\n * tracked files (minus worktree deletions) plus untracked non-ignored ones. `check` scans\n * exactly these, so local ignored artifacts (build output, generated copies) are neither measured\n * nor allowed to skew duplication detection.\n */\nexport async function listRepositoryFiles(repoRoot: string): Promise<Set<string>> {\n const [listed, deleted] = await Promise.all([\n runGit(repoRoot, ['ls-files', '--cached', '--others', '--exclude-standard', '-z']),\n runGit(repoRoot, ['ls-files', '--deleted', '-z']),\n ]);\n const files = new Set(listed.split('\\0').filter((path) => path !== ''));\n for (const path of deleted.split('\\0')) {\n files.delete(path);\n }\n return files;\n}\n\n/**\n * The line hunks turning the merge-base blob at `basePath` into the working-tree file at\n * `headPath` (equal unless renamed), as `git diff` computes them for review.\n */\nexport async function listLineHunks(\n repoRoot: string,\n mergeBase: string,\n basePath: string,\n headPath: string\n): Promise<LineHunk[]> {\n // Literal pathspecs: a name like `app/[slug]/page.tsx` must not glob-match other files.\n const paths = (basePath === headPath ? [headPath] : [basePath, headPath]).map((file) => `:(literal)${file}`);\n const output = await runGit(repoRoot, [\n // The algorithm and indent heuristic decide which lines count as added, so user config must\n // not change them; `-c` values an older git does not know are ignored rather than rejected.\n '-c',\n 'diff.algorithm=myers',\n '-c',\n 'diff.indentHeuristic=true',\n 'diff',\n '--unified=0',\n // diff.interHunkContext would otherwise merge nearby hunks, and the unchanged lines between\n // them would count as added.\n '--inter-hunk-context=0',\n // A file git would show as binary (a `-diff` attribute, a NUL byte) must still yield hunks.\n '--text',\n '--no-color',\n '--no-ext-diff',\n '--no-textconv',\n '--find-renames',\n mergeBase,\n '--',\n ...paths,\n ]);\n const hunks: LineHunk[] = [];\n for (const match of output.matchAll(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,(\\d+))? @@/gmu)) {\n const [, headStart, headCount] = match;\n hunks.push({\n headStart: Number(headStart),\n headCount: headCount === undefined ? 1 : Number(headCount),\n });\n }\n return hunks;\n}\n\n/** A file's `linguist-generated` / `linguist-vendored` attributes; undefined when unspecified. */\nexport interface LinguistAttributes {\n generated?: boolean;\n vendored?: boolean;\n}\n\n/**\n * The linguist attributes of repository-relative paths, from the working tree's `.gitattributes`.\n * Paths with neither attribute specified are omitted.\n */\nexport async function readLinguistAttributes(\n repoRoot: string,\n paths: Iterable<string>\n): Promise<Map<string, LinguistAttributes>> {\n const input = [...paths].join('\\0');\n if (input === '') {\n return new Map();\n }\n const output = await runGitWithInput(\n repoRoot,\n ['check-attr', '--stdin', '-z', 'linguist-generated', 'linguist-vendored'],\n input\n );\n const attributesByPath = new Map<string, LinguistAttributes>();\n const fields = output.split('\\0');\n for (let index = 0; index + 2 < fields.length; index += 3) {\n const [file, attribute, value] = fields.slice(index, index + 3) as [string, string, string];\n const enabled = toAttributeFlag(value);\n if (enabled === undefined) {\n continue;\n }\n const attributes = attributesByPath.get(file) ?? {};\n if (attribute === 'linguist-generated') {\n attributes.generated = enabled;\n } else {\n attributes.vendored = enabled;\n }\n attributesByPath.set(file, attributes);\n }\n return attributesByPath;\n}\n\n/** Linguist reads `attr`/`attr=true` as set and `-attr`/`attr=false` as explicitly unset. */\nfunction toAttributeFlag(value: string): boolean | undefined {\n if (value === 'set' || value === 'true') {\n return true;\n }\n if (value === 'unset' || value === 'false') {\n return false;\n }\n return undefined;\n}\n\nasync function runGitWithInput(cwd: string, args: string[], input: string): Promise<string> {\n return await new Promise((resolve, reject) => {\n const child = spawn('git', args, { cwd });\n // git exiting before it reads all input (e.g. on a usage error) makes the write fail with\n // EPIPE; its exit code and stderr, reported on close, already describe the failure.\n child.stdin.on('error', () => {});\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve(Buffer.concat(stdout).toString('utf8'));\n return;\n }\n const message = Buffer.concat(stderr).toString('utf8').trim();\n reject(new Error(`git ${describeCommand(args)} failed${message ? `: ${message}` : ''}`));\n });\n child.stdin.end(input);\n });\n}\n\nasync function runGit(cwd: string, args: string[]): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, { cwd, maxBuffer: maxOutputBytes, encoding: 'utf8' });\n return stdout;\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr?.trim();\n throw new Error(`git ${describeCommand(args)} failed${stderr ? `: ${stderr}` : ''}`);\n }\n}\n\n/** The subcommand and its first argument, skipping leading `-c key=value` overrides. */\nfunction describeCommand(args: readonly string[]): string {\n let start = 0;\n while (args[start] === '-c') {\n start += 2;\n }\n return args.slice(start, start + 2).join(' ');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"git.js","names":["execFileAsync","promisify","execFile","resolveRepoRoot","runGit","directory","isIgnored","path","repoRoot","error","resolveMergeBase","baseRef","listChangedFiles","diffOutput","untrackedOutput","mergeBase","files","parseNameStatusEntries","parts","output","index","kind","pathCount","changed","toChangedFile","first","second","paths","listRepositoryFiles","listed","deleted","listLineHunks","basePath","headPath","file","headers","header","baseStart","headStart","headCount","match","body","line","readFileAt","commit","readLinguistAttributes","input","runGitWithInput","attributesByPath","fields","attribute","value","enabled","toAttributeFlag","attributes","child","spawn","args","stdout","stderr","chunk","reject","code","resolve","message","describeCommand","start"],"sources":["../src/git.ts"],"sourcesContent":["import { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\n/** Large enough for whole-file blobs; execFile's 1 MiB default truncates real sources. */\nconst maxOutputBytes = 512 * 1024 * 1024;\n\n/** The head side of one region of a line diff: lines [headStart, headStart + headCount); a pure deletion has headCount 0. */\nexport interface LineHunk {\n headStart: number;\n headCount: number;\n /** The base lines the hunk deletes or replaces, from line `baseStart` of the base file on. */\n deletedLines?: readonly string[];\n baseStart?: number;\n /** Whether a deleted line is a `code-gauge-allow` directive; set by the check. */\n deletesDirective?: boolean;\n}\n\n/** One entry of the working tree's diff against the merge-base commit. */\nexport interface ChangedFile {\n status: 'added' | 'modified' | 'deleted' | 'renamed';\n /** Path at the merge-base, absent for added files; differs from headPath only for renames. */\n basePath?: string;\n /** Path in the working tree; for deleted files, the (now missing) base path. */\n headPath: string;\n}\n\nexport async function resolveRepoRoot(directory: string): Promise<string> {\n const output = await runGit(directory, ['rev-parse', '--show-toplevel']);\n return output.trim();\n}\n\n/** Whether git ignores the path or a directory above it (`git check-ignore` exits 1 otherwise). */\nexport async function isIgnored(repoRoot: string, path: string): Promise<boolean> {\n try {\n await execFileAsync('git', ['check-ignore', '--quiet', '--', path], { cwd: repoRoot });\n return true;\n } catch (error) {\n if ((error as { code?: unknown }).code === 1) return false;\n throw error;\n }\n}\n\nexport async function resolveMergeBase(repoRoot: string, baseRef: string): Promise<string> {\n const output = await runGit(repoRoot, ['merge-base', baseRef, 'HEAD']);\n return output.trim();\n}\n\n/**\n * Files whose working-tree content differs from the merge-base commit (staged or not), with\n * rename detection, plus untracked (non-ignored) files as additions.\n */\nexport async function listChangedFiles(repoRoot: string, mergeBase: string): Promise<ChangedFile[]> {\n const [diffOutput, untrackedOutput] = await Promise.all([\n runGit(repoRoot, ['diff', '--name-status', '--find-renames', '-z', mergeBase]),\n runGit(repoRoot, ['ls-files', '--others', '--exclude-standard', '-z']),\n ]);\n\n const files = parseNameStatusEntries(diffOutput);\n for (const path of untrackedOutput.split('\\0')) {\n if (path !== '') {\n files.push({ status: 'added', headPath: path });\n }\n }\n return files;\n}\n\n/** Parses `git diff --name-status -z` output: `<status>\\0<path>\\0`, with two paths for R/C. */\nfunction parseNameStatusEntries(output: string): ChangedFile[] {\n const files: ChangedFile[] = [];\n const parts = output.split('\\0');\n let index = 0;\n while (index < parts.length - 1) {\n const kind = (parts[index] as string).charAt(0);\n const pathCount = kind === 'R' || kind === 'C' ? 2 : 1;\n const changed = toChangedFile(kind, parts.slice(index + 1, index + 1 + pathCount));\n if (changed) {\n files.push(changed);\n }\n index += 1 + pathCount;\n }\n return files;\n}\n\n/** Undefined for unmerged (U) and unknown statuses, which have nothing to check. */\nfunction toChangedFile(kind: string, paths: (string | undefined)[]): ChangedFile | undefined {\n const [first, second] = paths;\n if (first === undefined) {\n return undefined;\n }\n switch (kind) {\n case 'R': {\n return second === undefined ? undefined : { status: 'renamed', basePath: first, headPath: second };\n }\n // A copy is an addition: every line of the new path is new.\n case 'C': {\n return second === undefined ? undefined : { status: 'added', headPath: second };\n }\n case 'A': {\n return { status: 'added', headPath: first };\n }\n case 'D': {\n return { status: 'deleted', basePath: first, headPath: first };\n }\n case 'M':\n case 'T': {\n return { status: 'modified', basePath: first, headPath: first };\n }\n default: {\n return undefined;\n }\n }\n}\n\n/**\n * Repository-relative paths git considers part of the project AND that exist in the working tree:\n * tracked files (minus worktree deletions) plus untracked non-ignored ones. `check` scans\n * exactly these, so local ignored artifacts (build output, generated copies) are neither measured\n * nor allowed to skew duplication detection.\n */\nexport async function listRepositoryFiles(repoRoot: string): Promise<Set<string>> {\n const [listed, deleted] = await Promise.all([\n runGit(repoRoot, ['ls-files', '--cached', '--others', '--exclude-standard', '-z']),\n runGit(repoRoot, ['ls-files', '--deleted', '-z']),\n ]);\n const files = new Set(listed.split('\\0').filter((path) => path !== ''));\n for (const path of deleted.split('\\0')) {\n files.delete(path);\n }\n return files;\n}\n\n/**\n * The line hunks turning the merge-base blob at `basePath` into the working-tree file at\n * `headPath` (equal unless renamed), as `git diff` computes them for review.\n */\nexport async function listLineHunks(\n repoRoot: string,\n mergeBase: string,\n basePath: string,\n headPath: string\n): Promise<LineHunk[]> {\n // Literal pathspecs: a name like `app/[slug]/page.tsx` must not glob-match other files.\n const paths = (basePath === headPath ? [headPath] : [basePath, headPath]).map((file) => `:(literal)${file}`);\n const output = await runGit(repoRoot, [\n // The algorithm and indent heuristic decide which lines count as added, so user config must\n // not change them; `-c` values an older git does not know are ignored rather than rejected.\n '-c',\n 'diff.algorithm=myers',\n '-c',\n 'diff.indentHeuristic=true',\n 'diff',\n '--unified=0',\n // diff.interHunkContext would otherwise merge nearby hunks, and the unchanged lines between\n // them would count as added.\n '--inter-hunk-context=0',\n // A file git would show as binary (a `-diff` attribute, a NUL byte) must still yield hunks.\n '--text',\n '--no-color',\n '--no-ext-diff',\n '--no-textconv',\n '--find-renames',\n mergeBase,\n '--',\n ...paths,\n ]);\n const headers = [...output.matchAll(/^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,(\\d+))? @@.*$/gmu)];\n return headers.map((match, index) => {\n const [header, baseStart, headStart, headCount] = match;\n const body = output.slice(match.index + header.length, headers[index + 1]?.index ?? output.length);\n return {\n headStart: Number(headStart),\n headCount: headCount === undefined ? 1 : Number(headCount),\n baseStart: Number(baseStart),\n deletedLines: body\n .split('\\n')\n .filter((line) => line.startsWith('-'))\n // A CRLF file's lines keep their carriage return after splitting the diff on line feeds.\n .map((line) => line.slice(1).replace(/\\r$/u, '')),\n };\n });\n}\n\n/** The content of a file at a commit. */\nexport async function readFileAt(repoRoot: string, commit: string, file: string): Promise<string> {\n return await runGit(repoRoot, ['show', `${commit}:${file}`]);\n}\n\n/** A file's `linguist-generated` / `linguist-vendored` attributes; undefined when unspecified. */\nexport interface LinguistAttributes {\n generated?: boolean;\n vendored?: boolean;\n}\n\n/**\n * The linguist attributes of repository-relative paths, from the working tree's `.gitattributes`.\n * Paths with neither attribute specified are omitted.\n */\nexport async function readLinguistAttributes(\n repoRoot: string,\n paths: Iterable<string>\n): Promise<Map<string, LinguistAttributes>> {\n const input = [...paths].join('\\0');\n if (input === '') {\n return new Map();\n }\n const output = await runGitWithInput(\n repoRoot,\n ['check-attr', '--stdin', '-z', 'linguist-generated', 'linguist-vendored'],\n input\n );\n const attributesByPath = new Map<string, LinguistAttributes>();\n const fields = output.split('\\0');\n for (let index = 0; index + 2 < fields.length; index += 3) {\n const [file, attribute, value] = fields.slice(index, index + 3) as [string, string, string];\n const enabled = toAttributeFlag(value);\n if (enabled === undefined) {\n continue;\n }\n const attributes = attributesByPath.get(file) ?? {};\n if (attribute === 'linguist-generated') {\n attributes.generated = enabled;\n } else {\n attributes.vendored = enabled;\n }\n attributesByPath.set(file, attributes);\n }\n return attributesByPath;\n}\n\n/** Linguist reads `attr`/`attr=true` as set and `-attr`/`attr=false` as explicitly unset. */\nfunction toAttributeFlag(value: string): boolean | undefined {\n if (value === 'set' || value === 'true') {\n return true;\n }\n if (value === 'unset' || value === 'false') {\n return false;\n }\n return undefined;\n}\n\nasync function runGitWithInput(cwd: string, args: string[], input: string): Promise<string> {\n return await new Promise((resolve, reject) => {\n const child = spawn('git', args, { cwd });\n // git exiting before it reads all input (e.g. on a usage error) makes the write fail with\n // EPIPE; its exit code and stderr, reported on close, already describe the failure.\n child.stdin.on('error', () => {});\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve(Buffer.concat(stdout).toString('utf8'));\n return;\n }\n const message = Buffer.concat(stderr).toString('utf8').trim();\n reject(new Error(`git ${describeCommand(args)} failed${message ? `: ${message}` : ''}`));\n });\n child.stdin.end(input);\n });\n}\n\nasync function runGit(cwd: string, args: string[]): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, { cwd, maxBuffer: maxOutputBytes, encoding: 'utf8' });\n return stdout;\n } catch (error) {\n const stderr = (error as { stderr?: string }).stderr?.trim();\n throw new Error(`git ${describeCommand(args)} failed${stderr ? `: ${stderr}` : ''}`);\n }\n}\n\n/** The subcommand and its first argument, skipping leading `-c key=value` overrides. */\nfunction describeCommand(args: readonly string[]): string {\n let start = 0;\n while (args[start] === '-c') {\n start += 2;\n }\n return args.slice(start, start + 2).join(' ');\n}\n"],"mappings":"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"}
|
package/dist/languages.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"languages.cjs","names":["defaultLanguages","createLanguageRegistry","registry","language","languages","alias","supportedLanguages","languageByExtension","detectLanguage","extension","extname","filePath","separatorIndex","baseName","dotIndex"],"sources":["../src/languages.ts"],"sourcesContent":["import type { LanguageDefinition, LanguageName, SupportedLanguage } from './types.js';\n\n/**\n * The built-in languages. Grammars and per-language node-type configuration live in the Rust\n * addon (native/src/languages.rs); this list only names the languages and their aliases so the\n * CLI and API can resolve and enumerate them without crossing the N-API boundary.\n */\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n { name: 'javascript', aliases: ['js', 'mjs', 'cjs'] },\n { name: 'jsx' },\n { name: 'typescript', aliases: ['ts'] },\n { name: 'tsx' },\n { name: 'python', aliases: ['py'] },\n { name: 'go' },\n { name: 'rust', aliases: ['rs'] },\n { name: 'java' },\n { name: 'ruby', aliases: ['rb'] },\n { name: 'c' },\n { name: 'cpp', aliases: ['c++', 'cxx'] },\n { name: 'csharp', aliases: ['cs', 'c#'] },\n { name: 'kotlin', aliases: ['kt', 'kts'] },\n];\n\nexport function createLanguageRegistry(\n languages: readonly LanguageDefinition[] = defaultLanguages\n): Map<LanguageName, LanguageDefinition> {\n const registry = new Map<LanguageName, LanguageDefinition>();\n\n for (const language of languages) {\n registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n registry.set(alias, language);\n }\n }\n\n return registry;\n}\n\nexport const supportedLanguages = defaultLanguages.map((language) => language.name);\n\nconst languageByExtension = new Map<string, SupportedLanguage>([\n ['.c', 'c'],\n ['.c++', 'cpp'],\n ['.cc', 'cpp'],\n ['.cjs', 'javascript'],\n ['.cp', 'cpp'],\n ['.cpp', 'cpp'],\n ['.cs', 'csharp'],\n ['.tcc', 'cpp'],\n ['.cts', 'typescript'],\n ['.cxx', 'cpp'],\n ['.go', 'go'],\n // Headers may be C or C++; the C++ grammar parses both.\n ['.h', 'cpp'],\n ['.hh', 'cpp'],\n ['.hpp', 'cpp'],\n ['.hxx', 'cpp'],\n ['.java', 'java'],\n ['.js', 'javascript'],\n ['.jsx', 'jsx'],\n ['.kt', 'kotlin'],\n ['.kts', 'kotlin'],\n ['.mjs', 'javascript'],\n ['.mts', 'typescript'],\n ['.py', 'python'],\n ['.rb', 'ruby'],\n ['.rs', 'rust'],\n ['.ts', 'typescript'],\n ['.tsx', 'tsx'],\n]);\n\n/** Detects the language of a source file from its extension, or `undefined` when unsupported. */\nexport function detectLanguage(filePath: string): SupportedLanguage | undefined {\n const extension = extname(filePath);\n // GCC treats an uppercase `.C` as C++; lowercasing first would misparse it with the C grammar.\n if (extension === '.C') {\n return 'cpp';\n }\n return languageByExtension.get(extension.toLowerCase());\n}\n\n/**\n * path.extname() without node:path, which runtimes such as Cloudflare Workers lack. Like Node.js,\n * backslashes separate paths only on Windows; elsewhere they are ordinary file-name characters.\n */\nfunction extname(filePath: string): string {\n const separatorIndex = Math.max(\n filePath.lastIndexOf('/'),\n globalThis.process?.platform === 'win32' ? filePath.lastIndexOf('\\\\') : -1\n );\n const baseName = filePath.slice(separatorIndex + 1);\n const dotIndex = baseName.lastIndexOf('.');\n return dotIndex > 0 ? baseName.slice(dotIndex) : '';\n}\n"],"mappings":"aAOA,MAAaA,EAAkD,CAC7D,CAAE,KAAM,aAAc,QAAS,CAAC,KAAM,MAAO,KAAK,CAAE,EACpD,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,aAAc,QAAS,CAAC,IAAI,CAAE,EACtC,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,SAAU,QAAS,CAAC,IAAI,CAAE,EAClC,CAAE,KAAM,IAAK,EACb,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,MAAO,EACf,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,GAAI,EACZ,CAAE,KAAM,MAAO,QAAS,CAAC,MAAO,KAAK,CAAE,EACvC,CAAE,KAAM,SAAU,QAAS,CAAC,KAAM,IAAI,CAAE,EACxC,CAAE,KAAM,SAAU,QAAS,CAAC,KAAM,KAAK,CAAE,CAC3C,EAEA,SAAgBC,EACd,EAA2CD,EACJ,CACvC,IAAME,kBAAW,IAAI,IAErB,IAAK,IAAMC,KAAYC,EAAW,CAChCF,EAAS,IAAIC,EAAS,KAAMA,CAAQ,EACpC,IAAK,IAAME,KAASF,EAAS,SAAW,CAAC,EACvCD,EAAS,IAAIG,EAAOF,CAAQ,CAEhC,CAEA,OAAOD,CACT,CAEA,MAAaI,EAAqBN,EAAiB,IAAK,GAAaG,EAAS,IAAI,EAE5EI,kBAAsB,IAAI,IAA+B,CAC7D,CAAC,KAAM,GAAG,EACV,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,YAAY,EACrB,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,QAAQ,EAChB,CAAC,OAAQ,KAAK,EACd,CAAC,OAAQ,YAAY,EACrB,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,IAAI,EAEZ,CAAC,KAAM,KAAK,EACZ,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,KAAK,EACd,CAAC,OAAQ,KAAK,EACd,CAAC,QAAS,MAAM,EAChB,CAAC,MAAO,YAAY,EACpB,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,QAAQ,EAChB,CAAC,OAAQ,QAAQ,EACjB,CAAC,OAAQ,YAAY,EACrB,CAAC,OAAQ,YAAY,EACrB,CAAC,MAAO,QAAQ,EAChB,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,YAAY,EACpB,CAAC,OAAQ,KAAK,CAChB,CAAC,EAGD,SAAgBC,EAAe,EAAiD,CAC9E,IAAMC,EAAYC,EAAQC,CAAQ,EAKlC,OAHIF,IAAc,KACT,MAEFF,EAAoB,IAAIE,EAAU,YAAY,CAAC,CACxD,CAMA,SAASC,EAAQ,EAA0B,CACzC,IAAME,EAAiB,KAAK,IAC1BD,EAAS,YAAY,GAAG,EACxB,WAAW,SAAS,WAAa,QAAUA,EAAS,YAAY,IAAI,EAAI,EAC1E,EACME,EAAWF,EAAS,MAAMC,EAAiB,CAAC,EAC5CE,EAAWD,EAAS,YAAY,GAAG,EACzC,OAAOC,EAAW,EAAID,EAAS,MAAMC,CAAQ,EAAI,EACnD"}
|
|
1
|
+
{"version":3,"file":"languages.cjs","names":["defaultLanguages","createLanguageRegistry","registry","language","languages","alias","supportedLanguages","languageByExtension","detectLanguage","extension","extname","filePath","separatorIndex","baseName","dotIndex"],"sources":["../src/languages.ts"],"sourcesContent":["import type { LanguageDefinition, LanguageName, SupportedLanguage } from './types.js';\n\n/**\n * The built-in languages. Grammars and per-language node-type configuration live in the Rust\n * addon (native/src/languages.rs); this list only names the languages and their aliases so the\n * CLI and API can resolve and enumerate them without crossing the N-API boundary.\n */\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n { name: 'javascript', aliases: ['js', 'mjs', 'cjs'] },\n { name: 'jsx' },\n { name: 'typescript', aliases: ['ts'] },\n { name: 'tsx' },\n { name: 'python', aliases: ['py'] },\n { name: 'go' },\n { name: 'rust', aliases: ['rs'] },\n { name: 'java' },\n { name: 'ruby', aliases: ['rb'] },\n { name: 'c' },\n { name: 'cpp', aliases: ['c++', 'cxx'] },\n { name: 'csharp', aliases: ['cs', 'c#'] },\n { name: 'kotlin', aliases: ['kt', 'kts'] },\n];\n\nexport function createLanguageRegistry(\n languages: readonly LanguageDefinition[] = defaultLanguages\n): Map<LanguageName, LanguageDefinition> {\n const registry = new Map<LanguageName, LanguageDefinition>();\n\n for (const language of languages) {\n registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n registry.set(alias, language);\n }\n }\n\n return registry;\n}\n\nexport const supportedLanguages = defaultLanguages.map((language) => language.name);\n\nconst languageByExtension = new Map<string, SupportedLanguage>([\n ['.c', 'c'],\n ['.c++', 'cpp'],\n ['.cc', 'cpp'],\n ['.cjs', 'javascript'],\n ['.cp', 'cpp'],\n ['.cpp', 'cpp'],\n ['.cs', 'csharp'],\n ['.tcc', 'cpp'],\n ['.cts', 'typescript'],\n ['.cxx', 'cpp'],\n ['.go', 'go'],\n // Headers may be C or C++; the C++ grammar parses both.\n ['.h', 'cpp'],\n ['.hh', 'cpp'],\n ['.hpp', 'cpp'],\n ['.hxx', 'cpp'],\n ['.java', 'java'],\n ['.js', 'javascript'],\n ['.jsx', 'jsx'],\n ['.kt', 'kotlin'],\n ['.kts', 'kotlin'],\n ['.mjs', 'javascript'],\n ['.mts', 'typescript'],\n ['.py', 'python'],\n ['.rb', 'ruby'],\n ['.rs', 'rust'],\n ['.ts', 'typescript'],\n ['.tsx', 'tsx'],\n]);\n\n/** Detects the language of a source file from its extension, or `undefined` when unsupported. */\nexport function detectLanguage(filePath: string): SupportedLanguage | undefined {\n const extension = extname(filePath);\n // GCC treats an uppercase `.C` as C++; lowercasing first would misparse it with the C grammar.\n if (extension === '.C') {\n return 'cpp';\n }\n return languageByExtension.get(extension.toLowerCase());\n}\n\n/**\n * path.extname() without node:path, which runtimes such as Cloudflare Workers lack. Like Node.js,\n * backslashes separate paths only on Windows; elsewhere they are ordinary file-name characters.\n */\nfunction extname(filePath: string): string {\n const separatorIndex = Math.max(\n filePath.lastIndexOf('/'),\n globalThis.process?.platform === 'win32' ? filePath.lastIndexOf('\\\\') : -1\n );\n const baseName = filePath.slice(separatorIndex + 1);\n const dotIndex = baseName.lastIndexOf('.');\n return dotIndex > 0 ? baseName.slice(dotIndex) : '';\n}\n"],"mappings":"A,aAOA,MAAaA,EAAkD,CAC7D,CAAE,KAAM,aAAc,QAAS,CAAC,KAAM,MAAO,KAAK,CAAE,EACpD,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,aAAc,QAAS,CAAC,IAAI,CAAE,EACtC,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,SAAU,QAAS,CAAC,IAAI,CAAE,EAClC,CAAE,KAAM,IAAK,EACb,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,MAAO,EACf,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,GAAI,EACZ,CAAE,KAAM,MAAO,QAAS,CAAC,MAAO,KAAK,CAAE,EACvC,CAAE,KAAM,SAAU,QAAS,CAAC,KAAM,IAAI,CAAE,EACxC,CAAE,KAAM,SAAU,QAAS,CAAC,KAAM,KAAK,CAAE,CAC3C,EAEA,SAAgBC,EACd,EAA2CD,EACJ,CACvC,IAAME,kBAAW,IAAI,IAErB,IAAK,IAAMC,KAAYC,EAAW,CAChCF,EAAS,IAAIC,EAAS,KAAMA,CAAQ,EACpC,IAAK,IAAME,KAASF,EAAS,SAAW,CAAC,EACvCD,EAAS,IAAIG,EAAOF,CAAQ,CAEhC,CAEA,OAAOD,CACT,CAEA,MAAaI,EAAqBN,EAAiB,IAAK,GAAaG,EAAS,IAAI,EAE5EI,kBAAsB,IAAI,IAA+B,CAC7D,CAAC,KAAM,GAAG,EACV,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,YAAY,EACrB,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,QAAQ,EAChB,CAAC,OAAQ,KAAK,EACd,CAAC,OAAQ,YAAY,EACrB,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,IAAI,EAEZ,CAAC,KAAM,KAAK,EACZ,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,KAAK,EACd,CAAC,OAAQ,KAAK,EACd,CAAC,QAAS,MAAM,EAChB,CAAC,MAAO,YAAY,EACpB,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,QAAQ,EAChB,CAAC,OAAQ,QAAQ,EACjB,CAAC,OAAQ,YAAY,EACrB,CAAC,OAAQ,YAAY,EACrB,CAAC,MAAO,QAAQ,EAChB,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,YAAY,EACpB,CAAC,OAAQ,KAAK,CAChB,CAAC,EAGD,SAAgBC,EAAe,EAAiD,CAC9E,IAAMC,EAAYC,EAAQC,CAAQ,EAKlC,OAHIF,IAAc,KACT,MAEFF,EAAoB,IAAIE,EAAU,YAAY,CAAC,CACxD,CAMA,SAASC,EAAQ,EAA0B,CACzC,IAAME,EAAiB,KAAK,IAC1BD,EAAS,YAAY,GAAG,EACxB,WAAW,SAAS,WAAa,QAAUA,EAAS,YAAY,IAAI,EAAI,EAC1E,EACME,EAAWF,EAAS,MAAMC,EAAiB,CAAC,EAC5CE,EAAWD,EAAS,YAAY,GAAG,EACzC,OAAOC,EAAW,EAAID,EAAS,MAAMC,CAAQ,EAAI,EACnD,C"}
|
package/dist/languages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"languages.js","names":["defaultLanguages","createLanguageRegistry","registry","language","languages","alias","supportedLanguages","languageByExtension","detectLanguage","extension","extname","filePath","separatorIndex","baseName","dotIndex"],"sources":["../src/languages.ts"],"sourcesContent":["import type { LanguageDefinition, LanguageName, SupportedLanguage } from './types.js';\n\n/**\n * The built-in languages. Grammars and per-language node-type configuration live in the Rust\n * addon (native/src/languages.rs); this list only names the languages and their aliases so the\n * CLI and API can resolve and enumerate them without crossing the N-API boundary.\n */\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n { name: 'javascript', aliases: ['js', 'mjs', 'cjs'] },\n { name: 'jsx' },\n { name: 'typescript', aliases: ['ts'] },\n { name: 'tsx' },\n { name: 'python', aliases: ['py'] },\n { name: 'go' },\n { name: 'rust', aliases: ['rs'] },\n { name: 'java' },\n { name: 'ruby', aliases: ['rb'] },\n { name: 'c' },\n { name: 'cpp', aliases: ['c++', 'cxx'] },\n { name: 'csharp', aliases: ['cs', 'c#'] },\n { name: 'kotlin', aliases: ['kt', 'kts'] },\n];\n\nexport function createLanguageRegistry(\n languages: readonly LanguageDefinition[] = defaultLanguages\n): Map<LanguageName, LanguageDefinition> {\n const registry = new Map<LanguageName, LanguageDefinition>();\n\n for (const language of languages) {\n registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n registry.set(alias, language);\n }\n }\n\n return registry;\n}\n\nexport const supportedLanguages = defaultLanguages.map((language) => language.name);\n\nconst languageByExtension = new Map<string, SupportedLanguage>([\n ['.c', 'c'],\n ['.c++', 'cpp'],\n ['.cc', 'cpp'],\n ['.cjs', 'javascript'],\n ['.cp', 'cpp'],\n ['.cpp', 'cpp'],\n ['.cs', 'csharp'],\n ['.tcc', 'cpp'],\n ['.cts', 'typescript'],\n ['.cxx', 'cpp'],\n ['.go', 'go'],\n // Headers may be C or C++; the C++ grammar parses both.\n ['.h', 'cpp'],\n ['.hh', 'cpp'],\n ['.hpp', 'cpp'],\n ['.hxx', 'cpp'],\n ['.java', 'java'],\n ['.js', 'javascript'],\n ['.jsx', 'jsx'],\n ['.kt', 'kotlin'],\n ['.kts', 'kotlin'],\n ['.mjs', 'javascript'],\n ['.mts', 'typescript'],\n ['.py', 'python'],\n ['.rb', 'ruby'],\n ['.rs', 'rust'],\n ['.ts', 'typescript'],\n ['.tsx', 'tsx'],\n]);\n\n/** Detects the language of a source file from its extension, or `undefined` when unsupported. */\nexport function detectLanguage(filePath: string): SupportedLanguage | undefined {\n const extension = extname(filePath);\n // GCC treats an uppercase `.C` as C++; lowercasing first would misparse it with the C grammar.\n if (extension === '.C') {\n return 'cpp';\n }\n return languageByExtension.get(extension.toLowerCase());\n}\n\n/**\n * path.extname() without node:path, which runtimes such as Cloudflare Workers lack. Like Node.js,\n * backslashes separate paths only on Windows; elsewhere they are ordinary file-name characters.\n */\nfunction extname(filePath: string): string {\n const separatorIndex = Math.max(\n filePath.lastIndexOf('/'),\n globalThis.process?.platform === 'win32' ? filePath.lastIndexOf('\\\\') : -1\n );\n const baseName = filePath.slice(separatorIndex + 1);\n const dotIndex = baseName.lastIndexOf('.');\n return dotIndex > 0 ? baseName.slice(dotIndex) : '';\n}\n"],"mappings":"AAOA,MAAaA,EAAkD,CAC7D,CAAE,KAAM,aAAc,QAAS,CAAC,KAAM,MAAO,KAAK,CAAE,EACpD,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,aAAc,QAAS,CAAC,IAAI,CAAE,EACtC,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,SAAU,QAAS,CAAC,IAAI,CAAE,EAClC,CAAE,KAAM,IAAK,EACb,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,MAAO,EACf,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,GAAI,EACZ,CAAE,KAAM,MAAO,QAAS,CAAC,MAAO,KAAK,CAAE,EACvC,CAAE,KAAM,SAAU,QAAS,CAAC,KAAM,IAAI,CAAE,EACxC,CAAE,KAAM,SAAU,QAAS,CAAC,KAAM,KAAK,CAAE,CAC3C,EAEA,SAAgBC,EACd,EAA2CD,EACJ,CACvC,IAAME,kBAAW,IAAI,IAErB,IAAK,IAAMC,KAAYC,EAAW,CAChCF,EAAS,IAAIC,EAAS,KAAMA,CAAQ,EACpC,IAAK,IAAME,KAASF,EAAS,SAAW,CAAC,EACvCD,EAAS,IAAIG,EAAOF,CAAQ,CAEhC,CAEA,OAAOD,CACT,CAEA,MAAaI,EAAqBN,EAAiB,IAAK,GAAaG,EAAS,IAAI,EAE5EI,kBAAsB,IAAI,IAA+B,CAC7D,CAAC,KAAM,GAAG,EACV,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,YAAY,EACrB,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,QAAQ,EAChB,CAAC,OAAQ,KAAK,EACd,CAAC,OAAQ,YAAY,EACrB,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,IAAI,EAEZ,CAAC,KAAM,KAAK,EACZ,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,KAAK,EACd,CAAC,OAAQ,KAAK,EACd,CAAC,QAAS,MAAM,EAChB,CAAC,MAAO,YAAY,EACpB,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,QAAQ,EAChB,CAAC,OAAQ,QAAQ,EACjB,CAAC,OAAQ,YAAY,EACrB,CAAC,OAAQ,YAAY,EACrB,CAAC,MAAO,QAAQ,EAChB,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,YAAY,EACpB,CAAC,OAAQ,KAAK,CAChB,CAAC,EAGD,SAAgBC,EAAe,EAAiD,CAC9E,IAAMC,EAAYC,EAAQC,CAAQ,EAKlC,OAHIF,IAAc,KACT,MAEFF,EAAoB,IAAIE,EAAU,YAAY,CAAC,CACxD,CAMA,SAASC,EAAQ,EAA0B,CACzC,IAAME,EAAiB,KAAK,IAC1BD,EAAS,YAAY,GAAG,EACxB,WAAW,SAAS,WAAa,QAAUA,EAAS,YAAY,IAAI,EAAI,EAC1E,EACME,EAAWF,EAAS,MAAMC,EAAiB,CAAC,EAC5CE,EAAWD,EAAS,YAAY,GAAG,EACzC,OAAOC,EAAW,EAAID,EAAS,MAAMC,CAAQ,EAAI,EACnD"}
|
|
1
|
+
{"version":3,"file":"languages.js","names":["defaultLanguages","createLanguageRegistry","registry","language","languages","alias","supportedLanguages","languageByExtension","detectLanguage","extension","extname","filePath","separatorIndex","baseName","dotIndex"],"sources":["../src/languages.ts"],"sourcesContent":["import type { LanguageDefinition, LanguageName, SupportedLanguage } from './types.js';\n\n/**\n * The built-in languages. Grammars and per-language node-type configuration live in the Rust\n * addon (native/src/languages.rs); this list only names the languages and their aliases so the\n * CLI and API can resolve and enumerate them without crossing the N-API boundary.\n */\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n { name: 'javascript', aliases: ['js', 'mjs', 'cjs'] },\n { name: 'jsx' },\n { name: 'typescript', aliases: ['ts'] },\n { name: 'tsx' },\n { name: 'python', aliases: ['py'] },\n { name: 'go' },\n { name: 'rust', aliases: ['rs'] },\n { name: 'java' },\n { name: 'ruby', aliases: ['rb'] },\n { name: 'c' },\n { name: 'cpp', aliases: ['c++', 'cxx'] },\n { name: 'csharp', aliases: ['cs', 'c#'] },\n { name: 'kotlin', aliases: ['kt', 'kts'] },\n];\n\nexport function createLanguageRegistry(\n languages: readonly LanguageDefinition[] = defaultLanguages\n): Map<LanguageName, LanguageDefinition> {\n const registry = new Map<LanguageName, LanguageDefinition>();\n\n for (const language of languages) {\n registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n registry.set(alias, language);\n }\n }\n\n return registry;\n}\n\nexport const supportedLanguages = defaultLanguages.map((language) => language.name);\n\nconst languageByExtension = new Map<string, SupportedLanguage>([\n ['.c', 'c'],\n ['.c++', 'cpp'],\n ['.cc', 'cpp'],\n ['.cjs', 'javascript'],\n ['.cp', 'cpp'],\n ['.cpp', 'cpp'],\n ['.cs', 'csharp'],\n ['.tcc', 'cpp'],\n ['.cts', 'typescript'],\n ['.cxx', 'cpp'],\n ['.go', 'go'],\n // Headers may be C or C++; the C++ grammar parses both.\n ['.h', 'cpp'],\n ['.hh', 'cpp'],\n ['.hpp', 'cpp'],\n ['.hxx', 'cpp'],\n ['.java', 'java'],\n ['.js', 'javascript'],\n ['.jsx', 'jsx'],\n ['.kt', 'kotlin'],\n ['.kts', 'kotlin'],\n ['.mjs', 'javascript'],\n ['.mts', 'typescript'],\n ['.py', 'python'],\n ['.rb', 'ruby'],\n ['.rs', 'rust'],\n ['.ts', 'typescript'],\n ['.tsx', 'tsx'],\n]);\n\n/** Detects the language of a source file from its extension, or `undefined` when unsupported. */\nexport function detectLanguage(filePath: string): SupportedLanguage | undefined {\n const extension = extname(filePath);\n // GCC treats an uppercase `.C` as C++; lowercasing first would misparse it with the C grammar.\n if (extension === '.C') {\n return 'cpp';\n }\n return languageByExtension.get(extension.toLowerCase());\n}\n\n/**\n * path.extname() without node:path, which runtimes such as Cloudflare Workers lack. Like Node.js,\n * backslashes separate paths only on Windows; elsewhere they are ordinary file-name characters.\n */\nfunction extname(filePath: string): string {\n const separatorIndex = Math.max(\n filePath.lastIndexOf('/'),\n globalThis.process?.platform === 'win32' ? filePath.lastIndexOf('\\\\') : -1\n );\n const baseName = filePath.slice(separatorIndex + 1);\n const dotIndex = baseName.lastIndexOf('.');\n return dotIndex > 0 ? baseName.slice(dotIndex) : '';\n}\n"],"mappings":"AAOA,MAAaA,EAAkD,CAC7D,CAAE,KAAM,aAAc,QAAS,CAAC,KAAM,MAAO,KAAK,CAAE,EACpD,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,aAAc,QAAS,CAAC,IAAI,CAAE,EACtC,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,SAAU,QAAS,CAAC,IAAI,CAAE,EAClC,CAAE,KAAM,IAAK,EACb,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,MAAO,EACf,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,GAAI,EACZ,CAAE,KAAM,MAAO,QAAS,CAAC,MAAO,KAAK,CAAE,EACvC,CAAE,KAAM,SAAU,QAAS,CAAC,KAAM,IAAI,CAAE,EACxC,CAAE,KAAM,SAAU,QAAS,CAAC,KAAM,KAAK,CAAE,CAC3C,EAEA,SAAgBC,EACd,EAA2CD,EACJ,CACvC,IAAME,kBAAW,IAAI,IAErB,IAAK,IAAMC,KAAYC,EAAW,CAChCF,EAAS,IAAIC,EAAS,KAAMA,CAAQ,EACpC,IAAK,IAAME,KAASF,EAAS,SAAW,CAAC,EACvCD,EAAS,IAAIG,EAAOF,CAAQ,CAEhC,CAEA,OAAOD,CACT,CAEA,MAAaI,EAAqBN,EAAiB,IAAK,GAAaG,EAAS,IAAI,EAE5EI,kBAAsB,IAAI,IAA+B,CAC7D,CAAC,KAAM,GAAG,EACV,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,YAAY,EACrB,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,QAAQ,EAChB,CAAC,OAAQ,KAAK,EACd,CAAC,OAAQ,YAAY,EACrB,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,IAAI,EAEZ,CAAC,KAAM,KAAK,EACZ,CAAC,MAAO,KAAK,EACb,CAAC,OAAQ,KAAK,EACd,CAAC,OAAQ,KAAK,EACd,CAAC,QAAS,MAAM,EAChB,CAAC,MAAO,YAAY,EACpB,CAAC,OAAQ,KAAK,EACd,CAAC,MAAO,QAAQ,EAChB,CAAC,OAAQ,QAAQ,EACjB,CAAC,OAAQ,YAAY,EACrB,CAAC,OAAQ,YAAY,EACrB,CAAC,MAAO,QAAQ,EAChB,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,YAAY,EACpB,CAAC,OAAQ,KAAK,CAChB,CAAC,EAGD,SAAgBC,EAAe,EAAiD,CAC9E,IAAMC,EAAYC,EAAQC,CAAQ,EAKlC,OAHIF,IAAc,KACT,MAEFF,EAAoB,IAAIE,EAAU,YAAY,CAAC,CACxD,CAMA,SAASC,EAAQ,EAA0B,CACzC,IAAME,EAAiB,KAAK,IAC1BD,EAAS,YAAY,GAAG,EACxB,WAAW,SAAS,WAAa,QAAUA,EAAS,YAAY,IAAI,EAAI,EAC1E,EACME,EAAWF,EAAS,MAAMC,EAAiB,CAAC,EAC5CE,EAAWD,EAAS,YAAY,GAAG,EACzC,OAAOC,EAAW,EAAID,EAAS,MAAMC,CAAQ,EAAI,EACnD,C"}
|
package/dist/metrics.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.cjs","names":["createLanguageRegistry","measureCodeNative","measureCodeNativeAsync","collectCrossFileDataNative","collectFunctionTokenSequencesNative","require_languages","require_nativeMetrics","TreeMeasurer","language","options","includeSyntaxTree","assembleNativeMetrics","code","assembleMetricsWithCrossFileData","toCrossFileDuplicationFileData","name","payload","fn","deriveHalsteadMetrics","counts","vocabulary","distinctOperators","distinctOperands","length","totalOperators","totalOperands","volume","difficulty","defaultMeasurer","measureCode","measureCodeWithCrossFileDataAsync","collectDuplicationCandidates","collectFunctionTokenSequences","collectCrossFileDuplicationFileData"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n measureCodeNativeAsync,\n type NativeCrossFileDataPayload,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Measures a file and collects its cross-file clone-detection contribution from one parse. */\n measureWithCrossFileData(\n code: string,\n options: MeasureOptions\n ): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** measureWithCrossFileData on the native addon's worker threads, for measuring files in parallel. */\n async measureWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n ): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = await measureCodeNativeAsync(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n return toCrossFileDuplicationFileData(\n collectCrossFileDataNative(code, language.name, options.duplication?.minTokens)\n );\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\nfunction assembleMetricsWithCrossFileData(\n payload: NativeMetricsPayload,\n includeSyntaxTree: boolean\n): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n if (!payload.crossFileData) {\n throw new Error('The native addon returned no cross-file duplication data');\n }\n return {\n metrics: assembleNativeMetrics(payload, includeSyntaxTree),\n crossFileData: toCrossFileDuplicationFileData(payload.crossFileData),\n };\n}\n\nfunction toCrossFileDuplicationFileData(payload: NativeCrossFileDataPayload): CrossFileDuplicationFileData {\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n nearMissBlocks: payload.nearMissBlocks,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n codeLineNumbers: payload.codeLineNumbers,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n containerName: fn.containerName,\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 cognitiveBlocks: fn.cognitiveBlocks ?? [],\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n difficulty,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring TreeMeasurer.measureWithCrossFileDataAsync for the default measurer. */\nexport function measureCodeWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n return defaultMeasurer.measureWithCrossFileDataAsync(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"metrics.cjs","names":["createLanguageRegistry","measureCodeNative","measureCodeNativeAsync","collectCrossFileDataNative","collectFunctionTokenSequencesNative","require_languages","require_nativeMetrics","TreeMeasurer","language","options","includeSyntaxTree","assembleNativeMetrics","code","assembleMetricsWithCrossFileData","toCrossFileDuplicationFileData","name","payload","fn","deriveHalsteadMetrics","counts","vocabulary","distinctOperators","distinctOperands","length","totalOperators","totalOperands","volume","difficulty","defaultMeasurer","measureCode","measureCodeWithCrossFileDataAsync","collectDuplicationCandidates","collectFunctionTokenSequences","collectCrossFileDuplicationFileData"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n measureCodeNativeAsync,\n type NativeCrossFileDataPayload,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Measures a file and collects its cross-file clone-detection contribution from one parse. */\n measureWithCrossFileData(\n code: string,\n options: MeasureOptions\n ): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** measureWithCrossFileData on the native addon's worker threads, for measuring files in parallel. */\n async measureWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n ): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = await measureCodeNativeAsync(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n return toCrossFileDuplicationFileData(\n collectCrossFileDataNative(code, language.name, options.duplication?.minTokens)\n );\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\nfunction assembleMetricsWithCrossFileData(\n payload: NativeMetricsPayload,\n includeSyntaxTree: boolean\n): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n if (!payload.crossFileData) {\n throw new Error('The native addon returned no cross-file duplication data');\n }\n return {\n metrics: assembleNativeMetrics(payload, includeSyntaxTree),\n crossFileData: toCrossFileDuplicationFileData(payload.crossFileData),\n };\n}\n\nfunction toCrossFileDuplicationFileData(payload: NativeCrossFileDataPayload): CrossFileDuplicationFileData {\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n nearMissBlocks: payload.nearMissBlocks,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n codeLineNumbers: payload.codeLineNumbers,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n containerName: fn.containerName,\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 cognitiveBlocks: fn.cognitiveBlocks ?? [],\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n difficulty,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring TreeMeasurer.measureWithCrossFileDataAsync for the default measurer. */\nexport function measureCodeWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n return defaultMeasurer.measureWithCrossFileDataAsync(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"A,iFAkBA,IAAaO,EAAb,KAA0B,CACxB,SAA4BP,EAAAA,uBAAuB,EAEnD,uBAAwC,CACtC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,IAAK,GAAaQ,EAAS,IAAI,CAAC,CAAC,CAClF,CAEA,QAAQ,EAAc,EAAsC,CAC1D,IAAMA,EAAW,KAAK,gBAAgBC,EAAQ,QAAQ,EAChDC,EAAoBD,EAAQ,mBAAqB,GAEvD,OAAOE,EADSV,EAAAA,kBAAkBW,EAAMJ,EAAS,KAAME,EAAmBD,EAAQ,WAC/C,EAAGC,CAAiB,CACzD,CAGA,yBACE,EACA,EACuE,CACvE,IAAMF,EAAW,KAAK,gBAAgBC,EAAQ,QAAQ,EAChDC,EAAoBD,EAAQ,mBAAqB,GAEvD,OAAOI,EADSZ,EAAAA,kBAAkBW,EAAMJ,EAAS,KAAME,EAAmBD,EAAQ,YAAa,EACjD,EAAGC,CAAiB,CACpE,CAGA,MAAM,8BACJ,EACA,EACgF,CAChF,IAAMF,EAAW,KAAK,gBAAgBC,EAAQ,QAAQ,EAChDC,EAAoBD,EAAQ,mBAAqB,GAEvD,OAAOI,EAAiC,MADlBX,EAAAA,uBAAuBU,EAAMJ,EAAS,KAAME,EAAmBD,EAAQ,YAAa,EAAI,EAC7DC,CAAiB,CACpE,CAGA,6BAA6B,EAAc,EAAwD,CACjG,OAAO,KAAK,oCAAoCE,EAAMH,CAAO,CAAC,CAAC,UACjE,CAMA,oCAAoC,EAAc,EAAuD,CACvG,IAAMD,EAAW,KAAK,gBAAgBC,EAAQ,QAAQ,EACtD,OAAOK,EACLX,EAAAA,2BAA2BS,EAAMJ,EAAS,KAAMC,EAAQ,aAAa,SAAS,CAChF,CACF,CAOA,8BAA8B,EAAc,EAAuC,CACjF,IAAMD,EAAW,KAAK,gBAAgBC,EAAQ,QAAQ,EACtD,OAAOL,EAAAA,oCAAoCQ,EAAMJ,EAAS,IAAI,CAChE,CAEA,gBAAwB,EAAwC,CAC9D,IAAMA,EAAW,KAAK,SAAS,IAAIO,CAAI,EACvC,GAAI,CAACP,EACH,MAAU,MAAM,yBAAyBO,GAAM,EAEjD,OAAOP,CACT,CACF,EAEA,SAASK,EACP,EACA,EACuE,CACvE,GAAI,CAACG,EAAQ,cACX,MAAU,MAAM,0DAA0D,EAE5E,MAAO,CACL,QAASL,EAAsBK,EAASN,CAAiB,EACzD,cAAeI,EAA+BE,EAAQ,aAAa,CACrE,CACF,CAEA,SAASF,EAA+B,EAAmE,CACzG,MAAO,CACL,WAAYE,EAAQ,WACpB,OAAQA,EAAQ,OAChB,oBAAqBA,EAAQ,oBAC7B,eAAgBA,EAAQ,eAExB,gBAAiB,IAAI,IAAIA,EAAQ,eAAe,CAClD,CACF,CAOA,SAASL,EAAsB,EAA+B,EAAyC,CACrG,MAAO,CACL,SAAUK,EAAQ,SAClB,MAAOA,EAAQ,MACf,MAAOA,EAAQ,MACf,gBAAiBA,EAAQ,gBACzB,UAAWA,EAAQ,UAAU,IAAK,IAAQ,CACxC,KAAMC,EAAG,KACT,cAAeA,EAAG,cAClB,SAAUA,EAAG,SACb,UAAWA,EAAG,UACd,YAAaA,EAAG,YAChB,QAASA,EAAG,QACZ,UAAWA,EAAG,UACd,qBAAsBA,EAAG,qBACzB,oBAAqBA,EAAG,oBACxB,gBAAiBA,EAAG,iBAAmB,CAAC,EACxC,aAAcA,EAAG,aACjB,KAAMA,EAAG,KACT,eAAgBA,EAAG,eACnB,SAAUC,EAAsBD,EAAG,cAAc,EACjD,UAAWA,EAAG,SAChB,EAAE,EACF,qBAAsBD,EAAQ,qBAC9B,oBAAqBA,EAAQ,oBAC7B,uBAAwBA,EAAQ,uBAChC,aAAcA,EAAQ,aACtB,UAAWA,EAAQ,UACnB,YAAaA,EAAQ,YACrB,SAAUE,EAAsBF,EAAQ,cAAc,EACtD,WAAYN,EAAoBM,EAAQ,WAAa,IAAA,EACvD,CACF,CAEA,SAASE,EAAsB,EAA+C,CAC5E,GAAM,CAAE,oBAAmB,mBAAkB,iBAAgB,iBAAkBC,EACzEC,EAAaC,EAAoBC,EACjCC,EAASC,EAAiBC,EAC1BC,EAASN,IAAe,EAAI,EAAIG,EAAS,KAAK,KAAKH,CAAU,EAC7DO,EAAaL,IAAqB,EAAI,EAAKD,EAAoB,GAAMI,EAAgBH,GAG3F,MAAO,CACL,oBACA,mBACA,iBACA,gBACA,aACA,SACA,SACA,aACA,OAXaK,EAAaD,CAY5B,CACF,CAEA,MAAaE,EAAkB,IAAIrB,EAEnC,SAAgBsB,EAAY,EAAc,EAAsC,CAC9E,OAAOD,EAAgB,QAAQhB,EAAMH,CAAO,CAC9C,CAGA,SAAgBqB,EACd,EACA,EACgF,CAChF,OAAOF,EAAgB,8BAA8BhB,EAAMH,CAAO,CACpE,CAGA,SAAgBsB,EAA6B,EAAc,EAAwD,CACjH,OAAOH,EAAgB,6BAA6BhB,EAAMH,CAAO,CACnE,CAGA,SAAgBuB,EAA8B,EAAc,EAAuC,CACjG,OAAOJ,EAAgB,8BAA8BhB,EAAMH,CAAO,CACpE,CAGA,SAAgBwB,EACd,EACA,EAC8B,CAC9B,OAAOL,EAAgB,oCAAoChB,EAAMH,CAAO,CAC1E,C"}
|
package/dist/metrics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","names":["TreeMeasurer","createLanguageRegistry","language","options","includeSyntaxTree","assembleNativeMetrics","measureCodeNative","code","assembleMetricsWithCrossFileData","measureCodeNativeAsync","toCrossFileDuplicationFileData","collectCrossFileDataNative","collectFunctionTokenSequencesNative","name","payload","fn","deriveHalsteadMetrics","counts","vocabulary","distinctOperators","distinctOperands","length","totalOperators","totalOperands","volume","difficulty","defaultMeasurer","measureCode","measureCodeWithCrossFileDataAsync","collectDuplicationCandidates","collectFunctionTokenSequences","collectCrossFileDuplicationFileData"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n measureCodeNativeAsync,\n type NativeCrossFileDataPayload,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Measures a file and collects its cross-file clone-detection contribution from one parse. */\n measureWithCrossFileData(\n code: string,\n options: MeasureOptions\n ): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** measureWithCrossFileData on the native addon's worker threads, for measuring files in parallel. */\n async measureWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n ): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = await measureCodeNativeAsync(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n return toCrossFileDuplicationFileData(\n collectCrossFileDataNative(code, language.name, options.duplication?.minTokens)\n );\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\nfunction assembleMetricsWithCrossFileData(\n payload: NativeMetricsPayload,\n includeSyntaxTree: boolean\n): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n if (!payload.crossFileData) {\n throw new Error('The native addon returned no cross-file duplication data');\n }\n return {\n metrics: assembleNativeMetrics(payload, includeSyntaxTree),\n crossFileData: toCrossFileDuplicationFileData(payload.crossFileData),\n };\n}\n\nfunction toCrossFileDuplicationFileData(payload: NativeCrossFileDataPayload): CrossFileDuplicationFileData {\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n nearMissBlocks: payload.nearMissBlocks,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n codeLineNumbers: payload.codeLineNumbers,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n containerName: fn.containerName,\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 cognitiveBlocks: fn.cognitiveBlocks ?? [],\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n difficulty,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring TreeMeasurer.measureWithCrossFileDataAsync for the default measurer. */\nexport function measureCodeWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n return defaultMeasurer.measureWithCrossFileDataAsync(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"metrics.js","names":["TreeMeasurer","createLanguageRegistry","language","options","includeSyntaxTree","assembleNativeMetrics","measureCodeNative","code","assembleMetricsWithCrossFileData","measureCodeNativeAsync","toCrossFileDuplicationFileData","collectCrossFileDataNative","collectFunctionTokenSequencesNative","name","payload","fn","deriveHalsteadMetrics","counts","vocabulary","distinctOperators","distinctOperands","length","totalOperators","totalOperands","volume","difficulty","defaultMeasurer","measureCode","measureCodeWithCrossFileDataAsync","collectDuplicationCandidates","collectFunctionTokenSequences","collectCrossFileDuplicationFileData"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n measureCodeNativeAsync,\n type NativeCrossFileDataPayload,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Measures a file and collects its cross-file clone-detection contribution from one parse. */\n measureWithCrossFileData(\n code: string,\n options: MeasureOptions\n ): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** measureWithCrossFileData on the native addon's worker threads, for measuring files in parallel. */\n async measureWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n ): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = await measureCodeNativeAsync(code, language.name, includeSyntaxTree, options.duplication, true);\n return assembleMetricsWithCrossFileData(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n return toCrossFileDuplicationFileData(\n collectCrossFileDataNative(code, language.name, options.duplication?.minTokens)\n );\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\nfunction assembleMetricsWithCrossFileData(\n payload: NativeMetricsPayload,\n includeSyntaxTree: boolean\n): { metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData } {\n if (!payload.crossFileData) {\n throw new Error('The native addon returned no cross-file duplication data');\n }\n return {\n metrics: assembleNativeMetrics(payload, includeSyntaxTree),\n crossFileData: toCrossFileDuplicationFileData(payload.crossFileData),\n };\n}\n\nfunction toCrossFileDuplicationFileData(payload: NativeCrossFileDataPayload): CrossFileDuplicationFileData {\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n nearMissBlocks: payload.nearMissBlocks,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n codeLineNumbers: payload.codeLineNumbers,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n containerName: fn.containerName,\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 cognitiveBlocks: fn.cognitiveBlocks ?? [],\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cyclomaticComplexity: payload.cyclomaticComplexity,\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n difficulty,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring TreeMeasurer.measureWithCrossFileDataAsync for the default measurer. */\nexport function measureCodeWithCrossFileDataAsync(\n code: string,\n options: MeasureOptions\n): Promise<{ metrics: CodeMetrics; crossFileData: CrossFileDuplicationFileData }> {\n return defaultMeasurer.measureWithCrossFileDataAsync(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"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"}
|
package/dist/nativeMetrics.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use strict";let e=require("@willbooster/shared-lib");function t(e,t,n,i,a=!1){return JSON.parse(f().measureCodeNative(...r(e,t,n,i,a)))}function n(e,t,n,i,a=!1){return f().measureCodeNativeAsync(...r(e,t,n,i,a)).then(e=>JSON.parse(e))}function r(e,t,n,r,i){return[o(e),t,n,s(r?.minTokens),s(r?.maxGapTokens),s(r?.minSimilarityPercent),i]}function i(e,t,n){return JSON.parse(f().collectCrossFileDataNative(o(e),t,s(n)))}function a(e,t){return JSON.parse(f().collectFunctionTokenSequencesNative(o(e),t)).map(e=>Int32Array.from(e))}function o(e){return e.isWellFormed()?e:e.toWellFormed()}function s(t){return t===void 0||Number.isNaN(t)?void 0:(0,e.clamp)(Math.trunc(t),0,4294967295)}var c=class extends Error{};let l,u;function d(e){l=e}function f(){if(l)return l;if(u)throw u;let t=process.getBuiltinModule(`node:module`).createRequire(require("url").pathToFileURL(__filename).href),n=[`code-gauge-${p()}`,`../native/code-gauge.node`],r=[];for(let i of n){let n;try{n=t(i)}catch(t){r.push(` ${i}: ${(0,e.getErrorMessage)(t).split(`
|
|
2
|
-
`)[0]}`);continue}let a=n.payloadVersion?.();if(a!==12)
|
|
2
|
+
`)[0]}`);continue}let a=n.payloadVersion?.();if(a!==12)r.push(` ${i}: payload version ${a??`unknown`} does not match the expected 12; rebuild the addon with \`bun run build-native\``);else return l=n,n}throw u=new c(`The code-gauge native addon is not available for ${p()}. Build it with \`node scripts/buildNative.mjs\` in the code-gauge package directory (requires a Rust toolchain).\n${r.join(`
|
|
3
3
|
`)}`),u}function p(){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=c,exports.collectCrossFileDataNative=i,exports.collectFunctionTokenSequencesNative=a,exports.measureCodeNative=t,exports.measureCodeNativeAsync=n,exports.setNativeBinding=d;
|
|
4
4
|
//# sourceMappingURL=nativeMetrics.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeMetrics.cjs","names":["clamp","getErrorMessage","_willbooster_shared_lib","measureCodeNative","loadBinding","toNativeMeasureArguments","code","language","includeSyntaxTree","duplication","includeCrossFileData","measureCodeNativeAsync","json","toWellFormed","clampToU32","collectCrossFileDataNative","minTokens","collectFunctionTokenSequencesNative","sequence","value","NativeAddonError","cachedBinding","cachedFailure","setNativeBinding","binding","requireNative","specifiers","platformTriplet","failures","specifier","error","version","base"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { clamp, getErrorMessage } from '@willbooster/shared-lib';\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' | 'cognitiveBlocks'> {\n /** Absent when the function has none. */\n cognitiveBlocks?: FunctionMetrics['cognitiveBlocks'];\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\ntype NativeMeasureArguments = [\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens: number | undefined,\n maxGapTokens: number | undefined,\n minSimilarityPercent: number | undefined,\n includeCrossFileData: boolean,\n];\n\nexport interface NativeBinding {\n measureCodeNative(...args: NativeMeasureArguments): string;\n measureCodeNativeAsync(...args: NativeMeasureArguments): Promise<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, or lack newer binding functions, instead of failing with a clear rebuild message.\n */\nexport const expectedPayloadVersion = 12;\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 ...toNativeMeasureArguments(code, language, includeSyntaxTree, duplication, includeCrossFileData)\n )\n ) as NativeMetricsPayload;\n}\n\n/**\n * measureCodeNative on the addon's worker threads, so several files can be measured in parallel.\n * A missing addon still throws synchronously, like measureCodeNative.\n */\nexport function measureCodeNativeAsync(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication?: DuplicationOptions,\n includeCrossFileData = false\n): Promise<NativeMetricsPayload> {\n return loadBinding()\n .measureCodeNativeAsync(\n ...toNativeMeasureArguments(code, language, includeSyntaxTree, duplication, includeCrossFileData)\n )\n .then((json) => JSON.parse(json) as NativeMetricsPayload);\n}\n\n/** The binding arguments shared by the sync and async measurements, so both measure alike. */\nfunction toNativeMeasureArguments(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication: DuplicationOptions | undefined,\n includeCrossFileData: boolean\n): NativeMeasureArguments {\n return [\n toWellFormed(code),\n language,\n includeSyntaxTree,\n clampToU32(duplication?.minTokens),\n clampToU32(duplication?.maxGapTokens),\n clampToU32(duplication?.minSimilarityPercent),\n includeCrossFileData,\n ];\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) ? undefined : clamp(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}: ${getErrorMessage(error).split('\\n')[0]}`);\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).\\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":"sDAqEA,SAAgBG,EACd,EACA,EACA,EACA,EACA,EAAuB,GACD,CACtB,OAAO,KAAK,MACVC,EAAY,CAAC,CAAC,kBACZ,GAAGC,EAAyBC,EAAMC,EAAUC,EAAmBC,EAAaC,CAAoB,CAClG,CACF,CACF,CAMA,SAAgBC,EACd,EACA,EACA,EACA,EACA,EAAuB,GACQ,CAC/B,OAAOP,EAAY,CAAC,CACjB,uBACC,GAAGC,EAAyBC,EAAMC,EAAUC,EAAmBC,EAAaC,CAAoB,CAClG,CAAC,CACA,KAAM,GAAS,KAAK,MAAME,CAAI,CAAyB,CAC5D,CAGA,SAASP,EACP,EACA,EACA,EACA,EACA,EACwB,CACxB,MAAO,CACLQ,EAAaP,CAAI,EACjBC,EACAC,EACAM,EAAWL,GAAa,SAAS,EACjCK,EAAWL,GAAa,YAAY,EACpCK,EAAWL,GAAa,oBAAoB,EAC5CC,CACF,CACF,CAGA,SAAgBK,EACd,EACA,EACA,EAC4B,CAC5B,OAAO,KAAK,MACVX,EAAY,CAAC,CAAC,2BAA2BS,EAAaP,CAAI,EAAGC,EAAUO,EAAWE,CAAS,CAAC,CAC9F,CACF,CAGA,SAAgBC,EAAoC,EAAc,EAAgC,CAIhG,OAHkB,KAAK,MACrBb,EAAY,CAAC,CAAC,oCAAoCS,EAAaP,CAAI,EAAGC,CAAQ,CAEjE,CAAC,CAAC,IAAK,GAAa,WAAW,KAAKW,CAAQ,CAAC,CAC9D,CAOA,SAASL,EAAa,EAAsB,CAC1C,OAAOP,EAAK,aAAa,EAAIA,EAAOA,EAAK,aAAa,CACxD,CAUA,SAASQ,EAAW,EAA+C,CACjE,OAAOK,IAAU,IAAA,IAAa,OAAO,MAAMA,CAAK,EAAI,IAAA,IAAA,EAAYnB,EAAAA,MAAAA,CAAM,KAAK,MAAMmB,CAAK,EAAG,EAAG,UAAa,CAC3G,CAOA,IAAaC,EAAb,cAAsC,KAAM,CAAC,EAE7C,IAAIC,EACAC,EAGJ,SAAgBC,EAAiB,EAA8B,CAC7D,EAAgBC,CAClB,CAEA,SAASpB,GAA6B,CACpC,GAAIiB,EACF,OAAOA,EAIT,GAAIC,EACF,MAAMA,EAIR,IAAMG,EAAgB,QAAQ,iBAAiB,aAAa,CAAC,CAAC,cAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,IAA6B,EACrFC,EAAa,CAEjB,cAAcC,EAAgB,IAE9B,2BACF,EACMC,EAAqB,CAAC,EAC5B,IAAK,IAAMC,KAAaH,EAAY,CAClC,IAAIF,EACJ,GAAI,CACF,EAAUC,EAAcI,CAAS,CACnC,OAASC,EAAO,CACdF,EAAS,KAAK,KAAKC,EAAU,KAAA,EAAI5B,EAAAA,gBAAAA,CAAgB6B,CAAK,CAAC,CAAC,MAAM;CAAI,CAAC,CAAC,IAAI,EACxE,QACF,CACA,IAAMC,EAAUP,EAAQ,iBAAiB,EACzC,GAAIO,IAAA,GAAoC,CACtCH,EAAS,KACP,KAAKC,EAAU,oBAAoBE,GAAW,UAAU,iFAE1D,EACA,QACF,CAEA,MADA,GAAgBP,EACTA,CACT,CAMA,KALA,GAAgB,IAAIJ,EAClB,oDAAoDO,EAAgB,EAAE,qHAEpDC,EAAS,KAAK;CAAI,GACtC,EACMN,CACR,CAQA,SAASK,GAA0B,CACjC,IAAMK,EAAO,GAAG,QAAQ,SAAS,GAAG,QAAQ,OAS5C,OARI,QAAQ,WAAa,QAChB,GAAGA,EAAK,OAEb,QAAQ,WAAa,SAIV,QAAQ,QAAQ,UAAU,EAAA,EAC1B,QAAQ,oBAAsB,GAAGA,EAAK,MAAQ,GAAGA,EAAK,OAJ5DA,CAKX"}
|
|
1
|
+
{"version":3,"file":"nativeMetrics.cjs","names":["clamp","getErrorMessage","_willbooster_shared_lib","measureCodeNative","loadBinding","toNativeMeasureArguments","code","language","includeSyntaxTree","duplication","includeCrossFileData","measureCodeNativeAsync","json","toWellFormed","clampToU32","collectCrossFileDataNative","minTokens","collectFunctionTokenSequencesNative","sequence","value","NativeAddonError","cachedBinding","cachedFailure","setNativeBinding","binding","requireNative","specifiers","platformTriplet","failures","specifier","error","version","base"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { clamp, getErrorMessage } from '@willbooster/shared-lib';\n\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' | 'cognitiveBlocks'> {\n /** Absent when the function has none. */\n cognitiveBlocks?: FunctionMetrics['cognitiveBlocks'];\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\ntype NativeMeasureArguments = [\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens: number | undefined,\n maxGapTokens: number | undefined,\n minSimilarityPercent: number | undefined,\n includeCrossFileData: boolean,\n];\n\nexport interface NativeBinding {\n measureCodeNative(...args: NativeMeasureArguments): string;\n measureCodeNativeAsync(...args: NativeMeasureArguments): Promise<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, or lack newer binding functions, instead of failing with a clear rebuild message.\n */\nexport const expectedPayloadVersion = 12;\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 ...toNativeMeasureArguments(code, language, includeSyntaxTree, duplication, includeCrossFileData)\n )\n ) as NativeMetricsPayload;\n}\n\n/**\n * measureCodeNative on the addon's worker threads, so several files can be measured in parallel.\n * A missing addon still throws synchronously, like measureCodeNative.\n */\nexport function measureCodeNativeAsync(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication?: DuplicationOptions,\n includeCrossFileData = false\n): Promise<NativeMetricsPayload> {\n return loadBinding()\n .measureCodeNativeAsync(\n ...toNativeMeasureArguments(code, language, includeSyntaxTree, duplication, includeCrossFileData)\n )\n .then((json) => JSON.parse(json) as NativeMetricsPayload);\n}\n\n/** The binding arguments shared by the sync and async measurements, so both measure alike. */\nfunction toNativeMeasureArguments(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication: DuplicationOptions | undefined,\n includeCrossFileData: boolean\n): NativeMeasureArguments {\n return [\n toWellFormed(code),\n language,\n includeSyntaxTree,\n clampToU32(duplication?.minTokens),\n clampToU32(duplication?.maxGapTokens),\n clampToU32(duplication?.minSimilarityPercent),\n includeCrossFileData,\n ];\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) ? undefined : clamp(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}: ${getErrorMessage(error).split('\\n')[0]}`);\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).\\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":"A,sDAsEA,SAAgBG,EACd,EACA,EACA,EACA,EACA,EAAuB,GACD,CACtB,OAAO,KAAK,MACVC,EAAY,CAAC,CAAC,kBACZ,GAAGC,EAAyBC,EAAMC,EAAUC,EAAmBC,EAAaC,CAAoB,CAClG,CACF,CACF,CAMA,SAAgBC,EACd,EACA,EACA,EACA,EACA,EAAuB,GACQ,CAC/B,OAAOP,EAAY,CAAC,CACjB,uBACC,GAAGC,EAAyBC,EAAMC,EAAUC,EAAmBC,EAAaC,CAAoB,CAClG,CAAC,CACA,KAAM,GAAS,KAAK,MAAME,CAAI,CAAyB,CAC5D,CAGA,SAASP,EACP,EACA,EACA,EACA,EACA,EACwB,CACxB,MAAO,CACLQ,EAAaP,CAAI,EACjBC,EACAC,EACAM,EAAWL,GAAa,SAAS,EACjCK,EAAWL,GAAa,YAAY,EACpCK,EAAWL,GAAa,oBAAoB,EAC5CC,CACF,CACF,CAGA,SAAgBK,EACd,EACA,EACA,EAC4B,CAC5B,OAAO,KAAK,MACVX,EAAY,CAAC,CAAC,2BAA2BS,EAAaP,CAAI,EAAGC,EAAUO,EAAWE,CAAS,CAAC,CAC9F,CACF,CAGA,SAAgBC,EAAoC,EAAc,EAAgC,CAIhG,OAHkB,KAAK,MACrBb,EAAY,CAAC,CAAC,oCAAoCS,EAAaP,CAAI,EAAGC,CAAQ,CAEjE,CAAC,CAAC,IAAK,GAAa,WAAW,KAAKW,CAAQ,CAAC,CAC9D,CAOA,SAASL,EAAa,EAAsB,CAC1C,OAAOP,EAAK,aAAa,EAAIA,EAAOA,EAAK,aAAa,CACxD,CAUA,SAASQ,EAAW,EAA+C,CACjE,OAAOK,IAAU,IAAA,IAAa,OAAO,MAAMA,CAAK,EAAI,IAAA,IAAA,EAAYnB,EAAAA,MAAAA,CAAM,KAAK,MAAMmB,CAAK,EAAG,EAAG,UAAa,CAC3G,CAOA,IAAaC,EAAb,cAAsC,KAAM,CAAC,EAE7C,IAAIC,EACAC,EAGJ,SAAgBC,EAAiB,EAA8B,CAC7D,EAAgBC,CAClB,CAEA,SAASpB,GAA6B,CACpC,GAAIiB,EACF,OAAOA,EAIT,GAAIC,EACF,MAAMA,EAIR,IAAMG,EAAgB,QAAQ,iBAAiB,aAAa,CAAC,CAAC,cAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,IAA6B,EACrFC,EAAa,CAEjB,cAAcC,EAAgB,IAE9B,2BACF,EACMC,EAAqB,CAAC,EAC5B,IAAK,IAAMC,KAAaH,EAAY,CAClC,IAAIF,EACJ,GAAI,CACF,EAAUC,EAAcI,CAAS,CACnC,OAASC,EAAO,CACdF,EAAS,KAAK,KAAKC,EAAU,KAAA,EAAI5B,EAAAA,gBAAAA,CAAgB6B,CAAK,CAAC,CAAC,MAAM;CAAI,CAAC,CAAC,IAAI,EACxE,QACF,CACA,IAAMC,EAAUP,EAAQ,iBAAiB,EACzC,GAAIO,IAAA,GACFH,EAAS,KACP,KAAKC,EAAU,oBAAoBE,GAAW,UAAU,iFAE1D,OAIF,MADA,GAAgBP,EACTA,CACT,CAMA,KALA,GAAgB,IAAIJ,EAClB,oDAAoDO,EAAgB,EAAE,qHAEpDC,EAAS,KAAK;CAAI,GACtC,EACMN,CACR,CAQA,SAASK,GAA0B,CACjC,IAAMK,EAAO,GAAG,QAAQ,SAAS,GAAG,QAAQ,OAS5C,OARI,QAAQ,WAAa,QAChB,GAAGA,EAAK,OAEb,QAAQ,WAAa,SAIV,QAAQ,QAAQ,UAAU,EAAA,EAC1B,QAAQ,oBAAsB,GAAGA,EAAK,MAAQ,GAAGA,EAAK,OAJ5DA,CAKX,C"}
|
package/dist/nativeMetrics.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{clamp as e,getErrorMessage as t}from"@willbooster/shared-lib";function n(e,t,n,r,a=!1){return JSON.parse(p().measureCodeNative(...i(e,t,n,r,a)))}function r(e,t,n,r,a=!1){return p().measureCodeNativeAsync(...i(e,t,n,r,a)).then(e=>JSON.parse(e))}function i(e,t,n,r,i){return[s(e),t,n,c(r?.minTokens),c(r?.maxGapTokens),c(r?.minSimilarityPercent),i]}function a(e,t,n){return JSON.parse(p().collectCrossFileDataNative(s(e),t,c(n)))}function o(e,t){return JSON.parse(p().collectFunctionTokenSequencesNative(s(e),t)).map(e=>Int32Array.from(e))}function s(e){return e.isWellFormed()?e:e.toWellFormed()}function c(t){return t===void 0||Number.isNaN(t)?void 0:e(Math.trunc(t),0,4294967295)}var l=class extends Error{};let u,d;function f(e){u=e}function p(){if(u)return u;if(d)throw d;let e=process.getBuiltinModule(`node:module`).createRequire(import.meta.url),n=[`code-gauge-${m()}`,`../native/code-gauge.node`],r=[];for(let i of n){let n;try{n=e(i)}catch(e){r.push(` ${i}: ${t(e).split(`
|
|
2
|
-
`)[0]}`);continue}let a=n.payloadVersion?.();if(a!==12)
|
|
2
|
+
`)[0]}`);continue}let a=n.payloadVersion?.();if(a!==12)r.push(` ${i}: payload version ${a??`unknown`} does not match the expected 12; rebuild the addon with \`bun run build-native\``);else return u=n,n}throw d=new l(`The code-gauge native addon is not available for ${m()}. Build it with \`node scripts/buildNative.mjs\` in the code-gauge package directory (requires a Rust toolchain).\n${r.join(`
|
|
3
3
|
`)}`),d}function m(){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{l as NativeAddonError,a as collectCrossFileDataNative,o as collectFunctionTokenSequencesNative,n as measureCodeNative,r as measureCodeNativeAsync,f as setNativeBinding};
|
|
4
4
|
//# sourceMappingURL=nativeMetrics.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeMetrics.js","names":["measureCodeNative","loadBinding","toNativeMeasureArguments","code","language","includeSyntaxTree","duplication","includeCrossFileData","measureCodeNativeAsync","json","toWellFormed","clampToU32","collectCrossFileDataNative","minTokens","collectFunctionTokenSequencesNative","sequence","value","clamp","NativeAddonError","cachedBinding","cachedFailure","setNativeBinding","binding","requireNative","specifiers","platformTriplet","failures","specifier","error","getErrorMessage","version","base"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { clamp, getErrorMessage } from '@willbooster/shared-lib';\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' | 'cognitiveBlocks'> {\n /** Absent when the function has none. */\n cognitiveBlocks?: FunctionMetrics['cognitiveBlocks'];\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\ntype NativeMeasureArguments = [\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens: number | undefined,\n maxGapTokens: number | undefined,\n minSimilarityPercent: number | undefined,\n includeCrossFileData: boolean,\n];\n\nexport interface NativeBinding {\n measureCodeNative(...args: NativeMeasureArguments): string;\n measureCodeNativeAsync(...args: NativeMeasureArguments): Promise<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, or lack newer binding functions, instead of failing with a clear rebuild message.\n */\nexport const expectedPayloadVersion = 12;\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 ...toNativeMeasureArguments(code, language, includeSyntaxTree, duplication, includeCrossFileData)\n )\n ) as NativeMetricsPayload;\n}\n\n/**\n * measureCodeNative on the addon's worker threads, so several files can be measured in parallel.\n * A missing addon still throws synchronously, like measureCodeNative.\n */\nexport function measureCodeNativeAsync(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication?: DuplicationOptions,\n includeCrossFileData = false\n): Promise<NativeMetricsPayload> {\n return loadBinding()\n .measureCodeNativeAsync(\n ...toNativeMeasureArguments(code, language, includeSyntaxTree, duplication, includeCrossFileData)\n )\n .then((json) => JSON.parse(json) as NativeMetricsPayload);\n}\n\n/** The binding arguments shared by the sync and async measurements, so both measure alike. */\nfunction toNativeMeasureArguments(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication: DuplicationOptions | undefined,\n includeCrossFileData: boolean\n): NativeMeasureArguments {\n return [\n toWellFormed(code),\n language,\n includeSyntaxTree,\n clampToU32(duplication?.minTokens),\n clampToU32(duplication?.maxGapTokens),\n clampToU32(duplication?.minSimilarityPercent),\n includeCrossFileData,\n ];\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) ? undefined : clamp(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}: ${getErrorMessage(error).split('\\n')[0]}`);\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).\\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":"qEAqEA,SAAgBA,EACd,EACA,EACA,EACA,EACA,EAAuB,GACD,CACtB,OAAO,KAAK,MACVC,EAAY,CAAC,CAAC,kBACZ,GAAGC,EAAyBC,EAAMC,EAAUC,EAAmBC,EAAaC,CAAoB,CAClG,CACF,CACF,CAMA,SAAgBC,EACd,EACA,EACA,EACA,EACA,EAAuB,GACQ,CAC/B,OAAOP,EAAY,CAAC,CACjB,uBACC,GAAGC,EAAyBC,EAAMC,EAAUC,EAAmBC,EAAaC,CAAoB,CAClG,CAAC,CACA,KAAM,GAAS,KAAK,MAAME,CAAI,CAAyB,CAC5D,CAGA,SAASP,EACP,EACA,EACA,EACA,EACA,EACwB,CACxB,MAAO,CACLQ,EAAaP,CAAI,EACjBC,EACAC,EACAM,EAAWL,GAAa,SAAS,EACjCK,EAAWL,GAAa,YAAY,EACpCK,EAAWL,GAAa,oBAAoB,EAC5CC,CACF,CACF,CAGA,SAAgBK,EACd,EACA,EACA,EAC4B,CAC5B,OAAO,KAAK,MACVX,EAAY,CAAC,CAAC,2BAA2BS,EAAaP,CAAI,EAAGC,EAAUO,EAAWE,CAAS,CAAC,CAC9F,CACF,CAGA,SAAgBC,EAAoC,EAAc,EAAgC,CAIhG,OAHkB,KAAK,MACrBb,EAAY,CAAC,CAAC,oCAAoCS,EAAaP,CAAI,EAAGC,CAAQ,CAEjE,CAAC,CAAC,IAAK,GAAa,WAAW,KAAKW,CAAQ,CAAC,CAC9D,CAOA,SAASL,EAAa,EAAsB,CAC1C,OAAOP,EAAK,aAAa,EAAIA,EAAOA,EAAK,aAAa,CACxD,CAUA,SAASQ,EAAW,EAA+C,CACjE,OAAOK,IAAU,IAAA,IAAa,OAAO,MAAMA,CAAK,EAAI,IAAA,GAAYC,EAAM,KAAK,MAAMD,CAAK,EAAG,EAAG,UAAa,CAC3G,CAOA,IAAaE,EAAb,cAAsC,KAAM,CAAC,EAE7C,IAAIC,EACAC,EAGJ,SAAgBC,EAAiB,EAA8B,CAC7D,EAAgBC,CAClB,CAEA,SAASrB,GAA6B,CACpC,GAAIkB,EACF,OAAOA,EAIT,GAAIC,EACF,MAAMA,EAIR,IAAMG,EAAgB,QAAQ,iBAAiB,aAAa,CAAC,CAAC,cAAc,YAAY,GAAG,EACrFC,EAAa,CAEjB,cAAcC,EAAgB,IAE9B,2BACF,EACMC,EAAqB,CAAC,EAC5B,IAAK,IAAMC,KAAaH,EAAY,CAClC,IAAIF,EACJ,GAAI,CACF,EAAUC,EAAcI,CAAS,CACnC,OAASC,EAAO,CACdF,EAAS,KAAK,KAAKC,EAAU,IAAIE,EAAgBD,CAAK,CAAC,CAAC,MAAM;CAAI,CAAC,CAAC,IAAI,EACxE,QACF,CACA,IAAME,EAAUR,EAAQ,iBAAiB,EACzC,GAAIQ,IAAA,GAAoC,CACtCJ,EAAS,KACP,KAAKC,EAAU,oBAAoBG,GAAW,UAAU,iFAE1D,EACA,QACF,CAEA,MADA,GAAgBR,EACTA,CACT,CAMA,KALA,GAAgB,IAAIJ,EAClB,oDAAoDO,EAAgB,EAAE,qHAEpDC,EAAS,KAAK;CAAI,GACtC,EACMN,CACR,CAQA,SAASK,GAA0B,CACjC,IAAMM,EAAO,GAAG,QAAQ,SAAS,GAAG,QAAQ,OAS5C,OARI,QAAQ,WAAa,QAChB,GAAGA,EAAK,OAEb,QAAQ,WAAa,SAIV,QAAQ,QAAQ,UAAU,EAAA,EAC1B,QAAQ,oBAAsB,GAAGA,EAAK,MAAQ,GAAGA,EAAK,OAJ5DA,CAKX"}
|
|
1
|
+
{"version":3,"file":"nativeMetrics.js","names":["measureCodeNative","loadBinding","toNativeMeasureArguments","code","language","includeSyntaxTree","duplication","includeCrossFileData","measureCodeNativeAsync","json","toWellFormed","clampToU32","collectCrossFileDataNative","minTokens","collectFunctionTokenSequencesNative","sequence","value","clamp","NativeAddonError","cachedBinding","cachedFailure","setNativeBinding","binding","requireNative","specifiers","platformTriplet","failures","specifier","error","getErrorMessage","version","base"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { clamp, getErrorMessage } from '@willbooster/shared-lib';\n\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' | 'cognitiveBlocks'> {\n /** Absent when the function has none. */\n cognitiveBlocks?: FunctionMetrics['cognitiveBlocks'];\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\ntype NativeMeasureArguments = [\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n minTokens: number | undefined,\n maxGapTokens: number | undefined,\n minSimilarityPercent: number | undefined,\n includeCrossFileData: boolean,\n];\n\nexport interface NativeBinding {\n measureCodeNative(...args: NativeMeasureArguments): string;\n measureCodeNativeAsync(...args: NativeMeasureArguments): Promise<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, or lack newer binding functions, instead of failing with a clear rebuild message.\n */\nexport const expectedPayloadVersion = 12;\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 ...toNativeMeasureArguments(code, language, includeSyntaxTree, duplication, includeCrossFileData)\n )\n ) as NativeMetricsPayload;\n}\n\n/**\n * measureCodeNative on the addon's worker threads, so several files can be measured in parallel.\n * A missing addon still throws synchronously, like measureCodeNative.\n */\nexport function measureCodeNativeAsync(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication?: DuplicationOptions,\n includeCrossFileData = false\n): Promise<NativeMetricsPayload> {\n return loadBinding()\n .measureCodeNativeAsync(\n ...toNativeMeasureArguments(code, language, includeSyntaxTree, duplication, includeCrossFileData)\n )\n .then((json) => JSON.parse(json) as NativeMetricsPayload);\n}\n\n/** The binding arguments shared by the sync and async measurements, so both measure alike. */\nfunction toNativeMeasureArguments(\n code: string,\n language: string,\n includeSyntaxTree: boolean,\n duplication: DuplicationOptions | undefined,\n includeCrossFileData: boolean\n): NativeMeasureArguments {\n return [\n toWellFormed(code),\n language,\n includeSyntaxTree,\n clampToU32(duplication?.minTokens),\n clampToU32(duplication?.maxGapTokens),\n clampToU32(duplication?.minSimilarityPercent),\n includeCrossFileData,\n ];\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) ? undefined : clamp(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}: ${getErrorMessage(error).split('\\n')[0]}`);\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).\\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":"A,qEAsEA,SAAgBA,EACd,EACA,EACA,EACA,EACA,EAAuB,GACD,CACtB,OAAO,KAAK,MACVC,EAAY,CAAC,CAAC,kBACZ,GAAGC,EAAyBC,EAAMC,EAAUC,EAAmBC,EAAaC,CAAoB,CAClG,CACF,CACF,CAMA,SAAgBC,EACd,EACA,EACA,EACA,EACA,EAAuB,GACQ,CAC/B,OAAOP,EAAY,CAAC,CACjB,uBACC,GAAGC,EAAyBC,EAAMC,EAAUC,EAAmBC,EAAaC,CAAoB,CAClG,CAAC,CACA,KAAM,GAAS,KAAK,MAAME,CAAI,CAAyB,CAC5D,CAGA,SAASP,EACP,EACA,EACA,EACA,EACA,EACwB,CACxB,MAAO,CACLQ,EAAaP,CAAI,EACjBC,EACAC,EACAM,EAAWL,GAAa,SAAS,EACjCK,EAAWL,GAAa,YAAY,EACpCK,EAAWL,GAAa,oBAAoB,EAC5CC,CACF,CACF,CAGA,SAAgBK,EACd,EACA,EACA,EAC4B,CAC5B,OAAO,KAAK,MACVX,EAAY,CAAC,CAAC,2BAA2BS,EAAaP,CAAI,EAAGC,EAAUO,EAAWE,CAAS,CAAC,CAC9F,CACF,CAGA,SAAgBC,EAAoC,EAAc,EAAgC,CAIhG,OAHkB,KAAK,MACrBb,EAAY,CAAC,CAAC,oCAAoCS,EAAaP,CAAI,EAAGC,CAAQ,CAEjE,CAAC,CAAC,IAAK,GAAa,WAAW,KAAKW,CAAQ,CAAC,CAC9D,CAOA,SAASL,EAAa,EAAsB,CAC1C,OAAOP,EAAK,aAAa,EAAIA,EAAOA,EAAK,aAAa,CACxD,CAUA,SAASQ,EAAW,EAA+C,CACjE,OAAOK,IAAU,IAAA,IAAa,OAAO,MAAMA,CAAK,EAAI,IAAA,GAAYC,EAAM,KAAK,MAAMD,CAAK,EAAG,EAAG,UAAa,CAC3G,CAOA,IAAaE,EAAb,cAAsC,KAAM,CAAC,EAE7C,IAAIC,EACAC,EAGJ,SAAgBC,EAAiB,EAA8B,CAC7D,EAAgBC,CAClB,CAEA,SAASrB,GAA6B,CACpC,GAAIkB,EACF,OAAOA,EAIT,GAAIC,EACF,MAAMA,EAIR,IAAMG,EAAgB,QAAQ,iBAAiB,aAAa,CAAC,CAAC,cAAc,YAAY,GAAG,EACrFC,EAAa,CAEjB,cAAcC,EAAgB,IAE9B,2BACF,EACMC,EAAqB,CAAC,EAC5B,IAAK,IAAMC,KAAaH,EAAY,CAClC,IAAIF,EACJ,GAAI,CACF,EAAUC,EAAcI,CAAS,CACnC,OAASC,EAAO,CACdF,EAAS,KAAK,KAAKC,EAAU,IAAIE,EAAgBD,CAAK,CAAC,CAAC,MAAM;CAAI,CAAC,CAAC,IAAI,EACxE,QACF,CACA,IAAME,EAAUR,EAAQ,iBAAiB,EACzC,GAAIQ,IAAA,GACFJ,EAAS,KACP,KAAKC,EAAU,oBAAoBG,GAAW,UAAU,iFAE1D,OAIF,MADA,GAAgBR,EACTA,CACT,CAMA,KALA,GAAgB,IAAIJ,EAClB,oDAAoDO,EAAgB,EAAE,qHAEpDC,EAAS,KAAK;CAAI,GACtC,EACMN,CACR,CAQA,SAASK,GAA0B,CACjC,IAAMM,EAAO,GAAG,QAAQ,SAAS,GAAG,QAAQ,OAS5C,OARI,QAAQ,WAAa,QAChB,GAAGA,EAAK,OAEb,QAAQ,WAAa,SAIV,QAAQ,QAAQ,UAAU,EAAA,EAC1B,QAAQ,oBAAsB,GAAGA,EAAK,MAAQ,GAAGA,EAAK,OAJ5DA,CAKX,C"}
|
package/dist/scan.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./languages.cjs"),r=require("./nativeMetrics.cjs"),i=require("./metrics.cjs");let a=require("@willbooster/shared-lib"),o=require("node:fs/promises"),s=require("node:
|
|
1
|
+
"use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./languages.cjs"),r=require("./nativeMetrics.cjs"),i=require("./metrics.cjs");let a=require("@willbooster/shared-lib"),o=require("node:fs/promises"),s=require("node:os");s=e.__toESM(s,1);let c=require("node:path");c=e.__toESM(c,1);const l=/* @__PURE__ */ new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`obj`,`target`,`test-fixtures`,`vendor`,`venv`]),u=/* @__PURE__ */ new Set([`__tests__`,`test`,`tests`,`spec`]),d=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,f=/Tests?\.(?:java|kt|cs)$/u;function p(e){return e===`~`?s.default.homedir():e.startsWith(`~/`)?c.default.join(s.default.homedir(),e.slice(2)):c.default.resolve(e)}async function m(e){try{return(await(0,o.stat)(e)).isDirectory()?e:c.default.dirname(e)}catch{return c.default.dirname(e)}}const h=s.default.availableParallelism()*2;async function g(e,t){let n=e;try{n=await(0,o.realpath)(e)}catch{}let r=c.default.dirname(n),i;try{i=await(0,o.stat)(n)}catch(e){let t=`${L(n,r)}: ${(0,a.getErrorMessage)(e)}`;return{displayRoot:r,files:[],errors:[t],warnings:[],generatedFiles:[],fatalError:t}}if(i.isFile()){let e=c.default.dirname(n),r=I(n,t,!0);if(!r){let t=`${L(n,e)}: unsupported file type`;return{displayRoot:e,files:[],errors:[t],warnings:[],generatedFiles:[],fatalError:t}}let i=x(t,e);return await O(n,r,`single-file`,i,n),b(i,e)}let s=x(t,n);return await T(n,s),await v(s),b(s,n)}async function _(e,t,n,r=/* @__PURE__ */ new Set,i=``){let a=x(n,e),s=i===``?``:`${i}/`;for(let i of t){let t=c.default.join(e,i),l=F(i.startsWith(s)?i.slice(s.length):i,n,r.has(t))?I(i,n,r.has(t)):void 0;if(!l)continue;let u=await(0,o.lstat)(t).catch(()=>{});(!u||u.isFile())&&a.candidates.push({file:t,language:l})}return await v(a),b(a,e)}async function v(e){let t=await e.options.loadExclusion?.(e.candidates.flatMap(({file:e,realFile:t})=>t===void 0?[e]:[e,t]));e.exclusion=t;let n=/* @__PURE__ */ new Map;for(let t of e.candidates){let r;try{r=t.realFile??await(0,o.realpath)(t.file)}catch(n){S(e,t.file,n);continue}n.set(r,[...n.get(r)??[],t])}for(let[r,i]of n){if(e.fatalSeen)return;let n=await y(r,i,t);n!==void 0&&await O(n.file,n.language,`directory`,e,r)}}async function y(e,t,n){if(n===void 0)return t[0];if(n.isExcludedPath(e))return;let r=t.filter(({file:e})=>!n.isExcludedPath(e)),i=r.find(({file:t})=>t===e);if(i!==void 0||r.length<=1)return i??r[0];let a=await(0,o.readFile)(e,`utf8`).catch(()=>{});return r.find(({file:e})=>a!==void 0&&!n.isGeneratedCode(e,a))??r[0]}async function b(e,t){let n=[],r=[],i=[],o=[];for(let s of e.outcomes){let e=await s;if(`fatal`in e){let s=(0,a.getErrorMessage)(e.fatal);return{displayRoot:t,files:n,errors:[...r,s],warnings:i,generatedFiles:o,fatalError:s}}`generatedFile`in e?o.push(e.generatedFile):`error`in e?r.push(e.error):(n.push(e.file),e.warning!==void 0&&i.push(e.warning))}return{displayRoot:t,files:n,errors:r,warnings:i,generatedFiles:o}}function x(e,t){return{options:e,outcomes:[],inFlight:/* @__PURE__ */ new Set,fatalSeen:!1,visitedDirectories:/* @__PURE__ */ new Set,visitedFiles:/* @__PURE__ */ new Set,rootDirectory:t,candidates:[]}}function S(e,t,n){e.outcomes.push({error:`${L(t,e.rootDirectory)}: ${(0,a.getErrorMessage)(n)}`})}async function C(e,t,n){try{return await e()}catch(e){S(n,t,e);return}}async function w(e,t){let n=await C(()=>(0,o.realpath)(e),e,t);return n!==void 0&&P(n,t.rootDirectory)?n:void 0}async function T(e,t){let n=await w(e,t);if(n===void 0||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r=await C(()=>(0,o.readdir)(e,{withFileTypes:!0}),e,t);if(r!==void 0){r.sort((e,t)=>e.name<t.name?-1:+(e.name>t.name));for(let i of r){let r=c.default.join(e,i.name);if(i.isSymbolicLink())await E(i.name,r,t);else if(i.isDirectory()){if(N(i.name,t.options))continue;await T(r,t)}else i.isFile()&&D(r,t,r,n===e?void 0:c.default.join(n,i.name))}}}async function E(e,t,n){let r=await w(t,n);if(r===void 0)return;let i=await C(()=>(0,o.stat)(t),t,n);if(i!==void 0){if(i.isDirectory()){if(N(e,n.options)||N(c.default.basename(r),n.options))return;await T(t,n)}else i.isFile()&&D(t,n,r,r)}}function D(e,t,n=e,r){let i=I(n,t.options);i&&t.candidates.push({file:e,language:i,realFile:r})}async function O(e,t,n,r,i){if(r.fatalSeen)return;let a;try{a=i??await(0,o.realpath)(e)}catch(t){S(r,e,t);return}if(r.visitedFiles.has(a))return;for(r.visitedFiles.add(a);r.inFlight.size>=h;)await Promise.race(r.inFlight);let s=k(e,a,t,n,r);r.inFlight.add(s),s.then(()=>r.inFlight.delete(s)),r.outcomes.push(s)}async function k(e,t,n,s,c){try{let r=await(0,o.readFile)(e,`utf8`),a={language:n,duplication:c.options.duplication};if(s===`single-file`)return{file:{file:e,metrics:i.measureCode(r,a)}};let l=c.exclusion;if(l&&(l.isGeneratedCode(e,r)||l.isGeneratedCode(t,r)))return{generatedFile:e};let{metrics:u,crossFileData:d,crossFileError:f}=await A(r,a);return{file:{file:e,metrics:u,duplicationCandidates:d},warning:f===void 0?void 0:`${L(e,c.rootDirectory)}: cross-file duplication candidates unavailable: ${f}`}}catch(t){return t instanceof r.NativeAddonError?(c.fatalSeen=!0,{fatal:t}):{error:`${L(e,c.rootDirectory)}: ${(0,a.getErrorMessage)(t)}`}}}async function A(e,t){try{return await i.measureCodeWithCrossFileDataAsync(e,t)}catch(n){if(n instanceof r.NativeAddonError)throw n;return{metrics:i.measureCode(e,t),crossFileError:(0,a.getErrorMessage)(n)}}}function j(e,n){if(e.fatalError||e.files.length<2)return;let r=e.files.flatMap(({file:t,duplicationCandidates:n})=>n?[{file:L(t,e.displayRoot),...n}]:[]);r.length<2||(e.crossFileDuplication=t.measureCrossFileDuplication(r,n.duplication))}function M(e,t,n){let r=new Set(e?.duplication.duplicateLineNumbers),i=t&&Object.hasOwn(t.duplicateLineNumbersByFile,n)?t.duplicateLineNumbersByFile[n]??[]:[];for(let e of i)r.add(e);return r}function N(e,t){return l.has(e)?!0:!t.includeTests&&u.has(e)}function P(e,t){let n=c.default.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${c.default.sep}`)&&!c.default.isAbsolute(n)}function F(e,t,n=!1){if(n)return I(e,t,!0)!==void 0;let r=e.split(`/`);for(let e of r.slice(0,-1))if(l.has(e)||!t.includeTests&&u.has(e))return!1;return I(e,t)!==void 0}function I(e,t,r=!1){let i=e.toLowerCase();if(!(!r&&(i.endsWith(`.d.ts`)||i.endsWith(`.d.mts`)||i.endsWith(`.d.cts`)||i.endsWith(`.min.js`)||i.endsWith(`.pnp.cjs`)))&&(r||t.includeTests||!d.test(c.default.basename(e))&&!f.test(c.default.basename(e))))return n.detectLanguage(e)}function L(e,t){return c.default.relative(t,e).replaceAll(c.default.sep,`/`)||c.default.basename(e)}function R(e){process.stdout.write(e)}function z(e){process.stderr.write(e)}exports.addCrossFileDuplication=j,exports.collectDuplicatedLineNumbers=M,exports.configSearchDirectory=m,exports.formatPath=L,exports.getLanguage=I,exports.isWithinDirectory=P,exports.resolveTarget=p,exports.scanListedFiles=_,exports.scanTarget=g,exports.writeStderr=z,exports.writeStdout=R;
|
|
2
2
|
//# sourceMappingURL=scan.cjs.map
|