code-gauge 1.7.0 → 1.8.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 +3 -3
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/cliConfig.cjs +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.d.ts +1 -1
- package/dist/cliConfig.js +1 -1
- package/dist/cliConfig.js.map +1 -1
- package/dist/languages.cjs +1 -1
- package/dist/languages.cjs.map +1 -1
- package/dist/languages.js +1 -1
- package/dist/languages.js.map +1 -1
- package/dist/metrics.cjs +1 -1
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/metrics.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +2 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readdir, readFile, realpath, stat } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { Command, InvalidArgumentError } from 'commander';\nimport { measureArchitecture, type ArchitectureFileMetrics, type ArchitectureMetrics } from './architectureMetrics.js';\nimport {\n type CliOptions,\n configFileName,\n loadConfig,\n type ResolvedOptions,\n resolveOptions,\n resolveThresholds,\n type Thresholds,\n} from './cliConfig.js';\nimport { measureCode } from './metrics.js';\nimport { measureTypeScriptProject, type TypeScriptProjectMetrics } from './typescriptProject.js';\nimport type { CodeMetrics, FunctionMetrics, LanguageName } from './types.js';\n\ninterface FileMetrics {\n file: string;\n metrics: CodeMetrics;\n}\n\ninterface RiskTrigger {\n metric: string;\n score: number;\n threshold: number;\n value: number;\n}\n\ninterface RiskFinding {\n cognitiveComplexity: number;\n cyclomaticComplexity: number;\n endLine?: number;\n file: string;\n kind: 'component' | 'file' | 'function';\n language: LanguageName;\n name?: string;\n score: number;\n startLine?: number;\n triggers: RiskTrigger[];\n}\n\ninterface ScanResult {\n architecture?: ArchitectureMetrics;\n componentFunctionKeys?: Set<string>;\n displayRoot: string;\n errors: string[];\n fatalError?: string;\n files: FileMetrics[];\n namedComponentFunctionKeys?: Set<string>;\n typeScriptProject?: TypeScriptProjectMetrics;\n}\n\nconst languageByExtension = new Map<string, LanguageName>([\n ['.cjs', 'javascript'],\n ['.cts', 'typescript'],\n ['.go', 'go'],\n ['.js', 'javascript'],\n ['.jsx', 'jsx'],\n ['.mjs', 'javascript'],\n ['.mts', 'typescript'],\n ['.py', 'python'],\n ['.ts', 'typescript'],\n ['.tsx', 'tsx'],\n]);\n\nconst ignoredDirectoryNames = new Set([\n '.agents',\n '.claude',\n '.cursor',\n '.git',\n '.next',\n '.playwright-cli',\n '.tox',\n '.tmp',\n '.turbo',\n '.venv',\n '.yarn',\n '__fixtures__',\n '__generated__',\n '__pycache__',\n 'coverage',\n 'dist',\n 'fixtures',\n 'generated',\n 'node_modules',\n 'test-fixtures',\n 'vendor',\n 'venv',\n]);\n\nconst testDirectoryNames = new Set(['__tests__', 'test', 'tests']);\nconst testFilePattern = /(?:^test(?:[_-].*)?|\\.(?:spec|test)|[_-]test)\\.[^.]+$/iu;\n\n// oxlint-disable-next-line unicorn/prefer-top-level-await -- CommonJS build output cannot preserve top-level await.\nvoid main().catch((error: unknown) => {\n writeStderr(`Error: ${formatError(error)}\\n`);\n process.exitCode = 1;\n});\n\nasync function main(): Promise<void> {\n const program = new Command()\n .name('code-gauge')\n .description('Measure code metrics and list high-risk findings.')\n .argument('[target]', 'file or directory to measure', '.')\n .option('--config <path>', `config file to use instead of the auto-detected ${configFileName}`)\n .option('--file-loc-threshold <number>', 'minimum file code LOC to report', parsePositiveInteger)\n .option('--function-loc-threshold <number>', 'minimum function physical LOC span to report', parsePositiveInteger)\n .option(\n '--component-loc-threshold <number>',\n 'minimum React component physical LOC span to report',\n parsePositiveInteger\n )\n .option('--cognitive-threshold <number>', 'minimum cognitive complexity to report', parsePositiveInteger)\n .option('--cyclomatic-threshold <number>', 'minimum cyclomatic complexity to report', parsePositiveInteger)\n .option('--call-threshold <number>', 'minimum function call count to report', parsePositiveInteger)\n .option('--import-threshold <number>', 'minimum unique import sources per file to report', parsePositiveInteger)\n .option('--fan-out-threshold <number>', 'minimum intra-file fan-out per function to report', parsePositiveInteger)\n .option('--parameter-threshold <number>', 'minimum function parameter count to report', parsePositiveInteger)\n .option(\n '--duplicate-block-threshold <number>',\n 'minimum count of duplicated code blocks per file to report',\n parsePositiveInteger\n )\n .option(\n '--transitive-dependency-threshold <number>',\n 'minimum transitively reachable local files to report',\n parsePositiveInteger\n )\n .option(\n '--structural-breadth-threshold <number>',\n 'minimum structural breadth score to report',\n parsePositiveInteger\n )\n .option(\n '--structural-coordination-threshold <number>',\n 'minimum structural coordination score to report',\n parsePositiveInteger\n )\n .option('--state-mutation-threshold <number>', 'minimum state mutation score to report', parsePositiveInteger)\n .option(\n '--duplicate-symbol-group-threshold <number>',\n 'minimum duplicate symbol group count to report',\n parsePositiveInteger\n )\n .option('--max-findings <number>', 'maximum number of risk findings to print', parsePositiveInteger)\n .option('--include-tests', 'include test files and test directories')\n .option('--tsconfig <path>', 'TypeScript project file to use instead of auto-detected tsconfig.json')\n .option('--json', 'print JSON output')\n .option('--fail-on-error', 'exit with code 1 when files or directories cannot be scanned')\n .option('--fail-on-risk', 'exit with code 1 when high-risk findings are found');\n\n program.action(async (target: string, cliOptions: CliOptions) => {\n const resolvedTarget = resolveTarget(target);\n const config = await loadConfig(cliOptions.config, await configSearchDirectory(resolvedTarget));\n const options = resolveOptions(cliOptions, config);\n const result = await scanTarget(resolvedTarget, options);\n await addArchitectureMetrics(result);\n await addTypeScriptProjectMetrics(result, options, resolvedTarget);\n const risks = findRiskyFunctions(\n result.files,\n result.architecture,\n result.componentFunctionKeys,\n result.namedComponentFunctionKeys,\n options,\n result.displayRoot\n );\n\n if (options.json) {\n printJson(result, risks, options);\n } else {\n printTextReport(resolvedTarget, result, risks, options);\n }\n\n if (\n result.fatalError ||\n (options.failOnError && result.errors.length > 0) ||\n (options.failOnRisk && risks.length > 0)\n ) {\n process.exitCode = 1;\n }\n });\n\n await program.parseAsync();\n}\n\nfunction resolveTarget(target: string): string {\n if (target === '~') {\n return os.homedir();\n }\n\n if (target.startsWith('~/')) {\n return path.join(os.homedir(), target.slice(2));\n }\n\n return path.resolve(target);\n}\n\n/** Returns the directory from which the config file search should start (the target itself if it is a directory). */\nasync function configSearchDirectory(target: string): Promise<string> {\n try {\n const targetStat = await stat(target);\n return targetStat.isDirectory() ? target : path.dirname(target);\n } catch {\n return path.dirname(target);\n }\n}\n\nasync function scanTarget(target: string, options: ResolvedOptions): Promise<ScanResult> {\n const files: FileMetrics[] = [];\n const errors: string[] = [];\n const visitedFiles = new Set<string>();\n let canonicalTarget = target;\n try {\n canonicalTarget = await realpath(target);\n } catch {\n // stat below reports missing targets with the original path.\n }\n\n const fallbackDisplayRoot = path.dirname(canonicalTarget);\n let targetStat;\n\n try {\n targetStat = await stat(canonicalTarget);\n } catch (error) {\n const fatalError = `${formatPath(canonicalTarget, fallbackDisplayRoot)}: ${formatError(error)}`;\n return { displayRoot: fallbackDisplayRoot, files, errors: [fatalError], fatalError };\n }\n\n if (targetStat.isFile()) {\n const displayRoot = path.dirname(canonicalTarget);\n const language = getLanguage(canonicalTarget, options, true);\n if (!language) {\n const fatalError = `${formatPath(canonicalTarget, displayRoot)}: unsupported file type`;\n return { displayRoot, files, errors: [fatalError], fatalError };\n }\n\n await measureFile(canonicalTarget, language, files, errors, visitedFiles, displayRoot, canonicalTarget);\n return { displayRoot, files, errors };\n }\n\n await scanDirectory(canonicalTarget, options, files, errors, new Set(), visitedFiles, canonicalTarget);\n return { displayRoot: canonicalTarget, files, errors };\n}\n\nasync function addTypeScriptProjectMetrics(\n result: ScanResult,\n options: ResolvedOptions,\n resolvedTarget: string\n): Promise<void> {\n if (result.fatalError) {\n return;\n }\n if (result.files.length === 0) {\n return;\n }\n\n const explicitConfigFile = options.tsconfig;\n const isExplicitConfig = explicitConfigFile !== undefined;\n if (!isExplicitConfig && !result.files.some(({ file }) => isTypeScriptProjectCandidateFile(file))) {\n return;\n }\n\n const configFile = explicitConfigFile ? resolveTarget(explicitConfigFile) : await findNearestTsconfig(resolvedTarget);\n if (!configFile) {\n return;\n }\n\n try {\n result.typeScriptProject = await measureTypeScriptProject(\n configFile,\n result.files.map(({ file }) => file)\n );\n result.componentFunctionKeys = new Set(\n result.typeScriptProject.reactComponentFunctions.map((component) =>\n functionLocationKey(component.file, component.startLine, component.startColumn)\n )\n );\n result.namedComponentFunctionKeys = new Set(\n result.typeScriptProject.reactComponentFunctions.flatMap((component) =>\n component.name ? [functionNameLocationKey(component.file, component.name, component.startLine)] : []\n )\n );\n } catch (error) {\n if (isExplicitConfig) {\n result.errors.push(`${formatPath(configFile, result.displayRoot)}: ${formatError(error)}`);\n }\n }\n}\n\nfunction isTypeScriptProjectCandidateFile(file: string): boolean {\n return ['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx'].includes(path.extname(file));\n}\n\nasync function findNearestTsconfig(target: string): Promise<string | undefined> {\n const targetStat = await stat(target);\n let currentDirectory = targetStat.isDirectory() ? target : path.dirname(target);\n while (true) {\n const configFile = path.join(currentDirectory, 'tsconfig.json');\n if (await fileExists(configFile)) {\n return configFile;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n try {\n const fileStat = await stat(file);\n return fileStat.isFile();\n } catch {\n return false;\n }\n}\n\nasync function addArchitectureMetrics(result: ScanResult): Promise<void> {\n if (result.fatalError) {\n return;\n }\n\n try {\n result.architecture = measureArchitecture(\n result.files.map(({ file, metrics }) => ({ file, metrics })),\n result.displayRoot\n );\n } catch (error) {\n result.errors.push(`architecture metrics: ${formatError(error)}`);\n }\n}\n\nasync function scanDirectory(\n directory: string,\n options: ResolvedOptions,\n files: FileMetrics[],\n errors: string[],\n visitedDirectories: Set<string>,\n visitedFiles: Set<string>,\n rootDirectory: string\n): Promise<void> {\n let resolvedDirectory;\n try {\n resolvedDirectory = await realpath(directory);\n } catch (error) {\n errors.push(`${formatPath(directory, rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (!isWithinDirectory(resolvedDirectory, rootDirectory)) {\n return;\n }\n\n if (visitedDirectories.has(resolvedDirectory)) {\n return;\n }\n visitedDirectories.add(resolvedDirectory);\n\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch (error) {\n errors.push(`${formatPath(directory, rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n await scanSymbolicLink(\n entry.name,\n entryPath,\n options,\n files,\n errors,\n visitedDirectories,\n visitedFiles,\n rootDirectory\n );\n continue;\n }\n\n if (entry.isDirectory()) {\n if (shouldSkipDirectory(entry.name, options)) {\n continue;\n }\n await scanDirectory(entryPath, options, files, errors, visitedDirectories, visitedFiles, rootDirectory);\n continue;\n }\n\n if (entry.isFile()) {\n await measureScannableFile(entryPath, options, files, errors, visitedFiles, rootDirectory);\n }\n }\n}\n\nasync function scanSymbolicLink(\n name: string,\n entryPath: string,\n options: ResolvedOptions,\n files: FileMetrics[],\n errors: string[],\n visitedDirectories: Set<string>,\n visitedFiles: Set<string>,\n rootDirectory: string\n): Promise<void> {\n let resolvedPath;\n try {\n resolvedPath = await realpath(entryPath);\n } catch (error) {\n errors.push(`${formatPath(entryPath, rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (!isWithinDirectory(resolvedPath, rootDirectory)) {\n return;\n }\n\n let entryStat;\n try {\n entryStat = await stat(entryPath);\n } catch (error) {\n errors.push(`${formatPath(entryPath, rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (entryStat.isDirectory()) {\n if (shouldSkipDirectory(name, options) || shouldSkipDirectory(path.basename(resolvedPath), options)) {\n return;\n }\n await scanDirectory(entryPath, options, files, errors, visitedDirectories, visitedFiles, rootDirectory);\n return;\n }\n\n if (entryStat.isFile()) {\n await measureScannableFile(\n entryPath,\n options,\n files,\n errors,\n visitedFiles,\n rootDirectory,\n resolvedPath,\n resolvedPath\n );\n }\n}\n\nasync function measureScannableFile(\n file: string,\n options: ResolvedOptions,\n files: FileMetrics[],\n errors: string[],\n visitedFiles: Set<string>,\n displayRoot: string,\n languageFile = file,\n realFile?: string\n): Promise<void> {\n const language = getLanguage(languageFile, options);\n if (language) {\n await measureFile(file, language, files, errors, visitedFiles, displayRoot, realFile);\n }\n}\n\nasync function measureFile(\n file: string,\n language: LanguageName,\n files: FileMetrics[],\n errors: string[],\n visitedFiles: Set<string>,\n displayRoot: string,\n realFile?: string\n): Promise<void> {\n try {\n const resolvedFile = realFile ?? (await realpath(file));\n if (visitedFiles.has(resolvedFile)) {\n return;\n }\n visitedFiles.add(resolvedFile);\n\n const code = await readFile(file, 'utf8');\n files.push({\n file,\n metrics: measureCode(code, { language }),\n });\n } catch (error) {\n errors.push(`${formatPath(file, displayRoot)}: ${formatError(error)}`);\n }\n}\n\nfunction findRiskyFunctions(\n files: FileMetrics[],\n architecture: ArchitectureMetrics | undefined,\n componentFunctionKeys: Set<string> | undefined,\n namedComponentFunctionKeys: Set<string> | undefined,\n options: ResolvedOptions,\n displayRoot: string\n): RiskFinding[] {\n const architectureByFile = new Map(architecture?.files.map((file) => [file.file, file]));\n const findings = files.flatMap(({ file, metrics }) => {\n const isReactFile = metrics.functions.some(\n (fn) => fn.returnsJsx || isReactComponent(file, fn, componentFunctionKeys, namedComponentFunctionKeys)\n );\n const thresholds = resolveThresholds(options, metrics.language, isReactFile);\n return [\n ...findRiskyFileMetrics(\n file,\n metrics,\n architectureByFile.get(formatPath(file, displayRoot)),\n thresholds,\n displayRoot\n ),\n ...metrics.functions.flatMap((fn) =>\n findRiskyFunctionMetrics(\n file,\n metrics.language,\n fn,\n thresholds,\n displayRoot,\n componentFunctionKeys,\n namedComponentFunctionKeys\n )\n ),\n ];\n });\n\n findings.sort(compareRiskFindings);\n return findings;\n}\n\nfunction findRiskyFileMetrics(\n file: string,\n metrics: CodeMetrics,\n architecture: ArchitectureFileMetrics | undefined,\n thresholds: Thresholds,\n displayRoot: string\n): RiskFinding[] {\n const triggers: RiskTrigger[] = [];\n const formattedFile = formatPath(file, displayRoot);\n addTrigger(triggers, 'file LOC', metrics.lines.code, thresholds.fileLoc);\n addTrigger(triggers, 'import sources', metrics.coupling.importSourceCount, thresholds.import);\n addTrigger(triggers, 'duplicated blocks', metrics.duplication.duplicateBlockCount, thresholds.duplicateBlock);\n if (architecture) {\n const hasFileScaleRisk = metrics.lines.code >= 100 || architecture.directLocalDependencyCount >= 8;\n if (hasFileScaleRisk) {\n addTrigger(\n triggers,\n 'transitive local dependencies',\n architecture.transitiveLocalDependencyCount,\n thresholds.transitiveDependency\n );\n }\n if (\n triggers.length > 0 ||\n architecture.directLocalDependencyCount >= 8 ||\n architecture.structuralCoordination.score >= thresholds.structuralCoordination\n ) {\n addTrigger(triggers, 'structural breadth', architecture.structuralBreadthScore, thresholds.structuralBreadth);\n }\n addTrigger(\n triggers,\n 'structural coordination',\n architecture.structuralCoordination.score,\n thresholds.structuralCoordination\n );\n addTrigger(\n triggers,\n 'state mutation',\n architecture.structuralCoordination.stateMutationScore,\n thresholds.stateMutation\n );\n addTrigger(\n triggers,\n 'duplicate symbol groups',\n architecture.duplicateSymbolGroupCount,\n thresholds.duplicateSymbolGroup\n );\n }\n if (triggers.length === 0) {\n return [];\n }\n\n return [\n {\n file: formattedFile,\n language: metrics.language,\n kind: 'file',\n cyclomaticComplexity: metrics.cyclomaticComplexity,\n cognitiveComplexity: metrics.cognitiveComplexity,\n triggers,\n score: maxTriggerScore(triggers),\n },\n ];\n}\n\nfunction findRiskyFunctionMetrics(\n file: string,\n language: LanguageName,\n fn: FunctionMetrics,\n thresholds: Thresholds,\n displayRoot: string,\n componentFunctionKeys?: Set<string>,\n namedComponentFunctionKeys?: Set<string>\n): RiskFinding[] {\n const loc = fn.endLine - fn.startLine + 1;\n const isComponent = isReactComponent(file, fn, componentFunctionKeys, namedComponentFunctionKeys);\n const kind = isComponent ? 'component' : 'function';\n const triggers: RiskTrigger[] = [];\n addTrigger(triggers, 'cognitive complexity', fn.cognitiveComplexity, thresholds.cognitive);\n addTrigger(triggers, 'cyclomatic complexity', fn.cyclomaticComplexity, thresholds.cyclomatic);\n addTrigger(triggers, isComponent ? 'component LOC' : 'function LOC', loc, getLocThreshold(isComponent, thresholds));\n addTrigger(triggers, 'function calls', fn.callCount, thresholds.call);\n addTrigger(triggers, 'fan-out', fn.fanOut, thresholds.fanOut);\n addTrigger(triggers, 'parameters', fn.parameterCount, thresholds.parameter);\n if (triggers.length === 0) {\n return [];\n }\n\n return [\n {\n file: formatPath(file, displayRoot),\n language,\n kind,\n name: fn.name ?? '<anonymous>',\n startLine: fn.startLine,\n endLine: fn.endLine,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n triggers,\n score: maxTriggerScore(triggers),\n },\n ];\n}\n\nfunction addTrigger(triggers: RiskTrigger[], metric: string, value: number, threshold: number): void {\n if (value < threshold) {\n return;\n }\n\n triggers.push({ metric, value, threshold, score: value / threshold });\n}\n\nfunction isReactComponent(\n file: string,\n fn: FunctionMetrics,\n componentFunctionKeys: Set<string> | undefined,\n namedComponentFunctionKeys: Set<string> | undefined\n): boolean {\n return (\n componentFunctionKeys?.has(functionLocationKey(file, fn.startLine, fn.startColumn)) ||\n (fn.name ? namedComponentFunctionKeys?.has(functionNameLocationKey(file, fn.name, fn.startLine)) : false) ||\n false\n );\n}\n\nfunction getLocThreshold(isComponent: boolean, thresholds: Thresholds): number {\n return isComponent ? thresholds.componentLoc : thresholds.functionLoc;\n}\n\nfunction functionLocationKey(file: string, startLine: number, startColumn: number): string {\n return `${path.resolve(file)}:${startLine}:${startColumn}`;\n}\n\nfunction functionNameLocationKey(file: string, name: string, startLine: number): string {\n return `${path.resolve(file)}:${name}:${startLine}`;\n}\n\nfunction maxTriggerScore(triggers: RiskTrigger[]): number {\n return Math.max(...triggers.map((trigger) => trigger.score));\n}\n\nfunction compareRiskFindings(left: RiskFinding, right: RiskFinding): number {\n return (\n right.score - left.score ||\n left.file.localeCompare(right.file) ||\n (left.startLine ?? 0) - (right.startLine ?? 0) ||\n (left.endLine ?? 0) - (right.endLine ?? 0) ||\n left.kind.localeCompare(right.kind)\n );\n}\n\nfunction printJson(result: ScanResult, risks: RiskFinding[], options: ResolvedOptions): void {\n const summary = summarize(result.files);\n const reportedRisks = risks.slice(0, options.maxFindings);\n writeStdout(\n JSON.stringify(\n {\n summary,\n thresholds: options.thresholds,\n profileThresholds: options.profileThresholds,\n totalRisks: risks.length,\n truncated: reportedRisks.length < risks.length,\n architecture: result.architecture,\n typeScriptProject: result.typeScriptProject,\n risks: reportedRisks,\n errors: result.errors,\n },\n undefined,\n 2\n ) + '\\n'\n );\n}\n\nfunction printTextReport(target: string, result: ScanResult, risks: RiskFinding[], options: ResolvedOptions): void {\n if (result.fatalError) {\n writeStderr(`Error: ${result.fatalError}\\n`);\n return;\n }\n\n const { thresholds } = options;\n const summary = summarize(result.files);\n writeStdout(`Measured ${summary.fileCount} files under ${target}\\n`);\n writeStdout(\n `LOC ${summary.linesOfCode}, functions ${summary.functionCount}, max cyclomatic ${summary.maxCyclomaticComplexity}, max cognitive ${summary.maxCognitiveComplexity}\\n`\n );\n writeStdout(\n `Calls ${summary.callCount}, internal edges ${summary.internalCallCount}, max call depth ${summary.maxCallDepth}, imports ${summary.importSourceCount}, exports ${summary.exportCount}\\n`\n );\n writeStdout(\n `Type annotations ${summary.typeAnnotationCount}, type aliases ${summary.typeAliasCount}, interfaces ${summary.interfaceCount}, avg cohesion ${summary.averageFunctionIdentifierOverlap.toFixed(2)}\\n`\n );\n if (result.architecture) {\n writeStdout(`${formatArchitectureMetrics(result.architecture)}\\n`);\n }\n if (result.typeScriptProject) {\n writeStdout(`${formatTypeScriptProjectMetrics(result.typeScriptProject)}\\n`);\n }\n writeStdout(\n `Risk thresholds: file LOC >= ${thresholds.fileLoc}, function LOC >= ${thresholds.functionLoc}, component LOC >= ${thresholds.componentLoc}, cognitive >= ${thresholds.cognitive}, cyclomatic >= ${thresholds.cyclomatic}, calls >= ${thresholds.call}, imports >= ${thresholds.import}, fan-out >= ${thresholds.fanOut}, parameters >= ${thresholds.parameter}, duplicated blocks >= ${thresholds.duplicateBlock}\\n`\n );\n const profileOverrides = formatProfileOverrides(options.profileThresholds);\n if (profileOverrides) {\n writeStdout(`Per-language overrides: ${profileOverrides}\\n`);\n }\n\n if (risks.length === 0) {\n writeStdout('No high-risk findings found.\\n');\n } else {\n const reportedRisks = risks.slice(0, options.maxFindings);\n const totalSuffix = risks.length > reportedRisks.length ? ` of ${risks.length}` : '';\n writeStdout(`\\nHigh-risk findings (top ${reportedRisks.length}${totalSuffix}):\\n`);\n for (const risk of reportedRisks) {\n writeStdout(`${formatRiskLocation(risk)} ${formatRiskName(risk)} ${formatRiskMetrics(risk)}\\n`);\n }\n }\n\n if (result.errors.length > 0) {\n writeStderr(`\\nSkipped ${result.errors.length} files or directories:\\n`);\n for (const error of result.errors.slice(0, 10)) {\n writeStderr(`- ${error}\\n`);\n }\n if (result.errors.length > 10) {\n writeStderr(`- ... ${result.errors.length - 10} more\\n`);\n }\n }\n}\n\nfunction formatProfileOverrides(profileThresholds: ResolvedOptions['profileThresholds']): string {\n return Object.entries(profileThresholds)\n .map(\n ([profile, overrides]) =>\n `${profile} { ${Object.entries(overrides)\n .map(([metric, value]) => `${metric} ${value}`)\n .join(', ')} }`\n )\n .join('; ');\n}\n\nfunction formatRiskLocation(risk: RiskFinding): string {\n return risk.startLine === undefined || risk.endLine === undefined\n ? risk.file\n : `${risk.file}:${risk.startLine}-${risk.endLine}`;\n}\n\nfunction formatRiskName(risk: RiskFinding): string {\n return risk.name ? `${risk.kind} ${risk.name}` : risk.kind;\n}\n\nfunction formatRiskMetrics(risk: RiskFinding): string {\n const triggerText = risk.triggers\n .map(\n (trigger) => `${trigger.metric} ${formatMetricValue(trigger.value)} >= ${formatMetricValue(trigger.threshold)}`\n )\n .join(', ');\n return `(${triggerText}; cyclomatic ${risk.cyclomaticComplexity}, cognitive ${risk.cognitiveComplexity})`;\n}\n\nfunction formatMetricValue(value: number): string {\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n}\n\nfunction formatArchitectureMetrics(metrics: ArchitectureMetrics): string {\n const maxStateMutationScore = Math.max(\n 0,\n ...metrics.files.map((file) => file.structuralCoordination.stateMutationScore)\n );\n return `Architecture max reachable files ${metrics.maxTransitiveLocalDependencyCount}, max structural breadth ${metrics.maxStructuralBreadthScore}, max structural coordination ${metrics.maxStructuralCoordinationScore}, max state mutation ${maxStateMutationScore}, duplicate symbol groups ${metrics.duplicateSymbolGroups.length}`;\n}\n\nfunction formatTypeScriptProjectMetrics(metrics: TypeScriptProjectMetrics): string {\n return `TypeScript project root files ${metrics.rootFileCount}, measured roots ${metrics.measuredRootFileCount}, semantic diagnostics ${metrics.semanticDiagnosticCount}, resolved calls ${metrics.resolvedCallExpressionCount}/${metrics.callExpressionCount} (${(metrics.resolvedCallExpressionRatio * 100).toFixed(1)}%)`;\n}\n\nfunction summarize(files: FileMetrics[]): {\n fileCount: number;\n functionCount: number;\n linesOfCode: number;\n maxCognitiveComplexity: number;\n maxCyclomaticComplexity: number;\n callCount: number;\n internalCallCount: number;\n maxCallDepth: number;\n importSourceCount: number;\n relativeImportCount: number;\n externalImportCount: number;\n exportCount: number;\n averageFunctionIdentifierOverlap: number;\n typeAnnotationCount: number;\n typeAliasCount: number;\n interfaceCount: number;\n genericParameterCount: number;\n} {\n let functionCount = 0;\n let linesOfCode = 0;\n let maxCyclomaticComplexity = 0;\n let maxCognitiveComplexity = 0;\n let callCount = 0;\n let internalCallCount = 0;\n let maxCallDepth = 0;\n let importSourceCount = 0;\n let relativeImportCount = 0;\n let externalImportCount = 0;\n let exportCount = 0;\n let cohesionTotal = 0;\n let typeAnnotationCount = 0;\n let typeAliasCount = 0;\n let interfaceCount = 0;\n let genericParameterCount = 0;\n\n for (const file of files) {\n functionCount += file.metrics.functionCount;\n linesOfCode += file.metrics.lines.code;\n maxCyclomaticComplexity = Math.max(maxCyclomaticComplexity, file.metrics.maxCyclomaticComplexity);\n maxCognitiveComplexity = Math.max(maxCognitiveComplexity, file.metrics.maxCognitiveComplexity);\n callCount += file.metrics.callGraph.callCount;\n internalCallCount += file.metrics.callGraph.internalCallCount;\n maxCallDepth = Math.max(maxCallDepth, file.metrics.callGraph.maxCallDepth);\n importSourceCount += file.metrics.coupling.importSourceCount;\n relativeImportCount += file.metrics.coupling.relativeImportCount;\n externalImportCount += file.metrics.coupling.externalImportCount;\n exportCount += file.metrics.coupling.exportCount;\n cohesionTotal += file.metrics.cohesion.averageFunctionIdentifierOverlap;\n typeAnnotationCount += file.metrics.typeComplexity.typeAnnotationCount;\n typeAliasCount += file.metrics.typeComplexity.typeAliasCount;\n interfaceCount += file.metrics.typeComplexity.interfaceCount;\n genericParameterCount += file.metrics.typeComplexity.genericParameterCount;\n }\n\n return {\n fileCount: files.length,\n functionCount,\n linesOfCode,\n maxCyclomaticComplexity,\n maxCognitiveComplexity,\n callCount,\n internalCallCount,\n maxCallDepth,\n importSourceCount,\n relativeImportCount,\n externalImportCount,\n exportCount,\n averageFunctionIdentifierOverlap: files.length === 0 ? 0 : cohesionTotal / files.length,\n typeAnnotationCount,\n typeAliasCount,\n interfaceCount,\n genericParameterCount,\n };\n}\n\nfunction shouldSkipDirectory(name: string, options: ResolvedOptions): boolean {\n if (ignoredDirectoryNames.has(name)) {\n return true;\n }\n\n if (options.includeTests) {\n return false;\n }\n\n return testDirectoryNames.has(name);\n}\n\nfunction isWithinDirectory(candidate: string, directory: string): boolean {\n const relative = path.relative(directory, candidate);\n return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));\n}\n\nfunction getLanguage(file: string, options: ResolvedOptions, explicitTarget = false): LanguageName | undefined {\n const lowerFile = file.toLowerCase();\n if (\n !explicitTarget &&\n (lowerFile.endsWith('.d.ts') ||\n lowerFile.endsWith('.d.mts') ||\n lowerFile.endsWith('.d.cts') ||\n lowerFile.endsWith('.min.js') ||\n lowerFile.endsWith('.pnp.cjs'))\n ) {\n return undefined;\n }\n\n if (!explicitTarget && !options.includeTests && testFilePattern.test(path.basename(file))) {\n return undefined;\n }\n\n return languageByExtension.get(path.extname(lowerFile));\n}\n\nfunction parsePositiveInteger(value: string): number {\n if (!/^[1-9]\\d*$/u.test(value)) {\n throw new InvalidArgumentError('Expected a positive integer.');\n }\n\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 1) {\n throw new InvalidArgumentError('Expected a positive integer.');\n }\n return parsed;\n}\n\nfunction formatPath(file: string, base: string): string {\n return path.relative(base, file) || path.basename(file);\n}\n\nfunction writeStdout(message: string): void {\n process.stdout.write(message);\n}\n\nfunction writeStderr(message: string): void {\n process.stderr.write(message);\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"names":["languageByExtension","Map","ignoredDirectoryNames","Set","testDirectoryNames","testFilePattern","resolveTarget","target","os","homedir","startsWith","path","join","slice","resolve","async","fileExists","file","stat","isFile","scanDirectory","directory","options","files","errors","visitedDirectories","visitedFiles","rootDirectory","resolvedDirectory","entries","realpath","error","push","formatPath","formatError","isWithinDirectory","has","add","readdir","withFileTypes","entry","entryPath","name","isSymbolicLink","scanSymbolicLink","isDirectory","shouldSkipDirectory","measureScannableFile","resolvedPath","entryStat","basename","displayRoot","languageFile","realFile","language","getLanguage","measureFile","resolvedFile","code","readFile","metrics","measureCode","findRiskyFileMetrics","architecture","thresholds","triggers","formattedFile","addTrigger","lines","fileLoc","coupling","importSourceCount","import","duplication","duplicateBlockCount","duplicateBlock","directLocalDependencyCount","transitiveLocalDependencyCount","transitiveDependency","length","structuralCoordination","score","structuralBreadthScore","structuralBreadth","stateMutationScore","stateMutation","duplicateSymbolGroupCount","duplicateSymbolGroup","kind","cyclomaticComplexity","cognitiveComplexity","maxTriggerScore","metric","value","threshold","isReactComponent","fn","componentFunctionKeys","namedComponentFunctionKeys","functionLocationKey","startLine","startColumn","functionNameLocationKey","Math","max","map","trigger","compareRiskFindings","left","right","localeCompare","endLine","formatRiskLocation","risk","undefined","formatRiskName","formatRiskMetrics","formatMetricValue","Number","isInteger","String","toFixed","summarize","functionCount","linesOfCode","maxCyclomaticComplexity","maxCognitiveComplexity","callCount","internalCallCount","maxCallDepth","relativeImportCount","externalImportCount","exportCount","cohesionTotal","typeAnnotationCount","typeAliasCount","interfaceCount","genericParameterCount","callGraph","cohesion","averageFunctionIdentifierOverlap","typeComplexity","fileCount","includeTests","candidate","relative","sep","isAbsolute","explicitTarget","lowerFile","toLowerCase","endsWith","test","get","extname","parsePositiveInteger","InvalidArgumentError","parsed","isSafeInteger","base","writeStdout","message","process","stdout","write","writeStderr","stderr","Error","program","Command","description","argument","option","configFileName","action","cliOptions","resolvedTarget","config","loadConfig","dirname","configSearchDirectory","resolveOptions","result","canonicalTarget","fallbackDisplayRoot","targetStat","fatalError","scanTarget","measureArchitecture","addArchitectureMetrics","explicitConfigFile","tsconfig","isExplicitConfig","some","includes","isTypeScriptProjectCandidateFile","configFile","currentDirectory","parentDirectory","findNearestTsconfig","typeScriptProject","measureTypeScriptProject","reactComponentFunctions","component","flatMap","addTypeScriptProjectMetrics","risks","architectureByFile","findings","isReactFile","functions","returnsJsx","resolveThresholds","loc","isComponent","cognitive","cyclomatic","componentLoc","functionLoc","getLocThreshold","call","fanOut","parameterCount","parameter","findRiskyFunctionMetrics","sort","findRiskyFunctions","json","summary","reportedRisks","maxFindings","JSON","stringify","profileThresholds","totalRisks","truncated","printJson","maxStateMutationScore","maxTransitiveLocalDependencyCount","maxStructuralBreadthScore","maxStructuralCoordinationScore","duplicateSymbolGroups","formatArchitectureMetrics","rootFileCount","measuredRootFileCount","semanticDiagnosticCount","resolvedCallExpressionCount","callExpressionCount","resolvedCallExpressionRatio","profileOverrides","Object","profile","overrides","totalSuffix","printTextReport","failOnError","failOnRisk","exitCode","parseAsync","main","catch"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readdir, readFile, realpath, stat } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { Command, InvalidArgumentError } from 'commander';\nimport { measureArchitecture, type ArchitectureFileMetrics, type ArchitectureMetrics } from './architectureMetrics.js';\nimport {\n type CliOptions,\n configFileName,\n loadConfig,\n type ResolvedOptions,\n resolveOptions,\n resolveThresholds,\n type Thresholds,\n} from './cliConfig.js';\nimport { measureCode } from './metrics.js';\nimport { measureTypeScriptProject, type TypeScriptProjectMetrics } from './typescriptProject.js';\nimport type { CodeMetrics, FunctionMetrics, LanguageName } from './types.js';\n\ninterface FileMetrics {\n file: string;\n metrics: CodeMetrics;\n}\n\ninterface RiskTrigger {\n metric: string;\n score: number;\n threshold: number;\n value: number;\n}\n\ninterface RiskFinding {\n cognitiveComplexity: number;\n cyclomaticComplexity: number;\n endLine?: number;\n file: string;\n kind: 'component' | 'file' | 'function';\n language: LanguageName;\n name?: string;\n score: number;\n startLine?: number;\n triggers: RiskTrigger[];\n}\n\ninterface ScanResult {\n architecture?: ArchitectureMetrics;\n componentFunctionKeys?: Set<string>;\n displayRoot: string;\n errors: string[];\n fatalError?: string;\n files: FileMetrics[];\n namedComponentFunctionKeys?: Set<string>;\n typeScriptProject?: TypeScriptProjectMetrics;\n}\n\nconst languageByExtension = new Map<string, LanguageName>([\n ['.cjs', 'javascript'],\n ['.cts', 'typescript'],\n ['.go', 'go'],\n ['.js', 'javascript'],\n ['.jsx', 'jsx'],\n ['.mjs', 'javascript'],\n ['.mts', 'typescript'],\n ['.py', 'python'],\n ['.rs', 'rust'],\n ['.ts', 'typescript'],\n ['.tsx', 'tsx'],\n]);\n\nconst ignoredDirectoryNames = new Set([\n '.agents',\n '.claude',\n '.cursor',\n '.git',\n '.next',\n '.playwright-cli',\n '.tox',\n '.tmp',\n '.turbo',\n '.venv',\n '.yarn',\n '__fixtures__',\n '__generated__',\n '__pycache__',\n 'coverage',\n 'dist',\n 'fixtures',\n 'generated',\n 'node_modules',\n 'target',\n 'test-fixtures',\n 'vendor',\n 'venv',\n]);\n\nconst testDirectoryNames = new Set(['__tests__', 'test', 'tests']);\nconst testFilePattern = /(?:^test(?:[_-].*)?|\\.(?:spec|test)|[_-]test)\\.[^.]+$/iu;\n\n// oxlint-disable-next-line unicorn/prefer-top-level-await -- CommonJS build output cannot preserve top-level await.\nvoid main().catch((error: unknown) => {\n writeStderr(`Error: ${formatError(error)}\\n`);\n process.exitCode = 1;\n});\n\nasync function main(): Promise<void> {\n const program = new Command()\n .name('code-gauge')\n .description('Measure code metrics and list high-risk findings.')\n .argument('[target]', 'file or directory to measure', '.')\n .option('--config <path>', `config file to use instead of the auto-detected ${configFileName}`)\n .option('--file-loc-threshold <number>', 'minimum file code LOC to report', parsePositiveInteger)\n .option('--function-loc-threshold <number>', 'minimum function physical LOC span to report', parsePositiveInteger)\n .option(\n '--component-loc-threshold <number>',\n 'minimum React component physical LOC span to report',\n parsePositiveInteger\n )\n .option('--cognitive-threshold <number>', 'minimum cognitive complexity to report', parsePositiveInteger)\n .option('--cyclomatic-threshold <number>', 'minimum cyclomatic complexity to report', parsePositiveInteger)\n .option('--call-threshold <number>', 'minimum function call count to report', parsePositiveInteger)\n .option('--import-threshold <number>', 'minimum unique import sources per file to report', parsePositiveInteger)\n .option('--fan-out-threshold <number>', 'minimum intra-file fan-out per function to report', parsePositiveInteger)\n .option('--parameter-threshold <number>', 'minimum function parameter count to report', parsePositiveInteger)\n .option(\n '--duplicate-block-threshold <number>',\n 'minimum count of duplicated code blocks per file to report',\n parsePositiveInteger\n )\n .option(\n '--transitive-dependency-threshold <number>',\n 'minimum transitively reachable local files to report',\n parsePositiveInteger\n )\n .option(\n '--structural-breadth-threshold <number>',\n 'minimum structural breadth score to report',\n parsePositiveInteger\n )\n .option(\n '--structural-coordination-threshold <number>',\n 'minimum structural coordination score to report',\n parsePositiveInteger\n )\n .option('--state-mutation-threshold <number>', 'minimum state mutation score to report', parsePositiveInteger)\n .option(\n '--duplicate-symbol-group-threshold <number>',\n 'minimum duplicate symbol group count to report',\n parsePositiveInteger\n )\n .option('--max-findings <number>', 'maximum number of risk findings to print', parsePositiveInteger)\n .option('--include-tests', 'include test files and test directories')\n .option('--tsconfig <path>', 'TypeScript project file to use instead of auto-detected tsconfig.json')\n .option('--json', 'print JSON output')\n .option('--fail-on-error', 'exit with code 1 when files or directories cannot be scanned')\n .option('--fail-on-risk', 'exit with code 1 when high-risk findings are found');\n\n program.action(async (target: string, cliOptions: CliOptions) => {\n const resolvedTarget = resolveTarget(target);\n const config = await loadConfig(cliOptions.config, await configSearchDirectory(resolvedTarget));\n const options = resolveOptions(cliOptions, config);\n const result = await scanTarget(resolvedTarget, options);\n await addArchitectureMetrics(result);\n await addTypeScriptProjectMetrics(result, options, resolvedTarget);\n const risks = findRiskyFunctions(\n result.files,\n result.architecture,\n result.componentFunctionKeys,\n result.namedComponentFunctionKeys,\n options,\n result.displayRoot\n );\n\n if (options.json) {\n printJson(result, risks, options);\n } else {\n printTextReport(resolvedTarget, result, risks, options);\n }\n\n if (\n result.fatalError ||\n (options.failOnError && result.errors.length > 0) ||\n (options.failOnRisk && risks.length > 0)\n ) {\n process.exitCode = 1;\n }\n });\n\n await program.parseAsync();\n}\n\nfunction resolveTarget(target: string): string {\n if (target === '~') {\n return os.homedir();\n }\n\n if (target.startsWith('~/')) {\n return path.join(os.homedir(), target.slice(2));\n }\n\n return path.resolve(target);\n}\n\n/** Returns the directory from which the config file search should start (the target itself if it is a directory). */\nasync function configSearchDirectory(target: string): Promise<string> {\n try {\n const targetStat = await stat(target);\n return targetStat.isDirectory() ? target : path.dirname(target);\n } catch {\n return path.dirname(target);\n }\n}\n\nasync function scanTarget(target: string, options: ResolvedOptions): Promise<ScanResult> {\n const files: FileMetrics[] = [];\n const errors: string[] = [];\n const visitedFiles = new Set<string>();\n let canonicalTarget = target;\n try {\n canonicalTarget = await realpath(target);\n } catch {\n // stat below reports missing targets with the original path.\n }\n\n const fallbackDisplayRoot = path.dirname(canonicalTarget);\n let targetStat;\n\n try {\n targetStat = await stat(canonicalTarget);\n } catch (error) {\n const fatalError = `${formatPath(canonicalTarget, fallbackDisplayRoot)}: ${formatError(error)}`;\n return { displayRoot: fallbackDisplayRoot, files, errors: [fatalError], fatalError };\n }\n\n if (targetStat.isFile()) {\n const displayRoot = path.dirname(canonicalTarget);\n const language = getLanguage(canonicalTarget, options, true);\n if (!language) {\n const fatalError = `${formatPath(canonicalTarget, displayRoot)}: unsupported file type`;\n return { displayRoot, files, errors: [fatalError], fatalError };\n }\n\n await measureFile(canonicalTarget, language, files, errors, visitedFiles, displayRoot, canonicalTarget);\n return { displayRoot, files, errors };\n }\n\n await scanDirectory(canonicalTarget, options, files, errors, new Set(), visitedFiles, canonicalTarget);\n return { displayRoot: canonicalTarget, files, errors };\n}\n\nasync function addTypeScriptProjectMetrics(\n result: ScanResult,\n options: ResolvedOptions,\n resolvedTarget: string\n): Promise<void> {\n if (result.fatalError) {\n return;\n }\n if (result.files.length === 0) {\n return;\n }\n\n const explicitConfigFile = options.tsconfig;\n const isExplicitConfig = explicitConfigFile !== undefined;\n if (!isExplicitConfig && !result.files.some(({ file }) => isTypeScriptProjectCandidateFile(file))) {\n return;\n }\n\n const configFile = explicitConfigFile ? resolveTarget(explicitConfigFile) : await findNearestTsconfig(resolvedTarget);\n if (!configFile) {\n return;\n }\n\n try {\n result.typeScriptProject = await measureTypeScriptProject(\n configFile,\n result.files.map(({ file }) => file)\n );\n result.componentFunctionKeys = new Set(\n result.typeScriptProject.reactComponentFunctions.map((component) =>\n functionLocationKey(component.file, component.startLine, component.startColumn)\n )\n );\n result.namedComponentFunctionKeys = new Set(\n result.typeScriptProject.reactComponentFunctions.flatMap((component) =>\n component.name ? [functionNameLocationKey(component.file, component.name, component.startLine)] : []\n )\n );\n } catch (error) {\n if (isExplicitConfig) {\n result.errors.push(`${formatPath(configFile, result.displayRoot)}: ${formatError(error)}`);\n }\n }\n}\n\nfunction isTypeScriptProjectCandidateFile(file: string): boolean {\n return ['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx'].includes(path.extname(file));\n}\n\nasync function findNearestTsconfig(target: string): Promise<string | undefined> {\n const targetStat = await stat(target);\n let currentDirectory = targetStat.isDirectory() ? target : path.dirname(target);\n while (true) {\n const configFile = path.join(currentDirectory, 'tsconfig.json');\n if (await fileExists(configFile)) {\n return configFile;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n try {\n const fileStat = await stat(file);\n return fileStat.isFile();\n } catch {\n return false;\n }\n}\n\nasync function addArchitectureMetrics(result: ScanResult): Promise<void> {\n if (result.fatalError) {\n return;\n }\n\n try {\n result.architecture = measureArchitecture(\n result.files.map(({ file, metrics }) => ({ file, metrics })),\n result.displayRoot\n );\n } catch (error) {\n result.errors.push(`architecture metrics: ${formatError(error)}`);\n }\n}\n\nasync function scanDirectory(\n directory: string,\n options: ResolvedOptions,\n files: FileMetrics[],\n errors: string[],\n visitedDirectories: Set<string>,\n visitedFiles: Set<string>,\n rootDirectory: string\n): Promise<void> {\n let resolvedDirectory;\n try {\n resolvedDirectory = await realpath(directory);\n } catch (error) {\n errors.push(`${formatPath(directory, rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (!isWithinDirectory(resolvedDirectory, rootDirectory)) {\n return;\n }\n\n if (visitedDirectories.has(resolvedDirectory)) {\n return;\n }\n visitedDirectories.add(resolvedDirectory);\n\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch (error) {\n errors.push(`${formatPath(directory, rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n await scanSymbolicLink(\n entry.name,\n entryPath,\n options,\n files,\n errors,\n visitedDirectories,\n visitedFiles,\n rootDirectory\n );\n continue;\n }\n\n if (entry.isDirectory()) {\n if (shouldSkipDirectory(entry.name, options)) {\n continue;\n }\n await scanDirectory(entryPath, options, files, errors, visitedDirectories, visitedFiles, rootDirectory);\n continue;\n }\n\n if (entry.isFile()) {\n await measureScannableFile(entryPath, options, files, errors, visitedFiles, rootDirectory);\n }\n }\n}\n\nasync function scanSymbolicLink(\n name: string,\n entryPath: string,\n options: ResolvedOptions,\n files: FileMetrics[],\n errors: string[],\n visitedDirectories: Set<string>,\n visitedFiles: Set<string>,\n rootDirectory: string\n): Promise<void> {\n let resolvedPath;\n try {\n resolvedPath = await realpath(entryPath);\n } catch (error) {\n errors.push(`${formatPath(entryPath, rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (!isWithinDirectory(resolvedPath, rootDirectory)) {\n return;\n }\n\n let entryStat;\n try {\n entryStat = await stat(entryPath);\n } catch (error) {\n errors.push(`${formatPath(entryPath, rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (entryStat.isDirectory()) {\n if (shouldSkipDirectory(name, options) || shouldSkipDirectory(path.basename(resolvedPath), options)) {\n return;\n }\n await scanDirectory(entryPath, options, files, errors, visitedDirectories, visitedFiles, rootDirectory);\n return;\n }\n\n if (entryStat.isFile()) {\n await measureScannableFile(\n entryPath,\n options,\n files,\n errors,\n visitedFiles,\n rootDirectory,\n resolvedPath,\n resolvedPath\n );\n }\n}\n\nasync function measureScannableFile(\n file: string,\n options: ResolvedOptions,\n files: FileMetrics[],\n errors: string[],\n visitedFiles: Set<string>,\n displayRoot: string,\n languageFile = file,\n realFile?: string\n): Promise<void> {\n const language = getLanguage(languageFile, options);\n if (language) {\n await measureFile(file, language, files, errors, visitedFiles, displayRoot, realFile);\n }\n}\n\nasync function measureFile(\n file: string,\n language: LanguageName,\n files: FileMetrics[],\n errors: string[],\n visitedFiles: Set<string>,\n displayRoot: string,\n realFile?: string\n): Promise<void> {\n try {\n const resolvedFile = realFile ?? (await realpath(file));\n if (visitedFiles.has(resolvedFile)) {\n return;\n }\n visitedFiles.add(resolvedFile);\n\n const code = await readFile(file, 'utf8');\n files.push({\n file,\n metrics: measureCode(code, { language }),\n });\n } catch (error) {\n errors.push(`${formatPath(file, displayRoot)}: ${formatError(error)}`);\n }\n}\n\nfunction findRiskyFunctions(\n files: FileMetrics[],\n architecture: ArchitectureMetrics | undefined,\n componentFunctionKeys: Set<string> | undefined,\n namedComponentFunctionKeys: Set<string> | undefined,\n options: ResolvedOptions,\n displayRoot: string\n): RiskFinding[] {\n const architectureByFile = new Map(architecture?.files.map((file) => [file.file, file]));\n const findings = files.flatMap(({ file, metrics }) => {\n const isReactFile = metrics.functions.some(\n (fn) => fn.returnsJsx || isReactComponent(file, fn, componentFunctionKeys, namedComponentFunctionKeys)\n );\n const thresholds = resolveThresholds(options, metrics.language, isReactFile);\n return [\n ...findRiskyFileMetrics(\n file,\n metrics,\n architectureByFile.get(formatPath(file, displayRoot)),\n thresholds,\n displayRoot\n ),\n ...metrics.functions.flatMap((fn) =>\n findRiskyFunctionMetrics(\n file,\n metrics.language,\n fn,\n thresholds,\n displayRoot,\n componentFunctionKeys,\n namedComponentFunctionKeys\n )\n ),\n ];\n });\n\n findings.sort(compareRiskFindings);\n return findings;\n}\n\nfunction findRiskyFileMetrics(\n file: string,\n metrics: CodeMetrics,\n architecture: ArchitectureFileMetrics | undefined,\n thresholds: Thresholds,\n displayRoot: string\n): RiskFinding[] {\n const triggers: RiskTrigger[] = [];\n const formattedFile = formatPath(file, displayRoot);\n addTrigger(triggers, 'file LOC', metrics.lines.code, thresholds.fileLoc);\n addTrigger(triggers, 'import sources', metrics.coupling.importSourceCount, thresholds.import);\n addTrigger(triggers, 'duplicated blocks', metrics.duplication.duplicateBlockCount, thresholds.duplicateBlock);\n if (architecture) {\n const hasFileScaleRisk = metrics.lines.code >= 100 || architecture.directLocalDependencyCount >= 8;\n if (hasFileScaleRisk) {\n addTrigger(\n triggers,\n 'transitive local dependencies',\n architecture.transitiveLocalDependencyCount,\n thresholds.transitiveDependency\n );\n }\n if (\n triggers.length > 0 ||\n architecture.directLocalDependencyCount >= 8 ||\n architecture.structuralCoordination.score >= thresholds.structuralCoordination\n ) {\n addTrigger(triggers, 'structural breadth', architecture.structuralBreadthScore, thresholds.structuralBreadth);\n }\n addTrigger(\n triggers,\n 'structural coordination',\n architecture.structuralCoordination.score,\n thresholds.structuralCoordination\n );\n addTrigger(\n triggers,\n 'state mutation',\n architecture.structuralCoordination.stateMutationScore,\n thresholds.stateMutation\n );\n addTrigger(\n triggers,\n 'duplicate symbol groups',\n architecture.duplicateSymbolGroupCount,\n thresholds.duplicateSymbolGroup\n );\n }\n if (triggers.length === 0) {\n return [];\n }\n\n return [\n {\n file: formattedFile,\n language: metrics.language,\n kind: 'file',\n cyclomaticComplexity: metrics.cyclomaticComplexity,\n cognitiveComplexity: metrics.cognitiveComplexity,\n triggers,\n score: maxTriggerScore(triggers),\n },\n ];\n}\n\nfunction findRiskyFunctionMetrics(\n file: string,\n language: LanguageName,\n fn: FunctionMetrics,\n thresholds: Thresholds,\n displayRoot: string,\n componentFunctionKeys?: Set<string>,\n namedComponentFunctionKeys?: Set<string>\n): RiskFinding[] {\n const loc = fn.endLine - fn.startLine + 1;\n const isComponent = isReactComponent(file, fn, componentFunctionKeys, namedComponentFunctionKeys);\n const kind = isComponent ? 'component' : 'function';\n const triggers: RiskTrigger[] = [];\n addTrigger(triggers, 'cognitive complexity', fn.cognitiveComplexity, thresholds.cognitive);\n addTrigger(triggers, 'cyclomatic complexity', fn.cyclomaticComplexity, thresholds.cyclomatic);\n addTrigger(triggers, isComponent ? 'component LOC' : 'function LOC', loc, getLocThreshold(isComponent, thresholds));\n addTrigger(triggers, 'function calls', fn.callCount, thresholds.call);\n addTrigger(triggers, 'fan-out', fn.fanOut, thresholds.fanOut);\n addTrigger(triggers, 'parameters', fn.parameterCount, thresholds.parameter);\n if (triggers.length === 0) {\n return [];\n }\n\n return [\n {\n file: formatPath(file, displayRoot),\n language,\n kind,\n name: fn.name ?? '<anonymous>',\n startLine: fn.startLine,\n endLine: fn.endLine,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n triggers,\n score: maxTriggerScore(triggers),\n },\n ];\n}\n\nfunction addTrigger(triggers: RiskTrigger[], metric: string, value: number, threshold: number): void {\n if (value < threshold) {\n return;\n }\n\n triggers.push({ metric, value, threshold, score: value / threshold });\n}\n\nfunction isReactComponent(\n file: string,\n fn: FunctionMetrics,\n componentFunctionKeys: Set<string> | undefined,\n namedComponentFunctionKeys: Set<string> | undefined\n): boolean {\n return (\n componentFunctionKeys?.has(functionLocationKey(file, fn.startLine, fn.startColumn)) ||\n (fn.name ? namedComponentFunctionKeys?.has(functionNameLocationKey(file, fn.name, fn.startLine)) : false) ||\n false\n );\n}\n\nfunction getLocThreshold(isComponent: boolean, thresholds: Thresholds): number {\n return isComponent ? thresholds.componentLoc : thresholds.functionLoc;\n}\n\nfunction functionLocationKey(file: string, startLine: number, startColumn: number): string {\n return `${path.resolve(file)}:${startLine}:${startColumn}`;\n}\n\nfunction functionNameLocationKey(file: string, name: string, startLine: number): string {\n return `${path.resolve(file)}:${name}:${startLine}`;\n}\n\nfunction maxTriggerScore(triggers: RiskTrigger[]): number {\n return Math.max(...triggers.map((trigger) => trigger.score));\n}\n\nfunction compareRiskFindings(left: RiskFinding, right: RiskFinding): number {\n return (\n right.score - left.score ||\n left.file.localeCompare(right.file) ||\n (left.startLine ?? 0) - (right.startLine ?? 0) ||\n (left.endLine ?? 0) - (right.endLine ?? 0) ||\n left.kind.localeCompare(right.kind)\n );\n}\n\nfunction printJson(result: ScanResult, risks: RiskFinding[], options: ResolvedOptions): void {\n const summary = summarize(result.files);\n const reportedRisks = risks.slice(0, options.maxFindings);\n writeStdout(\n JSON.stringify(\n {\n summary,\n thresholds: options.thresholds,\n profileThresholds: options.profileThresholds,\n totalRisks: risks.length,\n truncated: reportedRisks.length < risks.length,\n architecture: result.architecture,\n typeScriptProject: result.typeScriptProject,\n risks: reportedRisks,\n errors: result.errors,\n },\n undefined,\n 2\n ) + '\\n'\n );\n}\n\nfunction printTextReport(target: string, result: ScanResult, risks: RiskFinding[], options: ResolvedOptions): void {\n if (result.fatalError) {\n writeStderr(`Error: ${result.fatalError}\\n`);\n return;\n }\n\n const { thresholds } = options;\n const summary = summarize(result.files);\n writeStdout(`Measured ${summary.fileCount} files under ${target}\\n`);\n writeStdout(\n `LOC ${summary.linesOfCode}, functions ${summary.functionCount}, max cyclomatic ${summary.maxCyclomaticComplexity}, max cognitive ${summary.maxCognitiveComplexity}\\n`\n );\n writeStdout(\n `Calls ${summary.callCount}, internal edges ${summary.internalCallCount}, max call depth ${summary.maxCallDepth}, imports ${summary.importSourceCount}, exports ${summary.exportCount}\\n`\n );\n writeStdout(\n `Type annotations ${summary.typeAnnotationCount}, type aliases ${summary.typeAliasCount}, interfaces ${summary.interfaceCount}, avg cohesion ${summary.averageFunctionIdentifierOverlap.toFixed(2)}\\n`\n );\n if (result.architecture) {\n writeStdout(`${formatArchitectureMetrics(result.architecture)}\\n`);\n }\n if (result.typeScriptProject) {\n writeStdout(`${formatTypeScriptProjectMetrics(result.typeScriptProject)}\\n`);\n }\n writeStdout(\n `Risk thresholds: file LOC >= ${thresholds.fileLoc}, function LOC >= ${thresholds.functionLoc}, component LOC >= ${thresholds.componentLoc}, cognitive >= ${thresholds.cognitive}, cyclomatic >= ${thresholds.cyclomatic}, calls >= ${thresholds.call}, imports >= ${thresholds.import}, fan-out >= ${thresholds.fanOut}, parameters >= ${thresholds.parameter}, duplicated blocks >= ${thresholds.duplicateBlock}\\n`\n );\n const profileOverrides = formatProfileOverrides(options.profileThresholds);\n if (profileOverrides) {\n writeStdout(`Per-language overrides: ${profileOverrides}\\n`);\n }\n\n if (risks.length === 0) {\n writeStdout('No high-risk findings found.\\n');\n } else {\n const reportedRisks = risks.slice(0, options.maxFindings);\n const totalSuffix = risks.length > reportedRisks.length ? ` of ${risks.length}` : '';\n writeStdout(`\\nHigh-risk findings (top ${reportedRisks.length}${totalSuffix}):\\n`);\n for (const risk of reportedRisks) {\n writeStdout(`${formatRiskLocation(risk)} ${formatRiskName(risk)} ${formatRiskMetrics(risk)}\\n`);\n }\n }\n\n if (result.errors.length > 0) {\n writeStderr(`\\nSkipped ${result.errors.length} files or directories:\\n`);\n for (const error of result.errors.slice(0, 10)) {\n writeStderr(`- ${error}\\n`);\n }\n if (result.errors.length > 10) {\n writeStderr(`- ... ${result.errors.length - 10} more\\n`);\n }\n }\n}\n\nfunction formatProfileOverrides(profileThresholds: ResolvedOptions['profileThresholds']): string {\n return Object.entries(profileThresholds)\n .map(\n ([profile, overrides]) =>\n `${profile} { ${Object.entries(overrides)\n .map(([metric, value]) => `${metric} ${value}`)\n .join(', ')} }`\n )\n .join('; ');\n}\n\nfunction formatRiskLocation(risk: RiskFinding): string {\n return risk.startLine === undefined || risk.endLine === undefined\n ? risk.file\n : `${risk.file}:${risk.startLine}-${risk.endLine}`;\n}\n\nfunction formatRiskName(risk: RiskFinding): string {\n return risk.name ? `${risk.kind} ${risk.name}` : risk.kind;\n}\n\nfunction formatRiskMetrics(risk: RiskFinding): string {\n const triggerText = risk.triggers\n .map(\n (trigger) => `${trigger.metric} ${formatMetricValue(trigger.value)} >= ${formatMetricValue(trigger.threshold)}`\n )\n .join(', ');\n return `(${triggerText}; cyclomatic ${risk.cyclomaticComplexity}, cognitive ${risk.cognitiveComplexity})`;\n}\n\nfunction formatMetricValue(value: number): string {\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n}\n\nfunction formatArchitectureMetrics(metrics: ArchitectureMetrics): string {\n const maxStateMutationScore = Math.max(\n 0,\n ...metrics.files.map((file) => file.structuralCoordination.stateMutationScore)\n );\n return `Architecture max reachable files ${metrics.maxTransitiveLocalDependencyCount}, max structural breadth ${metrics.maxStructuralBreadthScore}, max structural coordination ${metrics.maxStructuralCoordinationScore}, max state mutation ${maxStateMutationScore}, duplicate symbol groups ${metrics.duplicateSymbolGroups.length}`;\n}\n\nfunction formatTypeScriptProjectMetrics(metrics: TypeScriptProjectMetrics): string {\n return `TypeScript project root files ${metrics.rootFileCount}, measured roots ${metrics.measuredRootFileCount}, semantic diagnostics ${metrics.semanticDiagnosticCount}, resolved calls ${metrics.resolvedCallExpressionCount}/${metrics.callExpressionCount} (${(metrics.resolvedCallExpressionRatio * 100).toFixed(1)}%)`;\n}\n\nfunction summarize(files: FileMetrics[]): {\n fileCount: number;\n functionCount: number;\n linesOfCode: number;\n maxCognitiveComplexity: number;\n maxCyclomaticComplexity: number;\n callCount: number;\n internalCallCount: number;\n maxCallDepth: number;\n importSourceCount: number;\n relativeImportCount: number;\n externalImportCount: number;\n exportCount: number;\n averageFunctionIdentifierOverlap: number;\n typeAnnotationCount: number;\n typeAliasCount: number;\n interfaceCount: number;\n genericParameterCount: number;\n} {\n let functionCount = 0;\n let linesOfCode = 0;\n let maxCyclomaticComplexity = 0;\n let maxCognitiveComplexity = 0;\n let callCount = 0;\n let internalCallCount = 0;\n let maxCallDepth = 0;\n let importSourceCount = 0;\n let relativeImportCount = 0;\n let externalImportCount = 0;\n let exportCount = 0;\n let cohesionTotal = 0;\n let typeAnnotationCount = 0;\n let typeAliasCount = 0;\n let interfaceCount = 0;\n let genericParameterCount = 0;\n\n for (const file of files) {\n functionCount += file.metrics.functionCount;\n linesOfCode += file.metrics.lines.code;\n maxCyclomaticComplexity = Math.max(maxCyclomaticComplexity, file.metrics.maxCyclomaticComplexity);\n maxCognitiveComplexity = Math.max(maxCognitiveComplexity, file.metrics.maxCognitiveComplexity);\n callCount += file.metrics.callGraph.callCount;\n internalCallCount += file.metrics.callGraph.internalCallCount;\n maxCallDepth = Math.max(maxCallDepth, file.metrics.callGraph.maxCallDepth);\n importSourceCount += file.metrics.coupling.importSourceCount;\n relativeImportCount += file.metrics.coupling.relativeImportCount;\n externalImportCount += file.metrics.coupling.externalImportCount;\n exportCount += file.metrics.coupling.exportCount;\n cohesionTotal += file.metrics.cohesion.averageFunctionIdentifierOverlap;\n typeAnnotationCount += file.metrics.typeComplexity.typeAnnotationCount;\n typeAliasCount += file.metrics.typeComplexity.typeAliasCount;\n interfaceCount += file.metrics.typeComplexity.interfaceCount;\n genericParameterCount += file.metrics.typeComplexity.genericParameterCount;\n }\n\n return {\n fileCount: files.length,\n functionCount,\n linesOfCode,\n maxCyclomaticComplexity,\n maxCognitiveComplexity,\n callCount,\n internalCallCount,\n maxCallDepth,\n importSourceCount,\n relativeImportCount,\n externalImportCount,\n exportCount,\n averageFunctionIdentifierOverlap: files.length === 0 ? 0 : cohesionTotal / files.length,\n typeAnnotationCount,\n typeAliasCount,\n interfaceCount,\n genericParameterCount,\n };\n}\n\nfunction shouldSkipDirectory(name: string, options: ResolvedOptions): boolean {\n if (ignoredDirectoryNames.has(name)) {\n return true;\n }\n\n if (options.includeTests) {\n return false;\n }\n\n return testDirectoryNames.has(name);\n}\n\nfunction isWithinDirectory(candidate: string, directory: string): boolean {\n const relative = path.relative(directory, candidate);\n return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));\n}\n\nfunction getLanguage(file: string, options: ResolvedOptions, explicitTarget = false): LanguageName | undefined {\n const lowerFile = file.toLowerCase();\n if (\n !explicitTarget &&\n (lowerFile.endsWith('.d.ts') ||\n lowerFile.endsWith('.d.mts') ||\n lowerFile.endsWith('.d.cts') ||\n lowerFile.endsWith('.min.js') ||\n lowerFile.endsWith('.pnp.cjs'))\n ) {\n return undefined;\n }\n\n if (!explicitTarget && !options.includeTests && testFilePattern.test(path.basename(file))) {\n return undefined;\n }\n\n return languageByExtension.get(path.extname(lowerFile));\n}\n\nfunction parsePositiveInteger(value: string): number {\n if (!/^[1-9]\\d*$/u.test(value)) {\n throw new InvalidArgumentError('Expected a positive integer.');\n }\n\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 1) {\n throw new InvalidArgumentError('Expected a positive integer.');\n }\n return parsed;\n}\n\nfunction formatPath(file: string, base: string): string {\n return path.relative(base, file) || path.basename(file);\n}\n\nfunction writeStdout(message: string): void {\n process.stdout.write(message);\n}\n\nfunction writeStderr(message: string): void {\n process.stderr.write(message);\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"names":["languageByExtension","Map","ignoredDirectoryNames","Set","testDirectoryNames","testFilePattern","resolveTarget","target","os","homedir","startsWith","path","join","slice","resolve","async","fileExists","file","stat","isFile","scanDirectory","directory","options","files","errors","visitedDirectories","visitedFiles","rootDirectory","resolvedDirectory","entries","realpath","error","push","formatPath","formatError","isWithinDirectory","has","add","readdir","withFileTypes","entry","entryPath","name","isSymbolicLink","scanSymbolicLink","isDirectory","shouldSkipDirectory","measureScannableFile","resolvedPath","entryStat","basename","displayRoot","languageFile","realFile","language","getLanguage","measureFile","resolvedFile","code","readFile","metrics","measureCode","findRiskyFileMetrics","architecture","thresholds","triggers","formattedFile","addTrigger","lines","fileLoc","coupling","importSourceCount","import","duplication","duplicateBlockCount","duplicateBlock","directLocalDependencyCount","transitiveLocalDependencyCount","transitiveDependency","length","structuralCoordination","score","structuralBreadthScore","structuralBreadth","stateMutationScore","stateMutation","duplicateSymbolGroupCount","duplicateSymbolGroup","kind","cyclomaticComplexity","cognitiveComplexity","maxTriggerScore","metric","value","threshold","isReactComponent","fn","componentFunctionKeys","namedComponentFunctionKeys","functionLocationKey","startLine","startColumn","functionNameLocationKey","Math","max","map","trigger","compareRiskFindings","left","right","localeCompare","endLine","formatRiskLocation","risk","undefined","formatRiskName","formatRiskMetrics","formatMetricValue","Number","isInteger","String","toFixed","summarize","functionCount","linesOfCode","maxCyclomaticComplexity","maxCognitiveComplexity","callCount","internalCallCount","maxCallDepth","relativeImportCount","externalImportCount","exportCount","cohesionTotal","typeAnnotationCount","typeAliasCount","interfaceCount","genericParameterCount","callGraph","cohesion","averageFunctionIdentifierOverlap","typeComplexity","fileCount","includeTests","candidate","relative","sep","isAbsolute","explicitTarget","lowerFile","toLowerCase","endsWith","test","get","extname","parsePositiveInteger","InvalidArgumentError","parsed","isSafeInteger","base","writeStdout","message","process","stdout","write","writeStderr","stderr","Error","program","Command","description","argument","option","configFileName","action","cliOptions","resolvedTarget","config","loadConfig","dirname","configSearchDirectory","resolveOptions","result","canonicalTarget","fallbackDisplayRoot","targetStat","fatalError","scanTarget","measureArchitecture","addArchitectureMetrics","explicitConfigFile","tsconfig","isExplicitConfig","some","includes","isTypeScriptProjectCandidateFile","configFile","currentDirectory","parentDirectory","findNearestTsconfig","typeScriptProject","measureTypeScriptProject","reactComponentFunctions","component","flatMap","addTypeScriptProjectMetrics","risks","architectureByFile","findings","isReactFile","functions","returnsJsx","resolveThresholds","loc","isComponent","cognitive","cyclomatic","componentLoc","functionLoc","getLocThreshold","call","fanOut","parameterCount","parameter","findRiskyFunctionMetrics","sort","findRiskyFunctions","json","summary","reportedRisks","maxFindings","JSON","stringify","profileThresholds","totalRisks","truncated","printJson","maxStateMutationScore","maxTransitiveLocalDependencyCount","maxStructuralBreadthScore","maxStructuralCoordinationScore","duplicateSymbolGroups","formatArchitectureMetrics","rootFileCount","measuredRootFileCount","semanticDiagnosticCount","resolvedCallExpressionCount","callExpressionCount","resolvedCallExpressionRatio","profileOverrides","Object","profile","overrides","totalSuffix","printTextReport","failOnError","failOnRisk","exitCode","parseAsync","main","catch"],"mappings":"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"}
|
package/dist/cliConfig.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var o=require("node:fs/promises"),e=require("node:path");const r={fileLoc:500,functionLoc:120,componentLoc:350,cognitive:25,cyclomatic:20,call:50,import:25,fanOut:10,parameter:8,duplicateBlock:2,transitiveDependency:25,structuralBreadth:8,structuralCoordination:300,stateMutation:50,duplicateSymbolGroup:5},t="code-gauge.config.json",n=["javascript","jsx","typescript","tsx","python","go","react"],i={python:{stateMutation:90,structuralCoordination:350},react:{import:30}};const s={fileLoc:"fileLocThreshold",functionLoc:"functionLocThreshold",componentLoc:"componentLocThreshold",cognitive:"cognitiveThreshold",cyclomatic:"cyclomaticThreshold",call:"callThreshold",import:"importThreshold",fanOut:"fanOutThreshold",parameter:"parameterThreshold",duplicateBlock:"duplicateBlockThreshold",transitiveDependency:"transitiveDependencyThreshold",structuralBreadth:"structuralBreadthThreshold",structuralCoordination:"structuralCoordinationThreshold",stateMutation:"stateMutationThreshold",duplicateSymbolGroup:"duplicateSymbolGroupThreshold"};function a(o,e){const r={};for(const t of n){const n={...o[t],...e?.[t]};Object.keys(n).length>0&&(r[t]=n)}return r}async function l(e){try{return(await o.stat(e)).isFile()}catch{return!1}}function c(o,e,t){if("object"!=typeof o||null===o||Array.isArray(o))throw new Error(`Config file "${t}": "${e}" must be an object.`);const n={};for(const[i,s]of Object.entries(o)){if(!(i in r))throw new Error(`Config file "${t}": unknown threshold "${i}" in "${e}".`);n[i]=f(s,`${e}.${i}`,t)}return n}function f(o,e,r){if("number"!=typeof o||!Number.isSafeInteger(o)||o<1)throw new Error(`Config file "${r}": "${e}" must be a positive integer.`);return o}function h(o,e,r){if("boolean"!=typeof o)throw new TypeError(`Config file "${r}": "${e}" must be a boolean.`);return o}function u(o){return o instanceof Error?o.message:String(o)}exports.configFileName=t,exports.defaultMaxFindings=20,exports.defaultProfileThresholds=i,exports.defaultThresholds=r,exports.loadConfig=async function(r,i){const s=r??await async function(o){let r=o;for(;;){const o=e.join(r,t);if(await l(o))return o;const n=e.dirname(r);if(n===r)return;r=n}}(i);if(!s)return{};let a,d;try{a=await o.readFile(s,"utf8")}catch(o){if(r)throw new Error(`Cannot read config file "${s}": ${u(o)}`);return{}}try{d=JSON.parse(a)}catch(o){throw new Error(`Invalid JSON in config file "${s}": ${u(o)}`)}return function(o,e){if("object"!=typeof o||null===o||Array.isArray(o))throw new Error(`Config file "${e}" must contain a JSON object.`);const r=o,t={};void 0!==r.thresholds&&(t.thresholds=c(r.thresholds,"thresholds",e));if(void 0!==r.languageThresholds){if("object"!=typeof r.languageThresholds||null===r.languageThresholds||Array.isArray(r.languageThresholds))throw new Error(`Config file "${e}": "languageThresholds" must be an object.`);const o={};for(const[t,i]of Object.entries(r.languageThresholds)){if(!n.includes(t))throw new Error(`Config file "${e}": unknown language profile "${t}" (expected one of ${n.join(", ")}).`);o[t]=c(i,`languageThresholds.${t}`,e)}t.languageThresholds=o}void 0!==r.maxFindings&&(t.maxFindings=f(r.maxFindings,"maxFindings",e));for(const o of["includeTests","failOnRisk","failOnError"])void 0!==r[o]&&(t[o]=h(r[o],o,e));if(void 0!==r.tsconfig){if("string"!=typeof r.tsconfig)throw new TypeError(`Config file "${e}": "tsconfig" must be a string.`);t.tsconfig=r.tsconfig}return t}(d,s)},exports.profileKeys=n,exports.resolveOptions=function(o,e){const t={...r};for(const r of Object.keys(t))t[r]=o[s[r]]??e.thresholds?.[r]??t[r];return{thresholds:t,profileThresholds:a(i,e.languageThresholds),maxFindings:o.maxFindings??e.maxFindings??20,includeTests:o.includeTests??e.includeTests??!1,failOnRisk:o.failOnRisk??e.failOnRisk??!1,failOnError:o.failOnError??e.failOnError??!1,json:o.json??!1,tsconfig:o.tsconfig??e.tsconfig}},exports.resolveThresholds=function(o,e,r){let t=o.thresholds;const n=o.profileThresholds[e];return n&&(t={...t,...n}),r&&o.profileThresholds.react&&(t={...t,...o.profileThresholds.react}),t};
|
|
1
|
+
"use strict";var o=require("node:fs/promises"),e=require("node:path");const r={fileLoc:500,functionLoc:120,componentLoc:350,cognitive:25,cyclomatic:20,call:50,import:25,fanOut:10,parameter:8,duplicateBlock:2,transitiveDependency:25,structuralBreadth:8,structuralCoordination:300,stateMutation:50,duplicateSymbolGroup:5},t="code-gauge.config.json",n=["javascript","jsx","typescript","tsx","python","go","rust","react"],i={python:{stateMutation:90,structuralCoordination:350},react:{import:30}};const s={fileLoc:"fileLocThreshold",functionLoc:"functionLocThreshold",componentLoc:"componentLocThreshold",cognitive:"cognitiveThreshold",cyclomatic:"cyclomaticThreshold",call:"callThreshold",import:"importThreshold",fanOut:"fanOutThreshold",parameter:"parameterThreshold",duplicateBlock:"duplicateBlockThreshold",transitiveDependency:"transitiveDependencyThreshold",structuralBreadth:"structuralBreadthThreshold",structuralCoordination:"structuralCoordinationThreshold",stateMutation:"stateMutationThreshold",duplicateSymbolGroup:"duplicateSymbolGroupThreshold"};function a(o,e){const r={};for(const t of n){const n={...o[t],...e?.[t]};Object.keys(n).length>0&&(r[t]=n)}return r}async function l(e){try{return(await o.stat(e)).isFile()}catch{return!1}}function c(o,e,t){if("object"!=typeof o||null===o||Array.isArray(o))throw new Error(`Config file "${t}": "${e}" must be an object.`);const n={};for(const[i,s]of Object.entries(o)){if(!(i in r))throw new Error(`Config file "${t}": unknown threshold "${i}" in "${e}".`);n[i]=f(s,`${e}.${i}`,t)}return n}function f(o,e,r){if("number"!=typeof o||!Number.isSafeInteger(o)||o<1)throw new Error(`Config file "${r}": "${e}" must be a positive integer.`);return o}function h(o,e,r){if("boolean"!=typeof o)throw new TypeError(`Config file "${r}": "${e}" must be a boolean.`);return o}function u(o){return o instanceof Error?o.message:String(o)}exports.configFileName=t,exports.defaultMaxFindings=20,exports.defaultProfileThresholds=i,exports.defaultThresholds=r,exports.loadConfig=async function(r,i){const s=r??await async function(o){let r=o;for(;;){const o=e.join(r,t);if(await l(o))return o;const n=e.dirname(r);if(n===r)return;r=n}}(i);if(!s)return{};let a,d;try{a=await o.readFile(s,"utf8")}catch(o){if(r)throw new Error(`Cannot read config file "${s}": ${u(o)}`);return{}}try{d=JSON.parse(a)}catch(o){throw new Error(`Invalid JSON in config file "${s}": ${u(o)}`)}return function(o,e){if("object"!=typeof o||null===o||Array.isArray(o))throw new Error(`Config file "${e}" must contain a JSON object.`);const r=o,t={};void 0!==r.thresholds&&(t.thresholds=c(r.thresholds,"thresholds",e));if(void 0!==r.languageThresholds){if("object"!=typeof r.languageThresholds||null===r.languageThresholds||Array.isArray(r.languageThresholds))throw new Error(`Config file "${e}": "languageThresholds" must be an object.`);const o={};for(const[t,i]of Object.entries(r.languageThresholds)){if(!n.includes(t))throw new Error(`Config file "${e}": unknown language profile "${t}" (expected one of ${n.join(", ")}).`);o[t]=c(i,`languageThresholds.${t}`,e)}t.languageThresholds=o}void 0!==r.maxFindings&&(t.maxFindings=f(r.maxFindings,"maxFindings",e));for(const o of["includeTests","failOnRisk","failOnError"])void 0!==r[o]&&(t[o]=h(r[o],o,e));if(void 0!==r.tsconfig){if("string"!=typeof r.tsconfig)throw new TypeError(`Config file "${e}": "tsconfig" must be a string.`);t.tsconfig=r.tsconfig}return t}(d,s)},exports.profileKeys=n,exports.resolveOptions=function(o,e){const t={...r};for(const r of Object.keys(t))t[r]=o[s[r]]??e.thresholds?.[r]??t[r];return{thresholds:t,profileThresholds:a(i,e.languageThresholds),maxFindings:o.maxFindings??e.maxFindings??20,includeTests:o.includeTests??e.includeTests??!1,failOnRisk:o.failOnRisk??e.failOnRisk??!1,failOnError:o.failOnError??e.failOnError??!1,json:o.json??!1,tsconfig:o.tsconfig??e.tsconfig}},exports.resolveThresholds=function(o,e,r){let t=o.thresholds;const n=o.profileThresholds[e];return n&&(t={...t,...n}),r&&o.profileThresholds.react&&(t={...t,...o.profileThresholds.react}),t};
|
|
2
2
|
//# sourceMappingURL=cliConfig.cjs.map
|
package/dist/cliConfig.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cliConfig.cjs","sources":["../src/cliConfig.ts"],"sourcesContent":["import { readFile, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\n/** Risk thresholds; a finding is reported when the measured value is greater than or equal to the threshold. */\nexport interface Thresholds {\n fileLoc: number;\n functionLoc: number;\n componentLoc: number;\n cognitive: number;\n cyclomatic: number;\n call: number;\n import: number;\n fanOut: number;\n parameter: number;\n duplicateBlock: number;\n transitiveDependency: number;\n structuralBreadth: number;\n structuralCoordination: number;\n stateMutation: number;\n duplicateSymbolGroup: number;\n}\n\n// Defaults tuned against blind human labels across five representative WillBooster/WillBoosterLab\n// repositories to maximize F1 (precision without sacrificing recall); see PR for the evaluation.\nexport const defaultThresholds: Thresholds = {\n fileLoc: 500,\n functionLoc: 120,\n componentLoc: 350,\n cognitive: 25,\n cyclomatic: 20,\n call: 50,\n import: 25,\n fanOut: 10,\n parameter: 8,\n duplicateBlock: 2,\n transitiveDependency: 25,\n structuralBreadth: 8,\n structuralCoordination: 300,\n stateMutation: 50,\n duplicateSymbolGroup: 5,\n};\n\nexport const defaultMaxFindings = 20;\nexport const configFileName = 'code-gauge.config.json';\n\n/**\n * Profile keys for per-language and React-specific threshold overrides. A file resolves its\n * thresholds as base → its language profile → the `react` profile (when it contains a component).\n */\nexport const profileKeys = ['javascript', 'jsx', 'typescript', 'tsx', 'python', 'go', 'react'] as const;\nexport type ProfileKey = (typeof profileKeys)[number];\n\n/**\n * Built-in per-profile overrides, calibrated because some metric distributions differ sharply by\n * language/type: Python treats every binding as an assignment (so `stateMutation` runs ~10x higher)\n * and coordinates more per file, while React files import roughly twice as many sources as pure TS.\n */\nexport const defaultProfileThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>> = {\n python: { stateMutation: 90, structuralCoordination: 350 },\n react: { import: 30 },\n};\n\n/** Shape of the JSON configuration file. All fields are optional and fall back to the built-in defaults. */\nexport interface CodeGaugeConfig {\n thresholds?: Partial<Thresholds>;\n /** Per-profile overrides keyed by language name or `react`; merged over `thresholds` for matching files. */\n languageThresholds?: Partial<Record<ProfileKey, Partial<Thresholds>>>;\n maxFindings?: number;\n includeTests?: boolean;\n failOnRisk?: boolean;\n failOnError?: boolean;\n tsconfig?: string;\n}\n\n/** Options after merging command-line flags, the configuration file, and the built-in defaults. */\nexport interface ResolvedOptions {\n thresholds: Thresholds;\n profileThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>>;\n maxFindings: number;\n includeTests: boolean;\n failOnRisk: boolean;\n failOnError: boolean;\n json: boolean;\n tsconfig?: string;\n}\n\n/**\n * Resolves the thresholds for a single file: the base thresholds overlaid with its language profile\n * and then, when the file contains a React component, the `react` profile.\n */\nexport function resolveThresholds(options: ResolvedOptions, language: string, isReact: boolean): Thresholds {\n let thresholds = options.thresholds;\n const languageOverride = options.profileThresholds[language as ProfileKey];\n if (languageOverride) {\n thresholds = { ...thresholds, ...languageOverride };\n }\n if (isReact && options.profileThresholds.react) {\n thresholds = { ...thresholds, ...options.profileThresholds.react };\n }\n return thresholds;\n}\n\n/** Raw command-line options; every threshold is undefined unless the user passed the flag. */\nexport interface CliOptions {\n config?: string;\n fileLocThreshold?: number;\n functionLocThreshold?: number;\n componentLocThreshold?: number;\n cognitiveThreshold?: number;\n cyclomaticThreshold?: number;\n callThreshold?: number;\n importThreshold?: number;\n fanOutThreshold?: number;\n parameterThreshold?: number;\n duplicateBlockThreshold?: number;\n transitiveDependencyThreshold?: number;\n structuralBreadthThreshold?: number;\n structuralCoordinationThreshold?: number;\n stateMutationThreshold?: number;\n duplicateSymbolGroupThreshold?: number;\n maxFindings?: number;\n includeTests?: boolean;\n failOnRisk?: boolean;\n failOnError?: boolean;\n json?: boolean;\n tsconfig?: string;\n}\n\n/** Maps each threshold to the matching command-line flag; the config key equals the flag without the `-threshold` suffix. */\nconst thresholdCliKeys: Record<keyof Thresholds, keyof CliOptions> = {\n fileLoc: 'fileLocThreshold',\n functionLoc: 'functionLocThreshold',\n componentLoc: 'componentLocThreshold',\n cognitive: 'cognitiveThreshold',\n cyclomatic: 'cyclomaticThreshold',\n call: 'callThreshold',\n import: 'importThreshold',\n fanOut: 'fanOutThreshold',\n parameter: 'parameterThreshold',\n duplicateBlock: 'duplicateBlockThreshold',\n transitiveDependency: 'transitiveDependencyThreshold',\n structuralBreadth: 'structuralBreadthThreshold',\n structuralCoordination: 'structuralCoordinationThreshold',\n stateMutation: 'stateMutationThreshold',\n duplicateSymbolGroup: 'duplicateSymbolGroupThreshold',\n};\n\n/** Resolves options with precedence command-line flags > configuration file > built-in defaults. */\nexport function resolveOptions(cli: CliOptions, config: CodeGaugeConfig): ResolvedOptions {\n const thresholds = { ...defaultThresholds };\n for (const key of Object.keys(thresholds) as (keyof Thresholds)[]) {\n thresholds[key] = (cli[thresholdCliKeys[key]] as number | undefined) ?? config.thresholds?.[key] ?? thresholds[key];\n }\n\n return {\n thresholds,\n profileThresholds: mergeProfileThresholds(defaultProfileThresholds, config.languageThresholds),\n maxFindings: cli.maxFindings ?? config.maxFindings ?? defaultMaxFindings,\n includeTests: cli.includeTests ?? config.includeTests ?? false,\n failOnRisk: cli.failOnRisk ?? config.failOnRisk ?? false,\n failOnError: cli.failOnError ?? config.failOnError ?? false,\n json: cli.json ?? false,\n tsconfig: cli.tsconfig ?? config.tsconfig,\n };\n}\n\n/** Merges user-supplied per-profile overrides on top of the built-in ones, per profile. */\nfunction mergeProfileThresholds(\n defaults: Partial<Record<ProfileKey, Partial<Thresholds>>>,\n overrides: Partial<Record<ProfileKey, Partial<Thresholds>>> | undefined\n): Partial<Record<ProfileKey, Partial<Thresholds>>> {\n const merged: Partial<Record<ProfileKey, Partial<Thresholds>>> = {};\n for (const key of profileKeys) {\n const combined = { ...defaults[key], ...overrides?.[key] };\n if (Object.keys(combined).length > 0) {\n merged[key] = combined;\n }\n }\n return merged;\n}\n\n/**\n * Loads the configuration file. An explicit path must exist; otherwise the nearest\n * `code-gauge.config.json` is searched by walking up from the target directory.\n */\nexport async function loadConfig(explicitPath: string | undefined, targetDirectory: string): Promise<CodeGaugeConfig> {\n const configFile = explicitPath ?? (await findNearestConfig(targetDirectory));\n if (!configFile) {\n return {};\n }\n\n let content;\n try {\n content = await readFile(configFile, 'utf8');\n } catch (error) {\n if (explicitPath) {\n throw new Error(`Cannot read config file \"${configFile}\": ${formatError(error)}`);\n }\n return {};\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (error) {\n throw new Error(`Invalid JSON in config file \"${configFile}\": ${formatError(error)}`);\n }\n\n return validateConfig(parsed, configFile);\n}\n\nasync function findNearestConfig(targetDirectory: string): Promise<string | undefined> {\n let currentDirectory = targetDirectory;\n while (true) {\n const configFile = path.join(currentDirectory, configFileName);\n if (await fileExists(configFile)) {\n return configFile;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n try {\n const fileStat = await stat(file);\n return fileStat.isFile();\n } catch {\n return false;\n }\n}\n\nfunction validateConfig(value: unknown, configFile: string): CodeGaugeConfig {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Config file \"${configFile}\" must contain a JSON object.`);\n }\n\n const raw = value as Record<string, unknown>;\n const config: CodeGaugeConfig = {};\n\n if (raw.thresholds !== undefined) {\n config.thresholds = validateThresholdObject(raw.thresholds, 'thresholds', configFile);\n }\n\n if (raw.languageThresholds !== undefined) {\n if (\n typeof raw.languageThresholds !== 'object' ||\n raw.languageThresholds === null ||\n Array.isArray(raw.languageThresholds)\n ) {\n throw new Error(`Config file \"${configFile}\": \"languageThresholds\" must be an object.`);\n }\n const languageThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>> = {};\n for (const [profile, thresholds] of Object.entries(raw.languageThresholds as Record<string, unknown>)) {\n if (!(profileKeys as readonly string[]).includes(profile)) {\n throw new Error(\n `Config file \"${configFile}\": unknown language profile \"${profile}\" (expected one of ${profileKeys.join(', ')}).`\n );\n }\n languageThresholds[profile as ProfileKey] = validateThresholdObject(\n thresholds,\n `languageThresholds.${profile}`,\n configFile\n );\n }\n config.languageThresholds = languageThresholds;\n }\n\n if (raw.maxFindings !== undefined) {\n config.maxFindings = requirePositiveInteger(raw.maxFindings, 'maxFindings', configFile);\n }\n for (const key of ['includeTests', 'failOnRisk', 'failOnError'] as const) {\n if (raw[key] !== undefined) {\n config[key] = requireBoolean(raw[key], key, configFile);\n }\n }\n if (raw.tsconfig !== undefined) {\n if (typeof raw.tsconfig !== 'string') {\n throw new TypeError(`Config file \"${configFile}\": \"tsconfig\" must be a string.`);\n }\n config.tsconfig = raw.tsconfig;\n }\n\n return config;\n}\n\nfunction validateThresholdObject(value: unknown, label: string, configFile: string): Partial<Thresholds> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Config file \"${configFile}\": \"${label}\" must be an object.`);\n }\n const thresholds: Partial<Thresholds> = {};\n for (const [key, threshold] of Object.entries(value as Record<string, unknown>)) {\n if (!(key in defaultThresholds)) {\n throw new Error(`Config file \"${configFile}\": unknown threshold \"${key}\" in \"${label}\".`);\n }\n thresholds[key as keyof Thresholds] = requirePositiveInteger(threshold, `${label}.${key}`, configFile);\n }\n return thresholds;\n}\n\nfunction requirePositiveInteger(value: unknown, key: string, configFile: string): number {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1) {\n throw new Error(`Config file \"${configFile}\": \"${key}\" must be a positive integer.`);\n }\n return value;\n}\n\nfunction requireBoolean(value: unknown, key: string, configFile: string): boolean {\n if (typeof value !== 'boolean') {\n throw new TypeError(`Config file \"${configFile}\": \"${key}\" must be a boolean.`);\n }\n return value;\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"names":["defaultThresholds","fileLoc","functionLoc","componentLoc","cognitive","cyclomatic","call","import","fanOut","parameter","duplicateBlock","transitiveDependency","structuralBreadth","structuralCoordination","stateMutation","duplicateSymbolGroup","configFileName","profileKeys","defaultProfileThresholds","python","react","thresholdCliKeys","mergeProfileThresholds","defaults","overrides","merged","key","combined","Object","keys","length","async","fileExists","file","stat","isFile","validateThresholdObject","value","label","configFile","Array","isArray","Error","thresholds","threshold","entries","requirePositiveInteger","Number","isSafeInteger","requireBoolean","TypeError","formatError","error","message","String","explicitPath","targetDirectory","currentDirectory","path","join","parentDirectory","dirname","findNearestConfig","content","parsed","readFile","JSON","parse","raw","config","undefined","languageThresholds","profile","includes","maxFindings","tsconfig","validateConfig","cli","profileThresholds","includeTests","failOnRisk","failOnError","json","options","language","isReact","languageOverride"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cliConfig.cjs","sources":["../src/cliConfig.ts"],"sourcesContent":["import { readFile, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\n/** Risk thresholds; a finding is reported when the measured value is greater than or equal to the threshold. */\nexport interface Thresholds {\n fileLoc: number;\n functionLoc: number;\n componentLoc: number;\n cognitive: number;\n cyclomatic: number;\n call: number;\n import: number;\n fanOut: number;\n parameter: number;\n duplicateBlock: number;\n transitiveDependency: number;\n structuralBreadth: number;\n structuralCoordination: number;\n stateMutation: number;\n duplicateSymbolGroup: number;\n}\n\n// Defaults tuned against blind human labels across five representative WillBooster/WillBoosterLab\n// repositories to maximize F1 (precision without sacrificing recall); see PR for the evaluation.\nexport const defaultThresholds: Thresholds = {\n fileLoc: 500,\n functionLoc: 120,\n componentLoc: 350,\n cognitive: 25,\n cyclomatic: 20,\n call: 50,\n import: 25,\n fanOut: 10,\n parameter: 8,\n duplicateBlock: 2,\n transitiveDependency: 25,\n structuralBreadth: 8,\n structuralCoordination: 300,\n stateMutation: 50,\n duplicateSymbolGroup: 5,\n};\n\nexport const defaultMaxFindings = 20;\nexport const configFileName = 'code-gauge.config.json';\n\n/**\n * Profile keys for per-language and React-specific threshold overrides. A file resolves its\n * thresholds as base → its language profile → the `react` profile (when it contains a component).\n */\nexport const profileKeys = ['javascript', 'jsx', 'typescript', 'tsx', 'python', 'go', 'rust', 'react'] as const;\nexport type ProfileKey = (typeof profileKeys)[number];\n\n/**\n * Built-in per-profile overrides, calibrated because some metric distributions differ sharply by\n * language/type: Python treats every binding as an assignment (so `stateMutation` runs ~10x higher)\n * and coordinates more per file, while React files import roughly twice as many sources as pure TS.\n */\nexport const defaultProfileThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>> = {\n python: { stateMutation: 90, structuralCoordination: 350 },\n react: { import: 30 },\n};\n\n/** Shape of the JSON configuration file. All fields are optional and fall back to the built-in defaults. */\nexport interface CodeGaugeConfig {\n thresholds?: Partial<Thresholds>;\n /** Per-profile overrides keyed by language name or `react`; merged over `thresholds` for matching files. */\n languageThresholds?: Partial<Record<ProfileKey, Partial<Thresholds>>>;\n maxFindings?: number;\n includeTests?: boolean;\n failOnRisk?: boolean;\n failOnError?: boolean;\n tsconfig?: string;\n}\n\n/** Options after merging command-line flags, the configuration file, and the built-in defaults. */\nexport interface ResolvedOptions {\n thresholds: Thresholds;\n profileThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>>;\n maxFindings: number;\n includeTests: boolean;\n failOnRisk: boolean;\n failOnError: boolean;\n json: boolean;\n tsconfig?: string;\n}\n\n/**\n * Resolves the thresholds for a single file: the base thresholds overlaid with its language profile\n * and then, when the file contains a React component, the `react` profile.\n */\nexport function resolveThresholds(options: ResolvedOptions, language: string, isReact: boolean): Thresholds {\n let thresholds = options.thresholds;\n const languageOverride = options.profileThresholds[language as ProfileKey];\n if (languageOverride) {\n thresholds = { ...thresholds, ...languageOverride };\n }\n if (isReact && options.profileThresholds.react) {\n thresholds = { ...thresholds, ...options.profileThresholds.react };\n }\n return thresholds;\n}\n\n/** Raw command-line options; every threshold is undefined unless the user passed the flag. */\nexport interface CliOptions {\n config?: string;\n fileLocThreshold?: number;\n functionLocThreshold?: number;\n componentLocThreshold?: number;\n cognitiveThreshold?: number;\n cyclomaticThreshold?: number;\n callThreshold?: number;\n importThreshold?: number;\n fanOutThreshold?: number;\n parameterThreshold?: number;\n duplicateBlockThreshold?: number;\n transitiveDependencyThreshold?: number;\n structuralBreadthThreshold?: number;\n structuralCoordinationThreshold?: number;\n stateMutationThreshold?: number;\n duplicateSymbolGroupThreshold?: number;\n maxFindings?: number;\n includeTests?: boolean;\n failOnRisk?: boolean;\n failOnError?: boolean;\n json?: boolean;\n tsconfig?: string;\n}\n\n/** Maps each threshold to the matching command-line flag; the config key equals the flag without the `-threshold` suffix. */\nconst thresholdCliKeys: Record<keyof Thresholds, keyof CliOptions> = {\n fileLoc: 'fileLocThreshold',\n functionLoc: 'functionLocThreshold',\n componentLoc: 'componentLocThreshold',\n cognitive: 'cognitiveThreshold',\n cyclomatic: 'cyclomaticThreshold',\n call: 'callThreshold',\n import: 'importThreshold',\n fanOut: 'fanOutThreshold',\n parameter: 'parameterThreshold',\n duplicateBlock: 'duplicateBlockThreshold',\n transitiveDependency: 'transitiveDependencyThreshold',\n structuralBreadth: 'structuralBreadthThreshold',\n structuralCoordination: 'structuralCoordinationThreshold',\n stateMutation: 'stateMutationThreshold',\n duplicateSymbolGroup: 'duplicateSymbolGroupThreshold',\n};\n\n/** Resolves options with precedence command-line flags > configuration file > built-in defaults. */\nexport function resolveOptions(cli: CliOptions, config: CodeGaugeConfig): ResolvedOptions {\n const thresholds = { ...defaultThresholds };\n for (const key of Object.keys(thresholds) as (keyof Thresholds)[]) {\n thresholds[key] = (cli[thresholdCliKeys[key]] as number | undefined) ?? config.thresholds?.[key] ?? thresholds[key];\n }\n\n return {\n thresholds,\n profileThresholds: mergeProfileThresholds(defaultProfileThresholds, config.languageThresholds),\n maxFindings: cli.maxFindings ?? config.maxFindings ?? defaultMaxFindings,\n includeTests: cli.includeTests ?? config.includeTests ?? false,\n failOnRisk: cli.failOnRisk ?? config.failOnRisk ?? false,\n failOnError: cli.failOnError ?? config.failOnError ?? false,\n json: cli.json ?? false,\n tsconfig: cli.tsconfig ?? config.tsconfig,\n };\n}\n\n/** Merges user-supplied per-profile overrides on top of the built-in ones, per profile. */\nfunction mergeProfileThresholds(\n defaults: Partial<Record<ProfileKey, Partial<Thresholds>>>,\n overrides: Partial<Record<ProfileKey, Partial<Thresholds>>> | undefined\n): Partial<Record<ProfileKey, Partial<Thresholds>>> {\n const merged: Partial<Record<ProfileKey, Partial<Thresholds>>> = {};\n for (const key of profileKeys) {\n const combined = { ...defaults[key], ...overrides?.[key] };\n if (Object.keys(combined).length > 0) {\n merged[key] = combined;\n }\n }\n return merged;\n}\n\n/**\n * Loads the configuration file. An explicit path must exist; otherwise the nearest\n * `code-gauge.config.json` is searched by walking up from the target directory.\n */\nexport async function loadConfig(explicitPath: string | undefined, targetDirectory: string): Promise<CodeGaugeConfig> {\n const configFile = explicitPath ?? (await findNearestConfig(targetDirectory));\n if (!configFile) {\n return {};\n }\n\n let content;\n try {\n content = await readFile(configFile, 'utf8');\n } catch (error) {\n if (explicitPath) {\n throw new Error(`Cannot read config file \"${configFile}\": ${formatError(error)}`);\n }\n return {};\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (error) {\n throw new Error(`Invalid JSON in config file \"${configFile}\": ${formatError(error)}`);\n }\n\n return validateConfig(parsed, configFile);\n}\n\nasync function findNearestConfig(targetDirectory: string): Promise<string | undefined> {\n let currentDirectory = targetDirectory;\n while (true) {\n const configFile = path.join(currentDirectory, configFileName);\n if (await fileExists(configFile)) {\n return configFile;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n try {\n const fileStat = await stat(file);\n return fileStat.isFile();\n } catch {\n return false;\n }\n}\n\nfunction validateConfig(value: unknown, configFile: string): CodeGaugeConfig {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Config file \"${configFile}\" must contain a JSON object.`);\n }\n\n const raw = value as Record<string, unknown>;\n const config: CodeGaugeConfig = {};\n\n if (raw.thresholds !== undefined) {\n config.thresholds = validateThresholdObject(raw.thresholds, 'thresholds', configFile);\n }\n\n if (raw.languageThresholds !== undefined) {\n if (\n typeof raw.languageThresholds !== 'object' ||\n raw.languageThresholds === null ||\n Array.isArray(raw.languageThresholds)\n ) {\n throw new Error(`Config file \"${configFile}\": \"languageThresholds\" must be an object.`);\n }\n const languageThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>> = {};\n for (const [profile, thresholds] of Object.entries(raw.languageThresholds as Record<string, unknown>)) {\n if (!(profileKeys as readonly string[]).includes(profile)) {\n throw new Error(\n `Config file \"${configFile}\": unknown language profile \"${profile}\" (expected one of ${profileKeys.join(', ')}).`\n );\n }\n languageThresholds[profile as ProfileKey] = validateThresholdObject(\n thresholds,\n `languageThresholds.${profile}`,\n configFile\n );\n }\n config.languageThresholds = languageThresholds;\n }\n\n if (raw.maxFindings !== undefined) {\n config.maxFindings = requirePositiveInteger(raw.maxFindings, 'maxFindings', configFile);\n }\n for (const key of ['includeTests', 'failOnRisk', 'failOnError'] as const) {\n if (raw[key] !== undefined) {\n config[key] = requireBoolean(raw[key], key, configFile);\n }\n }\n if (raw.tsconfig !== undefined) {\n if (typeof raw.tsconfig !== 'string') {\n throw new TypeError(`Config file \"${configFile}\": \"tsconfig\" must be a string.`);\n }\n config.tsconfig = raw.tsconfig;\n }\n\n return config;\n}\n\nfunction validateThresholdObject(value: unknown, label: string, configFile: string): Partial<Thresholds> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Config file \"${configFile}\": \"${label}\" must be an object.`);\n }\n const thresholds: Partial<Thresholds> = {};\n for (const [key, threshold] of Object.entries(value as Record<string, unknown>)) {\n if (!(key in defaultThresholds)) {\n throw new Error(`Config file \"${configFile}\": unknown threshold \"${key}\" in \"${label}\".`);\n }\n thresholds[key as keyof Thresholds] = requirePositiveInteger(threshold, `${label}.${key}`, configFile);\n }\n return thresholds;\n}\n\nfunction requirePositiveInteger(value: unknown, key: string, configFile: string): number {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1) {\n throw new Error(`Config file \"${configFile}\": \"${key}\" must be a positive integer.`);\n }\n return value;\n}\n\nfunction requireBoolean(value: unknown, key: string, configFile: string): boolean {\n if (typeof value !== 'boolean') {\n throw new TypeError(`Config file \"${configFile}\": \"${key}\" must be a boolean.`);\n }\n return value;\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"names":["defaultThresholds","fileLoc","functionLoc","componentLoc","cognitive","cyclomatic","call","import","fanOut","parameter","duplicateBlock","transitiveDependency","structuralBreadth","structuralCoordination","stateMutation","duplicateSymbolGroup","configFileName","profileKeys","defaultProfileThresholds","python","react","thresholdCliKeys","mergeProfileThresholds","defaults","overrides","merged","key","combined","Object","keys","length","async","fileExists","file","stat","isFile","validateThresholdObject","value","label","configFile","Array","isArray","Error","thresholds","threshold","entries","requirePositiveInteger","Number","isSafeInteger","requireBoolean","TypeError","formatError","error","message","String","explicitPath","targetDirectory","currentDirectory","path","join","parentDirectory","dirname","findNearestConfig","content","parsed","readFile","JSON","parse","raw","config","undefined","languageThresholds","profile","includes","maxFindings","tsconfig","validateConfig","cli","profileThresholds","includeTests","failOnRisk","failOnError","json","options","language","isReact","languageOverride"],"mappings":"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"}
|
package/dist/cliConfig.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const configFileName = "code-gauge.config.json";
|
|
|
23
23
|
* Profile keys for per-language and React-specific threshold overrides. A file resolves its
|
|
24
24
|
* thresholds as base → its language profile → the `react` profile (when it contains a component).
|
|
25
25
|
*/
|
|
26
|
-
export declare const profileKeys: readonly ['javascript', 'jsx', 'typescript', 'tsx', 'python', 'go', 'react'];
|
|
26
|
+
export declare const profileKeys: readonly ['javascript', 'jsx', 'typescript', 'tsx', 'python', 'go', 'rust', 'react'];
|
|
27
27
|
export type ProfileKey = (typeof profileKeys)[number];
|
|
28
28
|
/**
|
|
29
29
|
* Built-in per-profile overrides, calibrated because some metric distributions differ sharply by
|
package/dist/cliConfig.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readFile as o,stat as r}from"node:fs/promises";import t from"node:path";const n={fileLoc:500,functionLoc:120,componentLoc:350,cognitive:25,cyclomatic:20,call:50,import:25,fanOut:10,parameter:8,duplicateBlock:2,transitiveDependency:25,structuralBreadth:8,structuralCoordination:300,stateMutation:50,duplicateSymbolGroup:5},e=20,i="code-gauge.config.json",s=["javascript","jsx","typescript","tsx","python","go","react"],a={python:{stateMutation:90,structuralCoordination:350},react:{import:30}};function c(o,r,t){let n=o.thresholds;const e=o.profileThresholds[r];return e&&(n={...n,...e}),t&&o.profileThresholds.react&&(n={...n,...o.profileThresholds.react}),n}const l={fileLoc:"fileLocThreshold",functionLoc:"functionLocThreshold",componentLoc:"componentLocThreshold",cognitive:"cognitiveThreshold",cyclomatic:"cyclomaticThreshold",call:"callThreshold",import:"importThreshold",fanOut:"fanOutThreshold",parameter:"parameterThreshold",duplicateBlock:"duplicateBlockThreshold",transitiveDependency:"transitiveDependencyThreshold",structuralBreadth:"structuralBreadthThreshold",structuralCoordination:"structuralCoordinationThreshold",stateMutation:"stateMutationThreshold",duplicateSymbolGroup:"duplicateSymbolGroupThreshold"};function f(o,r){const t={...n};for(const n of Object.keys(t))t[n]=o[l[n]]??r.thresholds?.[n]??t[n];return{thresholds:t,profileThresholds:h(a,r.languageThresholds),maxFindings:o.maxFindings??r.maxFindings??20,includeTests:o.includeTests??r.includeTests??!1,failOnRisk:o.failOnRisk??r.failOnRisk??!1,failOnError:o.failOnError??r.failOnError??!1,json:o.json??!1,tsconfig:o.tsconfig??r.tsconfig}}function h(o,r){const t={};for(const n of s){const e={...o[n],...r?.[n]};Object.keys(e).length>0&&(t[n]=e)}return t}async function u(r,n){const e=r??await async function(o){let r=o;for(;;){const o=t.join(r,i);if(await d(o))return o;const n=t.dirname(r);if(n===r)return;r=n}}(n);if(!e)return{};let a,c;try{a=await o(e,"utf8")}catch(o){if(r)throw new Error(`Cannot read config file "${e}": ${y(o)}`);return{}}try{c=JSON.parse(a)}catch(o){throw new Error(`Invalid JSON in config file "${e}": ${y(o)}`)}return function(o,r){if("object"!=typeof o||null===o||Array.isArray(o))throw new Error(`Config file "${r}" must contain a JSON object.`);const t=o,n={};void 0!==t.thresholds&&(n.thresholds=g(t.thresholds,"thresholds",r));if(void 0!==t.languageThresholds){if("object"!=typeof t.languageThresholds||null===t.languageThresholds||Array.isArray(t.languageThresholds))throw new Error(`Config file "${r}": "languageThresholds" must be an object.`);const o={};for(const[n,e]of Object.entries(t.languageThresholds)){if(!s.includes(n))throw new Error(`Config file "${r}": unknown language profile "${n}" (expected one of ${s.join(", ")}).`);o[n]=g(e,`languageThresholds.${n}`,r)}n.languageThresholds=o}void 0!==t.maxFindings&&(n.maxFindings=p(t.maxFindings,"maxFindings",r));for(const o of["includeTests","failOnRisk","failOnError"])void 0!==t[o]&&(n[o]=m(t[o],o,r));if(void 0!==t.tsconfig){if("string"!=typeof t.tsconfig)throw new TypeError(`Config file "${r}": "tsconfig" must be a string.`);n.tsconfig=t.tsconfig}return n}(c,e)}async function d(o){try{return(await r(o)).isFile()}catch{return!1}}function g(o,r,t){if("object"!=typeof o||null===o||Array.isArray(o))throw new Error(`Config file "${t}": "${r}" must be an object.`);const e={};for(const[i,s]of Object.entries(o)){if(!(i in n))throw new Error(`Config file "${t}": unknown threshold "${i}" in "${r}".`);e[i]=p(s,`${r}.${i}`,t)}return e}function p(o,r,t){if("number"!=typeof o||!Number.isSafeInteger(o)||o<1)throw new Error(`Config file "${t}": "${r}" must be a positive integer.`);return o}function m(o,r,t){if("boolean"!=typeof o)throw new TypeError(`Config file "${t}": "${r}" must be a boolean.`);return o}function y(o){return o instanceof Error?o.message:String(o)}export{i as configFileName,e as defaultMaxFindings,a as defaultProfileThresholds,n as defaultThresholds,u as loadConfig,s as profileKeys,f as resolveOptions,c as resolveThresholds};
|
|
1
|
+
import{readFile as o,stat as r}from"node:fs/promises";import t from"node:path";const n={fileLoc:500,functionLoc:120,componentLoc:350,cognitive:25,cyclomatic:20,call:50,import:25,fanOut:10,parameter:8,duplicateBlock:2,transitiveDependency:25,structuralBreadth:8,structuralCoordination:300,stateMutation:50,duplicateSymbolGroup:5},e=20,i="code-gauge.config.json",s=["javascript","jsx","typescript","tsx","python","go","rust","react"],a={python:{stateMutation:90,structuralCoordination:350},react:{import:30}};function c(o,r,t){let n=o.thresholds;const e=o.profileThresholds[r];return e&&(n={...n,...e}),t&&o.profileThresholds.react&&(n={...n,...o.profileThresholds.react}),n}const l={fileLoc:"fileLocThreshold",functionLoc:"functionLocThreshold",componentLoc:"componentLocThreshold",cognitive:"cognitiveThreshold",cyclomatic:"cyclomaticThreshold",call:"callThreshold",import:"importThreshold",fanOut:"fanOutThreshold",parameter:"parameterThreshold",duplicateBlock:"duplicateBlockThreshold",transitiveDependency:"transitiveDependencyThreshold",structuralBreadth:"structuralBreadthThreshold",structuralCoordination:"structuralCoordinationThreshold",stateMutation:"stateMutationThreshold",duplicateSymbolGroup:"duplicateSymbolGroupThreshold"};function f(o,r){const t={...n};for(const n of Object.keys(t))t[n]=o[l[n]]??r.thresholds?.[n]??t[n];return{thresholds:t,profileThresholds:h(a,r.languageThresholds),maxFindings:o.maxFindings??r.maxFindings??20,includeTests:o.includeTests??r.includeTests??!1,failOnRisk:o.failOnRisk??r.failOnRisk??!1,failOnError:o.failOnError??r.failOnError??!1,json:o.json??!1,tsconfig:o.tsconfig??r.tsconfig}}function h(o,r){const t={};for(const n of s){const e={...o[n],...r?.[n]};Object.keys(e).length>0&&(t[n]=e)}return t}async function u(r,n){const e=r??await async function(o){let r=o;for(;;){const o=t.join(r,i);if(await d(o))return o;const n=t.dirname(r);if(n===r)return;r=n}}(n);if(!e)return{};let a,c;try{a=await o(e,"utf8")}catch(o){if(r)throw new Error(`Cannot read config file "${e}": ${y(o)}`);return{}}try{c=JSON.parse(a)}catch(o){throw new Error(`Invalid JSON in config file "${e}": ${y(o)}`)}return function(o,r){if("object"!=typeof o||null===o||Array.isArray(o))throw new Error(`Config file "${r}" must contain a JSON object.`);const t=o,n={};void 0!==t.thresholds&&(n.thresholds=g(t.thresholds,"thresholds",r));if(void 0!==t.languageThresholds){if("object"!=typeof t.languageThresholds||null===t.languageThresholds||Array.isArray(t.languageThresholds))throw new Error(`Config file "${r}": "languageThresholds" must be an object.`);const o={};for(const[n,e]of Object.entries(t.languageThresholds)){if(!s.includes(n))throw new Error(`Config file "${r}": unknown language profile "${n}" (expected one of ${s.join(", ")}).`);o[n]=g(e,`languageThresholds.${n}`,r)}n.languageThresholds=o}void 0!==t.maxFindings&&(n.maxFindings=p(t.maxFindings,"maxFindings",r));for(const o of["includeTests","failOnRisk","failOnError"])void 0!==t[o]&&(n[o]=m(t[o],o,r));if(void 0!==t.tsconfig){if("string"!=typeof t.tsconfig)throw new TypeError(`Config file "${r}": "tsconfig" must be a string.`);n.tsconfig=t.tsconfig}return n}(c,e)}async function d(o){try{return(await r(o)).isFile()}catch{return!1}}function g(o,r,t){if("object"!=typeof o||null===o||Array.isArray(o))throw new Error(`Config file "${t}": "${r}" must be an object.`);const e={};for(const[i,s]of Object.entries(o)){if(!(i in n))throw new Error(`Config file "${t}": unknown threshold "${i}" in "${r}".`);e[i]=p(s,`${r}.${i}`,t)}return e}function p(o,r,t){if("number"!=typeof o||!Number.isSafeInteger(o)||o<1)throw new Error(`Config file "${t}": "${r}" must be a positive integer.`);return o}function m(o,r,t){if("boolean"!=typeof o)throw new TypeError(`Config file "${t}": "${r}" must be a boolean.`);return o}function y(o){return o instanceof Error?o.message:String(o)}export{i as configFileName,e as defaultMaxFindings,a as defaultProfileThresholds,n as defaultThresholds,u as loadConfig,s as profileKeys,f as resolveOptions,c as resolveThresholds};
|
|
2
2
|
//# sourceMappingURL=cliConfig.js.map
|
package/dist/cliConfig.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cliConfig.js","sources":["../src/cliConfig.ts"],"sourcesContent":["import { readFile, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\n/** Risk thresholds; a finding is reported when the measured value is greater than or equal to the threshold. */\nexport interface Thresholds {\n fileLoc: number;\n functionLoc: number;\n componentLoc: number;\n cognitive: number;\n cyclomatic: number;\n call: number;\n import: number;\n fanOut: number;\n parameter: number;\n duplicateBlock: number;\n transitiveDependency: number;\n structuralBreadth: number;\n structuralCoordination: number;\n stateMutation: number;\n duplicateSymbolGroup: number;\n}\n\n// Defaults tuned against blind human labels across five representative WillBooster/WillBoosterLab\n// repositories to maximize F1 (precision without sacrificing recall); see PR for the evaluation.\nexport const defaultThresholds: Thresholds = {\n fileLoc: 500,\n functionLoc: 120,\n componentLoc: 350,\n cognitive: 25,\n cyclomatic: 20,\n call: 50,\n import: 25,\n fanOut: 10,\n parameter: 8,\n duplicateBlock: 2,\n transitiveDependency: 25,\n structuralBreadth: 8,\n structuralCoordination: 300,\n stateMutation: 50,\n duplicateSymbolGroup: 5,\n};\n\nexport const defaultMaxFindings = 20;\nexport const configFileName = 'code-gauge.config.json';\n\n/**\n * Profile keys for per-language and React-specific threshold overrides. A file resolves its\n * thresholds as base → its language profile → the `react` profile (when it contains a component).\n */\nexport const profileKeys = ['javascript', 'jsx', 'typescript', 'tsx', 'python', 'go', 'react'] as const;\nexport type ProfileKey = (typeof profileKeys)[number];\n\n/**\n * Built-in per-profile overrides, calibrated because some metric distributions differ sharply by\n * language/type: Python treats every binding as an assignment (so `stateMutation` runs ~10x higher)\n * and coordinates more per file, while React files import roughly twice as many sources as pure TS.\n */\nexport const defaultProfileThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>> = {\n python: { stateMutation: 90, structuralCoordination: 350 },\n react: { import: 30 },\n};\n\n/** Shape of the JSON configuration file. All fields are optional and fall back to the built-in defaults. */\nexport interface CodeGaugeConfig {\n thresholds?: Partial<Thresholds>;\n /** Per-profile overrides keyed by language name or `react`; merged over `thresholds` for matching files. */\n languageThresholds?: Partial<Record<ProfileKey, Partial<Thresholds>>>;\n maxFindings?: number;\n includeTests?: boolean;\n failOnRisk?: boolean;\n failOnError?: boolean;\n tsconfig?: string;\n}\n\n/** Options after merging command-line flags, the configuration file, and the built-in defaults. */\nexport interface ResolvedOptions {\n thresholds: Thresholds;\n profileThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>>;\n maxFindings: number;\n includeTests: boolean;\n failOnRisk: boolean;\n failOnError: boolean;\n json: boolean;\n tsconfig?: string;\n}\n\n/**\n * Resolves the thresholds for a single file: the base thresholds overlaid with its language profile\n * and then, when the file contains a React component, the `react` profile.\n */\nexport function resolveThresholds(options: ResolvedOptions, language: string, isReact: boolean): Thresholds {\n let thresholds = options.thresholds;\n const languageOverride = options.profileThresholds[language as ProfileKey];\n if (languageOverride) {\n thresholds = { ...thresholds, ...languageOverride };\n }\n if (isReact && options.profileThresholds.react) {\n thresholds = { ...thresholds, ...options.profileThresholds.react };\n }\n return thresholds;\n}\n\n/** Raw command-line options; every threshold is undefined unless the user passed the flag. */\nexport interface CliOptions {\n config?: string;\n fileLocThreshold?: number;\n functionLocThreshold?: number;\n componentLocThreshold?: number;\n cognitiveThreshold?: number;\n cyclomaticThreshold?: number;\n callThreshold?: number;\n importThreshold?: number;\n fanOutThreshold?: number;\n parameterThreshold?: number;\n duplicateBlockThreshold?: number;\n transitiveDependencyThreshold?: number;\n structuralBreadthThreshold?: number;\n structuralCoordinationThreshold?: number;\n stateMutationThreshold?: number;\n duplicateSymbolGroupThreshold?: number;\n maxFindings?: number;\n includeTests?: boolean;\n failOnRisk?: boolean;\n failOnError?: boolean;\n json?: boolean;\n tsconfig?: string;\n}\n\n/** Maps each threshold to the matching command-line flag; the config key equals the flag without the `-threshold` suffix. */\nconst thresholdCliKeys: Record<keyof Thresholds, keyof CliOptions> = {\n fileLoc: 'fileLocThreshold',\n functionLoc: 'functionLocThreshold',\n componentLoc: 'componentLocThreshold',\n cognitive: 'cognitiveThreshold',\n cyclomatic: 'cyclomaticThreshold',\n call: 'callThreshold',\n import: 'importThreshold',\n fanOut: 'fanOutThreshold',\n parameter: 'parameterThreshold',\n duplicateBlock: 'duplicateBlockThreshold',\n transitiveDependency: 'transitiveDependencyThreshold',\n structuralBreadth: 'structuralBreadthThreshold',\n structuralCoordination: 'structuralCoordinationThreshold',\n stateMutation: 'stateMutationThreshold',\n duplicateSymbolGroup: 'duplicateSymbolGroupThreshold',\n};\n\n/** Resolves options with precedence command-line flags > configuration file > built-in defaults. */\nexport function resolveOptions(cli: CliOptions, config: CodeGaugeConfig): ResolvedOptions {\n const thresholds = { ...defaultThresholds };\n for (const key of Object.keys(thresholds) as (keyof Thresholds)[]) {\n thresholds[key] = (cli[thresholdCliKeys[key]] as number | undefined) ?? config.thresholds?.[key] ?? thresholds[key];\n }\n\n return {\n thresholds,\n profileThresholds: mergeProfileThresholds(defaultProfileThresholds, config.languageThresholds),\n maxFindings: cli.maxFindings ?? config.maxFindings ?? defaultMaxFindings,\n includeTests: cli.includeTests ?? config.includeTests ?? false,\n failOnRisk: cli.failOnRisk ?? config.failOnRisk ?? false,\n failOnError: cli.failOnError ?? config.failOnError ?? false,\n json: cli.json ?? false,\n tsconfig: cli.tsconfig ?? config.tsconfig,\n };\n}\n\n/** Merges user-supplied per-profile overrides on top of the built-in ones, per profile. */\nfunction mergeProfileThresholds(\n defaults: Partial<Record<ProfileKey, Partial<Thresholds>>>,\n overrides: Partial<Record<ProfileKey, Partial<Thresholds>>> | undefined\n): Partial<Record<ProfileKey, Partial<Thresholds>>> {\n const merged: Partial<Record<ProfileKey, Partial<Thresholds>>> = {};\n for (const key of profileKeys) {\n const combined = { ...defaults[key], ...overrides?.[key] };\n if (Object.keys(combined).length > 0) {\n merged[key] = combined;\n }\n }\n return merged;\n}\n\n/**\n * Loads the configuration file. An explicit path must exist; otherwise the nearest\n * `code-gauge.config.json` is searched by walking up from the target directory.\n */\nexport async function loadConfig(explicitPath: string | undefined, targetDirectory: string): Promise<CodeGaugeConfig> {\n const configFile = explicitPath ?? (await findNearestConfig(targetDirectory));\n if (!configFile) {\n return {};\n }\n\n let content;\n try {\n content = await readFile(configFile, 'utf8');\n } catch (error) {\n if (explicitPath) {\n throw new Error(`Cannot read config file \"${configFile}\": ${formatError(error)}`);\n }\n return {};\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (error) {\n throw new Error(`Invalid JSON in config file \"${configFile}\": ${formatError(error)}`);\n }\n\n return validateConfig(parsed, configFile);\n}\n\nasync function findNearestConfig(targetDirectory: string): Promise<string | undefined> {\n let currentDirectory = targetDirectory;\n while (true) {\n const configFile = path.join(currentDirectory, configFileName);\n if (await fileExists(configFile)) {\n return configFile;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n try {\n const fileStat = await stat(file);\n return fileStat.isFile();\n } catch {\n return false;\n }\n}\n\nfunction validateConfig(value: unknown, configFile: string): CodeGaugeConfig {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Config file \"${configFile}\" must contain a JSON object.`);\n }\n\n const raw = value as Record<string, unknown>;\n const config: CodeGaugeConfig = {};\n\n if (raw.thresholds !== undefined) {\n config.thresholds = validateThresholdObject(raw.thresholds, 'thresholds', configFile);\n }\n\n if (raw.languageThresholds !== undefined) {\n if (\n typeof raw.languageThresholds !== 'object' ||\n raw.languageThresholds === null ||\n Array.isArray(raw.languageThresholds)\n ) {\n throw new Error(`Config file \"${configFile}\": \"languageThresholds\" must be an object.`);\n }\n const languageThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>> = {};\n for (const [profile, thresholds] of Object.entries(raw.languageThresholds as Record<string, unknown>)) {\n if (!(profileKeys as readonly string[]).includes(profile)) {\n throw new Error(\n `Config file \"${configFile}\": unknown language profile \"${profile}\" (expected one of ${profileKeys.join(', ')}).`\n );\n }\n languageThresholds[profile as ProfileKey] = validateThresholdObject(\n thresholds,\n `languageThresholds.${profile}`,\n configFile\n );\n }\n config.languageThresholds = languageThresholds;\n }\n\n if (raw.maxFindings !== undefined) {\n config.maxFindings = requirePositiveInteger(raw.maxFindings, 'maxFindings', configFile);\n }\n for (const key of ['includeTests', 'failOnRisk', 'failOnError'] as const) {\n if (raw[key] !== undefined) {\n config[key] = requireBoolean(raw[key], key, configFile);\n }\n }\n if (raw.tsconfig !== undefined) {\n if (typeof raw.tsconfig !== 'string') {\n throw new TypeError(`Config file \"${configFile}\": \"tsconfig\" must be a string.`);\n }\n config.tsconfig = raw.tsconfig;\n }\n\n return config;\n}\n\nfunction validateThresholdObject(value: unknown, label: string, configFile: string): Partial<Thresholds> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Config file \"${configFile}\": \"${label}\" must be an object.`);\n }\n const thresholds: Partial<Thresholds> = {};\n for (const [key, threshold] of Object.entries(value as Record<string, unknown>)) {\n if (!(key in defaultThresholds)) {\n throw new Error(`Config file \"${configFile}\": unknown threshold \"${key}\" in \"${label}\".`);\n }\n thresholds[key as keyof Thresholds] = requirePositiveInteger(threshold, `${label}.${key}`, configFile);\n }\n return thresholds;\n}\n\nfunction requirePositiveInteger(value: unknown, key: string, configFile: string): number {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1) {\n throw new Error(`Config file \"${configFile}\": \"${key}\" must be a positive integer.`);\n }\n return value;\n}\n\nfunction requireBoolean(value: unknown, key: string, configFile: string): boolean {\n if (typeof value !== 'boolean') {\n throw new TypeError(`Config file \"${configFile}\": \"${key}\" must be a boolean.`);\n }\n return value;\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"names":["defaultThresholds","fileLoc","functionLoc","componentLoc","cognitive","cyclomatic","call","import","fanOut","parameter","duplicateBlock","transitiveDependency","structuralBreadth","structuralCoordination","stateMutation","duplicateSymbolGroup","defaultMaxFindings","configFileName","profileKeys","defaultProfileThresholds","python","react","resolveThresholds","options","language","isReact","thresholds","languageOverride","profileThresholds","thresholdCliKeys","resolveOptions","cli","config","key","Object","keys","mergeProfileThresholds","languageThresholds","maxFindings","includeTests","failOnRisk","failOnError","json","tsconfig","defaults","overrides","merged","combined","length","async","loadConfig","explicitPath","targetDirectory","configFile","currentDirectory","path","join","fileExists","parentDirectory","dirname","findNearestConfig","content","parsed","readFile","error","Error","formatError","JSON","parse","value","Array","isArray","raw","undefined","validateThresholdObject","profile","entries","includes","requirePositiveInteger","requireBoolean","TypeError","validateConfig","file","stat","isFile","label","threshold","Number","isSafeInteger","message","String"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cliConfig.js","sources":["../src/cliConfig.ts"],"sourcesContent":["import { readFile, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\n/** Risk thresholds; a finding is reported when the measured value is greater than or equal to the threshold. */\nexport interface Thresholds {\n fileLoc: number;\n functionLoc: number;\n componentLoc: number;\n cognitive: number;\n cyclomatic: number;\n call: number;\n import: number;\n fanOut: number;\n parameter: number;\n duplicateBlock: number;\n transitiveDependency: number;\n structuralBreadth: number;\n structuralCoordination: number;\n stateMutation: number;\n duplicateSymbolGroup: number;\n}\n\n// Defaults tuned against blind human labels across five representative WillBooster/WillBoosterLab\n// repositories to maximize F1 (precision without sacrificing recall); see PR for the evaluation.\nexport const defaultThresholds: Thresholds = {\n fileLoc: 500,\n functionLoc: 120,\n componentLoc: 350,\n cognitive: 25,\n cyclomatic: 20,\n call: 50,\n import: 25,\n fanOut: 10,\n parameter: 8,\n duplicateBlock: 2,\n transitiveDependency: 25,\n structuralBreadth: 8,\n structuralCoordination: 300,\n stateMutation: 50,\n duplicateSymbolGroup: 5,\n};\n\nexport const defaultMaxFindings = 20;\nexport const configFileName = 'code-gauge.config.json';\n\n/**\n * Profile keys for per-language and React-specific threshold overrides. A file resolves its\n * thresholds as base → its language profile → the `react` profile (when it contains a component).\n */\nexport const profileKeys = ['javascript', 'jsx', 'typescript', 'tsx', 'python', 'go', 'rust', 'react'] as const;\nexport type ProfileKey = (typeof profileKeys)[number];\n\n/**\n * Built-in per-profile overrides, calibrated because some metric distributions differ sharply by\n * language/type: Python treats every binding as an assignment (so `stateMutation` runs ~10x higher)\n * and coordinates more per file, while React files import roughly twice as many sources as pure TS.\n */\nexport const defaultProfileThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>> = {\n python: { stateMutation: 90, structuralCoordination: 350 },\n react: { import: 30 },\n};\n\n/** Shape of the JSON configuration file. All fields are optional and fall back to the built-in defaults. */\nexport interface CodeGaugeConfig {\n thresholds?: Partial<Thresholds>;\n /** Per-profile overrides keyed by language name or `react`; merged over `thresholds` for matching files. */\n languageThresholds?: Partial<Record<ProfileKey, Partial<Thresholds>>>;\n maxFindings?: number;\n includeTests?: boolean;\n failOnRisk?: boolean;\n failOnError?: boolean;\n tsconfig?: string;\n}\n\n/** Options after merging command-line flags, the configuration file, and the built-in defaults. */\nexport interface ResolvedOptions {\n thresholds: Thresholds;\n profileThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>>;\n maxFindings: number;\n includeTests: boolean;\n failOnRisk: boolean;\n failOnError: boolean;\n json: boolean;\n tsconfig?: string;\n}\n\n/**\n * Resolves the thresholds for a single file: the base thresholds overlaid with its language profile\n * and then, when the file contains a React component, the `react` profile.\n */\nexport function resolveThresholds(options: ResolvedOptions, language: string, isReact: boolean): Thresholds {\n let thresholds = options.thresholds;\n const languageOverride = options.profileThresholds[language as ProfileKey];\n if (languageOverride) {\n thresholds = { ...thresholds, ...languageOverride };\n }\n if (isReact && options.profileThresholds.react) {\n thresholds = { ...thresholds, ...options.profileThresholds.react };\n }\n return thresholds;\n}\n\n/** Raw command-line options; every threshold is undefined unless the user passed the flag. */\nexport interface CliOptions {\n config?: string;\n fileLocThreshold?: number;\n functionLocThreshold?: number;\n componentLocThreshold?: number;\n cognitiveThreshold?: number;\n cyclomaticThreshold?: number;\n callThreshold?: number;\n importThreshold?: number;\n fanOutThreshold?: number;\n parameterThreshold?: number;\n duplicateBlockThreshold?: number;\n transitiveDependencyThreshold?: number;\n structuralBreadthThreshold?: number;\n structuralCoordinationThreshold?: number;\n stateMutationThreshold?: number;\n duplicateSymbolGroupThreshold?: number;\n maxFindings?: number;\n includeTests?: boolean;\n failOnRisk?: boolean;\n failOnError?: boolean;\n json?: boolean;\n tsconfig?: string;\n}\n\n/** Maps each threshold to the matching command-line flag; the config key equals the flag without the `-threshold` suffix. */\nconst thresholdCliKeys: Record<keyof Thresholds, keyof CliOptions> = {\n fileLoc: 'fileLocThreshold',\n functionLoc: 'functionLocThreshold',\n componentLoc: 'componentLocThreshold',\n cognitive: 'cognitiveThreshold',\n cyclomatic: 'cyclomaticThreshold',\n call: 'callThreshold',\n import: 'importThreshold',\n fanOut: 'fanOutThreshold',\n parameter: 'parameterThreshold',\n duplicateBlock: 'duplicateBlockThreshold',\n transitiveDependency: 'transitiveDependencyThreshold',\n structuralBreadth: 'structuralBreadthThreshold',\n structuralCoordination: 'structuralCoordinationThreshold',\n stateMutation: 'stateMutationThreshold',\n duplicateSymbolGroup: 'duplicateSymbolGroupThreshold',\n};\n\n/** Resolves options with precedence command-line flags > configuration file > built-in defaults. */\nexport function resolveOptions(cli: CliOptions, config: CodeGaugeConfig): ResolvedOptions {\n const thresholds = { ...defaultThresholds };\n for (const key of Object.keys(thresholds) as (keyof Thresholds)[]) {\n thresholds[key] = (cli[thresholdCliKeys[key]] as number | undefined) ?? config.thresholds?.[key] ?? thresholds[key];\n }\n\n return {\n thresholds,\n profileThresholds: mergeProfileThresholds(defaultProfileThresholds, config.languageThresholds),\n maxFindings: cli.maxFindings ?? config.maxFindings ?? defaultMaxFindings,\n includeTests: cli.includeTests ?? config.includeTests ?? false,\n failOnRisk: cli.failOnRisk ?? config.failOnRisk ?? false,\n failOnError: cli.failOnError ?? config.failOnError ?? false,\n json: cli.json ?? false,\n tsconfig: cli.tsconfig ?? config.tsconfig,\n };\n}\n\n/** Merges user-supplied per-profile overrides on top of the built-in ones, per profile. */\nfunction mergeProfileThresholds(\n defaults: Partial<Record<ProfileKey, Partial<Thresholds>>>,\n overrides: Partial<Record<ProfileKey, Partial<Thresholds>>> | undefined\n): Partial<Record<ProfileKey, Partial<Thresholds>>> {\n const merged: Partial<Record<ProfileKey, Partial<Thresholds>>> = {};\n for (const key of profileKeys) {\n const combined = { ...defaults[key], ...overrides?.[key] };\n if (Object.keys(combined).length > 0) {\n merged[key] = combined;\n }\n }\n return merged;\n}\n\n/**\n * Loads the configuration file. An explicit path must exist; otherwise the nearest\n * `code-gauge.config.json` is searched by walking up from the target directory.\n */\nexport async function loadConfig(explicitPath: string | undefined, targetDirectory: string): Promise<CodeGaugeConfig> {\n const configFile = explicitPath ?? (await findNearestConfig(targetDirectory));\n if (!configFile) {\n return {};\n }\n\n let content;\n try {\n content = await readFile(configFile, 'utf8');\n } catch (error) {\n if (explicitPath) {\n throw new Error(`Cannot read config file \"${configFile}\": ${formatError(error)}`);\n }\n return {};\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (error) {\n throw new Error(`Invalid JSON in config file \"${configFile}\": ${formatError(error)}`);\n }\n\n return validateConfig(parsed, configFile);\n}\n\nasync function findNearestConfig(targetDirectory: string): Promise<string | undefined> {\n let currentDirectory = targetDirectory;\n while (true) {\n const configFile = path.join(currentDirectory, configFileName);\n if (await fileExists(configFile)) {\n return configFile;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n try {\n const fileStat = await stat(file);\n return fileStat.isFile();\n } catch {\n return false;\n }\n}\n\nfunction validateConfig(value: unknown, configFile: string): CodeGaugeConfig {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Config file \"${configFile}\" must contain a JSON object.`);\n }\n\n const raw = value as Record<string, unknown>;\n const config: CodeGaugeConfig = {};\n\n if (raw.thresholds !== undefined) {\n config.thresholds = validateThresholdObject(raw.thresholds, 'thresholds', configFile);\n }\n\n if (raw.languageThresholds !== undefined) {\n if (\n typeof raw.languageThresholds !== 'object' ||\n raw.languageThresholds === null ||\n Array.isArray(raw.languageThresholds)\n ) {\n throw new Error(`Config file \"${configFile}\": \"languageThresholds\" must be an object.`);\n }\n const languageThresholds: Partial<Record<ProfileKey, Partial<Thresholds>>> = {};\n for (const [profile, thresholds] of Object.entries(raw.languageThresholds as Record<string, unknown>)) {\n if (!(profileKeys as readonly string[]).includes(profile)) {\n throw new Error(\n `Config file \"${configFile}\": unknown language profile \"${profile}\" (expected one of ${profileKeys.join(', ')}).`\n );\n }\n languageThresholds[profile as ProfileKey] = validateThresholdObject(\n thresholds,\n `languageThresholds.${profile}`,\n configFile\n );\n }\n config.languageThresholds = languageThresholds;\n }\n\n if (raw.maxFindings !== undefined) {\n config.maxFindings = requirePositiveInteger(raw.maxFindings, 'maxFindings', configFile);\n }\n for (const key of ['includeTests', 'failOnRisk', 'failOnError'] as const) {\n if (raw[key] !== undefined) {\n config[key] = requireBoolean(raw[key], key, configFile);\n }\n }\n if (raw.tsconfig !== undefined) {\n if (typeof raw.tsconfig !== 'string') {\n throw new TypeError(`Config file \"${configFile}\": \"tsconfig\" must be a string.`);\n }\n config.tsconfig = raw.tsconfig;\n }\n\n return config;\n}\n\nfunction validateThresholdObject(value: unknown, label: string, configFile: string): Partial<Thresholds> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new Error(`Config file \"${configFile}\": \"${label}\" must be an object.`);\n }\n const thresholds: Partial<Thresholds> = {};\n for (const [key, threshold] of Object.entries(value as Record<string, unknown>)) {\n if (!(key in defaultThresholds)) {\n throw new Error(`Config file \"${configFile}\": unknown threshold \"${key}\" in \"${label}\".`);\n }\n thresholds[key as keyof Thresholds] = requirePositiveInteger(threshold, `${label}.${key}`, configFile);\n }\n return thresholds;\n}\n\nfunction requirePositiveInteger(value: unknown, key: string, configFile: string): number {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1) {\n throw new Error(`Config file \"${configFile}\": \"${key}\" must be a positive integer.`);\n }\n return value;\n}\n\nfunction requireBoolean(value: unknown, key: string, configFile: string): boolean {\n if (typeof value !== 'boolean') {\n throw new TypeError(`Config file \"${configFile}\": \"${key}\" must be a boolean.`);\n }\n return value;\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"names":["defaultThresholds","fileLoc","functionLoc","componentLoc","cognitive","cyclomatic","call","import","fanOut","parameter","duplicateBlock","transitiveDependency","structuralBreadth","structuralCoordination","stateMutation","duplicateSymbolGroup","defaultMaxFindings","configFileName","profileKeys","defaultProfileThresholds","python","react","resolveThresholds","options","language","isReact","thresholds","languageOverride","profileThresholds","thresholdCliKeys","resolveOptions","cli","config","key","Object","keys","mergeProfileThresholds","languageThresholds","maxFindings","includeTests","failOnRisk","failOnError","json","tsconfig","defaults","overrides","merged","combined","length","async","loadConfig","explicitPath","targetDirectory","configFile","currentDirectory","path","join","fileExists","parentDirectory","dirname","findNearestConfig","content","parsed","readFile","error","Error","formatError","JSON","parse","value","Array","isArray","raw","undefined","validateThresholdObject","profile","entries","includes","requirePositiveInteger","requireBoolean","TypeError","validateConfig","file","stat","isFile","label","threshold","Number","isSafeInteger","message","String"],"mappings":"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"}
|
package/dist/languages.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var e=require("tree-sitter-go"),t=require("tree-sitter-javascript"),
|
|
1
|
+
"use strict";var e=require("tree-sitter-go"),t=require("tree-sitter-javascript"),n=require("tree-sitter-python"),s=require("tree-sitter-rust"),a=require("tree-sitter-typescript");const r=["function","function_declaration","function_definition","function_expression","function_item","function_signature_item","function_declarator","method_declaration","method_definition","method_spec","arrow_function","generator_function","generator_function_declaration","lambda","lambda_expression","closure_expression"],i=["class","class_declaration","class_definition","interface_declaration","trait_item","struct_item","enum_item","union_item"],o=["if_statement","elif_clause","else_if_clause","for_statement","for_in_statement","while_statement","do_statement","catch_clause","except_clause","case_clause","switch_case","match_arm","conditional_expression","ternary_expression","if_expression","while_expression","for_expression","loop_expression"];function c(e){return function(e,t){if("object"!=typeof e||null===e||!(t in e))return!1;return Boolean(e[t])}(e,"default")?e.default:e}function u(e){return a[e]}const p=[{name:"javascript",aliases:["js","mjs","cjs"],parserLanguage:c(t)},{name:"jsx",parserLanguage:c(t)},{name:"typescript",aliases:["ts"],parserLanguage:u("typescript")},{name:"tsx",parserLanguage:u("tsx")},{name:"python",aliases:["py"],parserLanguage:c(n)},{name:"go",parserLanguage:c(e)},{name:"rust",aliases:["rs"],parserLanguage:c(s)}].map(e=>({functionNodeTypes:r,classNodeTypes:i,decisionNodeTypes:o,nestingNodeTypes:o,...e}));const _=p.map(e=>e.name);exports.createLanguageRegistry=function(e=p){const t=new Map;for(const n of e){t.set(n.name,n);for(const e of n.aliases??[])t.set(e,n)}return t},exports.defaultLanguages=p,exports.supportedLanguages=_;
|
|
2
2
|
//# sourceMappingURL=languages.cjs.map
|
package/dist/languages.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"languages.cjs","sources":["../src/languages.ts"],"sourcesContent":["import Go from 'tree-sitter-go';\nimport JavaScript from 'tree-sitter-javascript';\nimport Python from 'tree-sitter-python';\nimport TypeScript from 'tree-sitter-typescript';\nimport type { LanguageDefinition, LanguageName, ParserLanguage } from './types.js';\n\ntype GrammarModule = unknown;\n\nconst commonFunctionNodes = [\n 'function',\n 'function_declaration',\n 'function_definition',\n 'function_expression',\n 'function_item',\n 'function_declarator',\n 'method_declaration',\n 'method_definition',\n 'method_spec',\n 'arrow_function',\n 'generator_function',\n 'generator_function_declaration',\n 'lambda',\n 'lambda_expression',\n] as const;\n\nconst commonClassNodes = [\n 'class',\n 'class_declaration',\n 'class_definition',\n 'interface_declaration',\n 'trait_item',\n '
|
|
1
|
+
{"version":3,"file":"languages.cjs","sources":["../src/languages.ts"],"sourcesContent":["import Go from 'tree-sitter-go';\nimport JavaScript from 'tree-sitter-javascript';\nimport Python from 'tree-sitter-python';\nimport Rust from 'tree-sitter-rust';\nimport TypeScript from 'tree-sitter-typescript';\nimport type { LanguageDefinition, LanguageName, ParserLanguage } from './types.js';\n\ntype GrammarModule = unknown;\n\nconst commonFunctionNodes = [\n 'function',\n 'function_declaration',\n 'function_definition',\n 'function_expression',\n 'function_item',\n 'function_signature_item',\n 'function_declarator',\n 'method_declaration',\n 'method_definition',\n 'method_spec',\n 'arrow_function',\n 'generator_function',\n 'generator_function_declaration',\n 'lambda',\n 'lambda_expression',\n 'closure_expression',\n] as const;\n\nconst commonClassNodes = [\n 'class',\n 'class_declaration',\n 'class_definition',\n 'interface_declaration',\n 'trait_item',\n 'struct_item',\n 'enum_item',\n 'union_item',\n] as const;\n\nconst commonDecisionNodes = [\n 'if_statement',\n 'elif_clause',\n 'else_if_clause',\n 'for_statement',\n 'for_in_statement',\n 'while_statement',\n 'do_statement',\n 'catch_clause',\n 'except_clause',\n 'case_clause',\n 'switch_case',\n 'match_arm',\n 'conditional_expression',\n 'ternary_expression',\n 'if_expression',\n 'while_expression',\n 'for_expression',\n 'loop_expression',\n] as const;\n\nfunction normalizeGrammar(module: GrammarModule): ParserLanguage {\n if (isGrammarWrapper(module, 'default')) {\n return module.default;\n }\n\n return module;\n}\n\nfunction getTypeScriptGrammar(name: 'typescript' | 'tsx'): ParserLanguage {\n const grammars = TypeScript as unknown as Record<string, GrammarModule>;\n return grammars[name];\n}\n\nfunction isGrammarWrapper(value: GrammarModule, key: 'default'): value is Record<typeof key, ParserLanguage> {\n if (typeof value !== 'object' || value === null || !(key in value)) {\n return false;\n }\n\n return Boolean((value as Record<string, ParserLanguage>)[key]);\n}\n\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n {\n name: 'javascript',\n aliases: ['js', 'mjs', 'cjs'],\n parserLanguage: normalizeGrammar(JavaScript as unknown as GrammarModule),\n },\n {\n name: 'jsx',\n parserLanguage: normalizeGrammar(JavaScript as unknown as GrammarModule),\n },\n {\n name: 'typescript',\n aliases: ['ts'],\n parserLanguage: getTypeScriptGrammar('typescript'),\n },\n {\n name: 'tsx',\n parserLanguage: getTypeScriptGrammar('tsx'),\n },\n {\n name: 'python',\n aliases: ['py'],\n parserLanguage: normalizeGrammar(Python as unknown as GrammarModule),\n },\n {\n name: 'go',\n parserLanguage: normalizeGrammar(Go as unknown as GrammarModule),\n },\n {\n name: 'rust',\n aliases: ['rs'],\n parserLanguage: normalizeGrammar(Rust as unknown as GrammarModule),\n },\n].map((language) => ({\n functionNodeTypes: commonFunctionNodes,\n classNodeTypes: commonClassNodes,\n decisionNodeTypes: commonDecisionNodes,\n nestingNodeTypes: commonDecisionNodes,\n ...language,\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"],"names":["commonFunctionNodes","commonClassNodes","commonDecisionNodes","normalizeGrammar","module","value","key","Boolean","isGrammarWrapper","default","getTypeScriptGrammar","name","TypeScript","defaultLanguages","aliases","parserLanguage","JavaScript","Python","Go","Rust","map","language","functionNodeTypes","classNodeTypes","decisionNodeTypes","nestingNodeTypes","supportedLanguages","languages","registry","Map","set","alias"],"mappings":"mLASA,MAAMA,EAAsB,CAC1B,WACA,uBACA,sBACA,sBACA,gBACA,0BACA,sBACA,qBACA,oBACA,cACA,iBACA,qBACA,iCACA,SACA,oBACA,sBAGIC,EAAmB,CACvB,QACA,oBACA,mBACA,wBACA,aACA,cACA,YACA,cAGIC,EAAsB,CAC1B,eACA,cACA,iBACA,gBACA,mBACA,kBACA,eACA,eACA,gBACA,cACA,cACA,YACA,yBACA,qBACA,gBACA,mBACA,iBACA,mBAGF,SAASC,EAAiBC,GACxB,OAYF,SAA0BC,EAAsBC,GAC9C,GAAqB,iBAAVD,GAAgC,OAAVA,KAAoBC,KAAOD,GAC1D,OAAO,EAGT,OAAOE,QAASF,EAAyCC,GAC3D,CAlBME,CAAiBJ,EAAQ,WACpBA,EAAOK,QAGTL,CACT,CAEA,SAASM,EAAqBC,GAE5B,OADiBC,EACDD,EAClB,CAUO,MAAME,EAAkD,CAC7D,CACEF,KAAM,aACNG,QAAS,CAAC,KAAM,MAAO,OACvBC,eAAgBZ,EAAiBa,IAEnC,CACEL,KAAM,MACNI,eAAgBZ,EAAiBa,IAEnC,CACEL,KAAM,aACNG,QAAS,CAAC,MACVC,eAAgBL,EAAqB,eAEvC,CACEC,KAAM,MACNI,eAAgBL,EAAqB,QAEvC,CACEC,KAAM,SACNG,QAAS,CAAC,MACVC,eAAgBZ,EAAiBc,IAEnC,CACEN,KAAM,KACNI,eAAgBZ,EAAiBe,IAEnC,CACEP,KAAM,OACNG,QAAS,CAAC,MACVC,eAAgBZ,EAAiBgB,KAEnCC,IAAKC,IAAQ,CACbC,kBAAmBtB,EACnBuB,eAAgBtB,EAChBuB,kBAAmBtB,EACnBuB,iBAAkBvB,KACfmB,KAkBE,MAAMK,EAAqBb,EAAiBO,IAAKC,GAAaA,EAASV,qCAfvE,SACLgB,EAA2Cd,GAE3C,MAAMe,EAAW,IAAIC,IAErB,IAAK,MAAMR,KAAYM,EAAW,CAChCC,EAASE,IAAIT,EAASV,KAAMU,GAC5B,IAAK,MAAMU,KAASV,EAASP,SAAW,GACtCc,EAASE,IAAIC,EAAOV,EAExB,CAEA,OAAOO,CACT"}
|
package/dist/languages.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"tree-sitter-go";import t from"tree-sitter-javascript";import n from"tree-sitter-python";import a from"tree-sitter-typescript";const
|
|
1
|
+
import e from"tree-sitter-go";import t from"tree-sitter-javascript";import n from"tree-sitter-python";import s from"tree-sitter-rust";import a from"tree-sitter-typescript";const r=["function","function_declaration","function_definition","function_expression","function_item","function_signature_item","function_declarator","method_declaration","method_definition","method_spec","arrow_function","generator_function","generator_function_declaration","lambda","lambda_expression","closure_expression"],i=["class","class_declaration","class_definition","interface_declaration","trait_item","struct_item","enum_item","union_item"],o=["if_statement","elif_clause","else_if_clause","for_statement","for_in_statement","while_statement","do_statement","catch_clause","except_clause","case_clause","switch_case","match_arm","conditional_expression","ternary_expression","if_expression","while_expression","for_expression","loop_expression"];function c(e){return function(e,t){if("object"!=typeof e||null===e||!(t in e))return!1;return Boolean(e[t])}(e,"default")?e.default:e}function p(e){return a[e]}const _=[{name:"javascript",aliases:["js","mjs","cjs"],parserLanguage:c(t)},{name:"jsx",parserLanguage:c(t)},{name:"typescript",aliases:["ts"],parserLanguage:p("typescript")},{name:"tsx",parserLanguage:p("tsx")},{name:"python",aliases:["py"],parserLanguage:c(n)},{name:"go",parserLanguage:c(e)},{name:"rust",aliases:["rs"],parserLanguage:c(s)}].map(e=>({functionNodeTypes:r,classNodeTypes:i,decisionNodeTypes:o,nestingNodeTypes:o,...e}));function u(e=_){const t=new Map;for(const n of e){t.set(n.name,n);for(const e of n.aliases??[])t.set(e,n)}return t}const m=_.map(e=>e.name);export{u as createLanguageRegistry,_ as defaultLanguages,m as supportedLanguages};
|
|
2
2
|
//# sourceMappingURL=languages.js.map
|
package/dist/languages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"languages.js","sources":["../src/languages.ts"],"sourcesContent":["import Go from 'tree-sitter-go';\nimport JavaScript from 'tree-sitter-javascript';\nimport Python from 'tree-sitter-python';\nimport TypeScript from 'tree-sitter-typescript';\nimport type { LanguageDefinition, LanguageName, ParserLanguage } from './types.js';\n\ntype GrammarModule = unknown;\n\nconst commonFunctionNodes = [\n 'function',\n 'function_declaration',\n 'function_definition',\n 'function_expression',\n 'function_item',\n 'function_declarator',\n 'method_declaration',\n 'method_definition',\n 'method_spec',\n 'arrow_function',\n 'generator_function',\n 'generator_function_declaration',\n 'lambda',\n 'lambda_expression',\n] as const;\n\nconst commonClassNodes = [\n 'class',\n 'class_declaration',\n 'class_definition',\n 'interface_declaration',\n 'trait_item',\n '
|
|
1
|
+
{"version":3,"file":"languages.js","sources":["../src/languages.ts"],"sourcesContent":["import Go from 'tree-sitter-go';\nimport JavaScript from 'tree-sitter-javascript';\nimport Python from 'tree-sitter-python';\nimport Rust from 'tree-sitter-rust';\nimport TypeScript from 'tree-sitter-typescript';\nimport type { LanguageDefinition, LanguageName, ParserLanguage } from './types.js';\n\ntype GrammarModule = unknown;\n\nconst commonFunctionNodes = [\n 'function',\n 'function_declaration',\n 'function_definition',\n 'function_expression',\n 'function_item',\n 'function_signature_item',\n 'function_declarator',\n 'method_declaration',\n 'method_definition',\n 'method_spec',\n 'arrow_function',\n 'generator_function',\n 'generator_function_declaration',\n 'lambda',\n 'lambda_expression',\n 'closure_expression',\n] as const;\n\nconst commonClassNodes = [\n 'class',\n 'class_declaration',\n 'class_definition',\n 'interface_declaration',\n 'trait_item',\n 'struct_item',\n 'enum_item',\n 'union_item',\n] as const;\n\nconst commonDecisionNodes = [\n 'if_statement',\n 'elif_clause',\n 'else_if_clause',\n 'for_statement',\n 'for_in_statement',\n 'while_statement',\n 'do_statement',\n 'catch_clause',\n 'except_clause',\n 'case_clause',\n 'switch_case',\n 'match_arm',\n 'conditional_expression',\n 'ternary_expression',\n 'if_expression',\n 'while_expression',\n 'for_expression',\n 'loop_expression',\n] as const;\n\nfunction normalizeGrammar(module: GrammarModule): ParserLanguage {\n if (isGrammarWrapper(module, 'default')) {\n return module.default;\n }\n\n return module;\n}\n\nfunction getTypeScriptGrammar(name: 'typescript' | 'tsx'): ParserLanguage {\n const grammars = TypeScript as unknown as Record<string, GrammarModule>;\n return grammars[name];\n}\n\nfunction isGrammarWrapper(value: GrammarModule, key: 'default'): value is Record<typeof key, ParserLanguage> {\n if (typeof value !== 'object' || value === null || !(key in value)) {\n return false;\n }\n\n return Boolean((value as Record<string, ParserLanguage>)[key]);\n}\n\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n {\n name: 'javascript',\n aliases: ['js', 'mjs', 'cjs'],\n parserLanguage: normalizeGrammar(JavaScript as unknown as GrammarModule),\n },\n {\n name: 'jsx',\n parserLanguage: normalizeGrammar(JavaScript as unknown as GrammarModule),\n },\n {\n name: 'typescript',\n aliases: ['ts'],\n parserLanguage: getTypeScriptGrammar('typescript'),\n },\n {\n name: 'tsx',\n parserLanguage: getTypeScriptGrammar('tsx'),\n },\n {\n name: 'python',\n aliases: ['py'],\n parserLanguage: normalizeGrammar(Python as unknown as GrammarModule),\n },\n {\n name: 'go',\n parserLanguage: normalizeGrammar(Go as unknown as GrammarModule),\n },\n {\n name: 'rust',\n aliases: ['rs'],\n parserLanguage: normalizeGrammar(Rust as unknown as GrammarModule),\n },\n].map((language) => ({\n functionNodeTypes: commonFunctionNodes,\n classNodeTypes: commonClassNodes,\n decisionNodeTypes: commonDecisionNodes,\n nestingNodeTypes: commonDecisionNodes,\n ...language,\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"],"names":["commonFunctionNodes","commonClassNodes","commonDecisionNodes","normalizeGrammar","module","value","key","Boolean","isGrammarWrapper","default","getTypeScriptGrammar","name","TypeScript","defaultLanguages","aliases","parserLanguage","JavaScript","Python","Go","Rust","map","language","functionNodeTypes","classNodeTypes","decisionNodeTypes","nestingNodeTypes","createLanguageRegistry","languages","registry","Map","set","alias","supportedLanguages"],"mappings":"4KASA,MAAMA,EAAsB,CAC1B,WACA,uBACA,sBACA,sBACA,gBACA,0BACA,sBACA,qBACA,oBACA,cACA,iBACA,qBACA,iCACA,SACA,oBACA,sBAGIC,EAAmB,CACvB,QACA,oBACA,mBACA,wBACA,aACA,cACA,YACA,cAGIC,EAAsB,CAC1B,eACA,cACA,iBACA,gBACA,mBACA,kBACA,eACA,eACA,gBACA,cACA,cACA,YACA,yBACA,qBACA,gBACA,mBACA,iBACA,mBAGF,SAASC,EAAiBC,GACxB,OAYF,SAA0BC,EAAsBC,GAC9C,GAAqB,iBAAVD,GAAgC,OAAVA,KAAoBC,KAAOD,GAC1D,OAAO,EAGT,OAAOE,QAASF,EAAyCC,GAC3D,CAlBME,CAAiBJ,EAAQ,WACpBA,EAAOK,QAGTL,CACT,CAEA,SAASM,EAAqBC,GAE5B,OADiBC,EACDD,EAClB,CAUO,MAAME,EAAkD,CAC7D,CACEF,KAAM,aACNG,QAAS,CAAC,KAAM,MAAO,OACvBC,eAAgBZ,EAAiBa,IAEnC,CACEL,KAAM,MACNI,eAAgBZ,EAAiBa,IAEnC,CACEL,KAAM,aACNG,QAAS,CAAC,MACVC,eAAgBL,EAAqB,eAEvC,CACEC,KAAM,MACNI,eAAgBL,EAAqB,QAEvC,CACEC,KAAM,SACNG,QAAS,CAAC,MACVC,eAAgBZ,EAAiBc,IAEnC,CACEN,KAAM,KACNI,eAAgBZ,EAAiBe,IAEnC,CACEP,KAAM,OACNG,QAAS,CAAC,MACVC,eAAgBZ,EAAiBgB,KAEnCC,IAAKC,IAAQ,CACbC,kBAAmBtB,EACnBuB,eAAgBtB,EAChBuB,kBAAmBtB,EACnBuB,iBAAkBvB,KACfmB,KAGE,SAASK,EACdC,EAA2Cd,GAE3C,MAAMe,EAAW,IAAIC,IAErB,IAAK,MAAMR,KAAYM,EAAW,CAChCC,EAASE,IAAIT,EAASV,KAAMU,GAC5B,IAAK,MAAMU,KAASV,EAASP,SAAW,GACtCc,EAASE,IAAIC,EAAOV,EAExB,CAEA,OAAOO,CACT,CAEO,MAAMI,EAAqBnB,EAAiBO,IAAKC,GAAaA,EAASV"}
|