code-gauge 10.0.0 → 10.2.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 +56 -34
- package/THIRD-PARTY-NOTICES.txt +8 -8
- package/dist/checkCommand.cjs +2 -2
- package/dist/checkCommand.cjs.map +1 -1
- package/dist/checkCommand.d.ts +2 -5
- package/dist/checkCommand.js +2 -2
- package/dist/checkCommand.js.map +1 -1
- package/dist/crossFileDuplication.cjs +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.d.ts +14 -2
- package/dist/crossFileDuplication.js +1 -1
- package/dist/crossFileDuplication.js.map +1 -1
- package/dist/crossFileNearMiss.cjs +1 -1
- package/dist/crossFileNearMiss.cjs.map +1 -1
- package/dist/crossFileNearMiss.d.ts +6 -4
- package/dist/crossFileNearMiss.js +1 -1
- package/dist/crossFileNearMiss.js.map +1 -1
- package/dist/duplication.cjs +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.d.ts +22 -7
- package/dist/duplication.js +1 -1
- package/dist/duplication.js.map +1 -1
- package/dist/nativeMetrics.cjs +1 -1
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.d.ts +1 -1
- package/dist/nativeMetrics.js +1 -1
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/thresholdCheck.cjs +1 -1
- package/dist/thresholdCheck.cjs.map +1 -1
- package/dist/thresholdCheck.d.ts +10 -8
- package/dist/thresholdCheck.js +1 -1
- package/dist/thresholdCheck.js.map +1 -1
- package/dist/thresholds.cjs +1 -1
- package/dist/thresholds.cjs.map +1 -1
- package/dist/thresholds.d.ts +5 -3
- package/dist/thresholds.js +1 -1
- package/dist/thresholds.js.map +1 -1
- package/dist/types.d.ts +29 -7
- package/dist/wasmBinding.cjs +1 -1
- package/dist/wasmBinding.js +1 -1
- package/native/Cargo.lock +16 -16
- package/native/Cargo.toml +8 -8
- package/native/code-gauge.wasm +0 -0
- package/native/src/complexity.rs +10 -1
- package/native/src/container.rs +158 -7
- package/native/src/dep_degree.rs +13 -14
- package/native/src/duplication.rs +213 -85
- package/native/src/functions.rs +112 -24
- package/native/src/languages.rs +5 -0
- package/native/src/lib.rs +1 -1
- package/native/src/measure.rs +9 -4
- package/native/src/near_miss.rs +303 -52
- package/native/src/types.rs +3 -0
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thresholdCheck.cjs","names":["functionThresholds","metricNameOf","fileThresholds","levels","duplicationThreshold"],"sources":["../src/thresholdCheck.ts"],"sourcesContent":["import assert from 'node:assert';\nimport type { CrossFileDuplicateBlockGroup, CrossFileDuplicationMetrics } from './crossFileDuplication.js';\nimport type { LineHunk } from './git.js';\nimport {\n duplicationThreshold,\n fileThresholds,\n functionThresholds,\n levels,\n metricNameOf,\n type Level,\n type LimitsByLevel,\n type Threshold,\n} from './thresholds.js';\nimport type { CodeMetrics, CognitiveBlock, FunctionMetrics } from './types.js';\n\n/** A measured file to check, under the path cross-file duplication knows it by. */\nexport interface CheckedFile {\n file: string;\n metrics: CodeMetrics;\n /** The change to the file; when set, only what the change touches is checked. */\n hunks?: readonly LineHunk[];\n}\n\n/**\n * By file path, the 1-based lines that are neither blank nor comment-only, by which duplicated\n * blocks are measured and their copies ranked: of the checked files and of every other measured\n * file a copy may lie in.\n */\nexport type CodeLineNumbersByFile = ReadonlyMap<string, ReadonlySet<number>>;\n\nexport interface ExceededLimit {\n metric: string;\n value: number;\n /** The most severe level whose limit the value violates. */\n level: Level;\n /** That level's limit: the value is allowed up to it; a duplicated block is allowed below it. */\n limit: number;\n}\n\nexport interface BlockLocation {\n file: string;\n startLine: number;\n endLine: number;\n}\n\nexport interface Violation extends BlockLocation {\n kind: 'function' | 'file' | 'duplication';\n /** The most severe level among `exceeded`. */\n level: Level;\n /** The function's name, qualified with the class it is a member of, for kind `function`. */\n name?: string;\n exceeded: ExceededLimit[];\n /**\n * The parts adding the most to the cognitive complexity, largest first, for a function that\n * exceeds a limit of it and has such parts: operator sequences and jumps outside any branching\n * construct add to the complexity without being one.\n */\n largestBlocks?: CognitiveBlock[];\n /** The other copies of the block, most code lines first, for kind `duplication`. */\n partners?: BlockLocation[];\n}\n\nexport interface CheckResult {\n violations: Violation[];\n checkedFunctionCount: number;\n}\n\nconst violationKinds: readonly Violation['kind'][] = ['file', 'function', 'duplication'];\n\nconst cognitiveComplexityMetric = 'functionCognitiveComplexity';\n\nconst nestedInclusiveMetrics = new Set(\n functionThresholds\n .filter(({ includesNestedFunctions }) => includesNestedFunctions)\n .map((threshold) => metricNameOf(threshold))\n);\n\n/** Every limit the files exceed, errors first, then ordered by path, then line. */\nexport function checkThresholds(\n files: readonly CheckedFile[],\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n limitsOf: (language: string) => LimitsByLevel,\n codeLineNumbersByFile: CodeLineNumbersByFile\n): CheckResult {\n const crossFileGroupsByFile = indexGroupsByFile(crossFileDuplication, new Set(files.map(({ file }) => file)));\n const violations: Violation[] = [];\n let checkedFunctionCount = 0;\n for (const checkedFile of files) {\n const { file, metrics, hunks } = checkedFile;\n const limits = limitsOf(metrics.language);\n // A rename or a mode change lists the file as changed without touching any of its lines.\n const fileExceeded = hunks?.length === 0 ? [] : collectExceeded(fileThresholds, metrics, limits);\n if (fileExceeded.length > 0) {\n violations.push(toViolation({ file, startLine: 1, endLine: metrics.lines.total }, 'file', fileExceeded));\n }\n const checkedFunctions = listCheckedFunctions(checkedFile);\n checkedFunctionCount += checkedFunctions.length;\n const exceedingFunctions = checkedFunctions\n .map((fn) => ({ fn, exceeded: collectExceeded(functionThresholds, fn, limits) }))\n .filter(({ exceeded }) => exceeded.length > 0);\n for (const { fn, exceeded } of exceedingFunctions) {\n const ownExceeded = exceeded.filter((limit) => !isReportedByEnclosingFunction(limit, fn, exceedingFunctions));\n if (ownExceeded.length > 0) {\n violations.push(toFunctionViolation(file, fn, ownExceeded));\n }\n }\n violations.push(\n ...collectDuplicationViolations(checkedFile, crossFileGroupsByFile.get(file) ?? [], limits, codeLineNumbersByFile)\n );\n }\n return { violations: violations.toSorted(compareViolations), checkedFunctionCount };\n}\n\n// The kind is compared before the end line because a file violation's end line is corrected after\n// this ordering (to the last line that exists), and the order must not depend on that correction.\nfunction compareViolations(left: Violation, right: Violation): number {\n return (\n levels.indexOf(right.level) - levels.indexOf(left.level) ||\n compareStrings(left.file, right.file) ||\n left.startLine - right.startLine ||\n violationKinds.indexOf(left.kind) - violationKinds.indexOf(right.kind) ||\n left.endLine - right.endLine\n );\n}\n\n/** The file's functions or, for a changed file, those the change touches. */\nfunction listCheckedFunctions({ metrics, hunks }: CheckedFile): FunctionMetrics[] {\n const endsByIndentation = metrics.language === 'python';\n return hunks\n ? metrics.functions.filter((fn) =>\n hunks.some((hunk) => touchesSpan(hunk, fn.startLine, fn.endLine, endsByIndentation))\n )\n : metrics.functions;\n}\n\n/**\n * Whether a function enclosing `fn` exceeds the same limit with a value that covers `fn`, so\n * reporting it for `fn` too would count the same code twice. The enclosing value is at least that\n * of `fn` and both share the file's limits, so its level is never milder.\n */\nfunction isReportedByEnclosingFunction(\n limit: ExceededLimit,\n fn: FunctionMetrics,\n exceedingFunctions: readonly { fn: FunctionMetrics; exceeded: ExceededLimit[] }[]\n): boolean {\n if (!nestedInclusiveMetrics.has(limit.metric)) {\n return false;\n }\n return exceedingFunctions.some(\n (outer) => outer.fn !== fn && encloses(outer.fn, fn) && outer.exceeded.some(({ metric }) => metric === limit.metric)\n );\n}\n\nfunction encloses(outer: FunctionMetrics, inner: FunctionMetrics): boolean {\n const startsBefore =\n outer.startLine < inner.startLine ||\n (outer.startLine === inner.startLine && outer.startColumn <= inner.startColumn);\n const endsAfter =\n outer.endLine > inner.endLine || (outer.endLine === inner.endLine && outer.endColumn >= inner.endColumn);\n return startsBefore && endsAfter;\n}\n\nfunction toFunctionViolation(file: string, fn: FunctionMetrics, exceeded: ExceededLimit[]): Violation {\n const { startLine, endLine } = fn;\n const exceedsCognitiveComplexity = exceeded.some(({ metric }) => metric === cognitiveComplexityMetric);\n return {\n ...toViolation({ file, startLine, endLine }, 'function', exceeded),\n name: qualifyFunctionName(fn),\n ...(exceedsCognitiveComplexity && fn.cognitiveBlocks.length > 0 && { largestBlocks: fn.cognitiveBlocks }),\n };\n}\n\n/** How reports name a function: with the type or module it is a member of, so that it can be found. */\nexport function qualifyFunctionName({ name = '<anonymous>', containerName }: FunctionMetrics): string {\n return containerName ? `${containerName}.${name}` : name;\n}\n\nfunction toViolation(location: BlockLocation, kind: Violation['kind'], exceeded: ExceededLimit[]): Violation {\n const level = exceeded.some((limit) => limit.level === 'error') ? 'error' : 'warning';\n return { kind, level, ...location, exceeded };\n}\n\nfunction collectExceeded<Subject>(\n thresholds: readonly Threshold<Subject>[],\n subject: Subject,\n limits: LimitsByLevel\n): ExceededLimit[] {\n return thresholds.flatMap((threshold) => findExceeded(threshold, subject, limits, (value, limit) => value > limit));\n}\n\n/** The most severe violated limit of the threshold, if any. */\nfunction findExceeded<Subject>(\n threshold: Threshold<Subject>,\n subject: Subject,\n limits: LimitsByLevel,\n violates: (value: number, limit: number) => boolean\n): ExceededLimit[] {\n const value = threshold.measure(subject);\n const level = levels.findLast((candidate) => violates(value, limits[candidate][threshold.key] as number));\n return level\n ? [{ metric: metricNameOf(threshold), value, level, limit: limits[level][threshold.key] as number }]\n : [];\n}\n\n/**\n * Whether the hunk changes the head lines [startLine, endLine]. A hunk without head lines (a pure\n * deletion) sits between its headStart line and the next one. Where indentation ends a span, its\n * last line is a statement rather than a closing delimiter, so a deletion right after that line may\n * have removed the span's tail; `endsByIndentation` counts it as touching, since the head alone\n * cannot tell it from a deletion after the span.\n */\nfunction touchesSpan(hunk: LineHunk, startLine: number, endLine: number, endsByIndentation = false): boolean {\n return hunk.headCount === 0\n ? hunk.headStart >= startLine && (hunk.headStart < endLine || (endsByIndentation && hunk.headStart === endLine))\n : hunk.headStart <= endLine && hunk.headStart + hunk.headCount > startLine;\n}\n\n/** The cross-file groups with an occurrence in each of the given files. */\nfunction indexGroupsByFile(\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n files: ReadonlySet<string>\n): Map<string, CrossFileDuplicateBlockGroup[]> {\n const groupsByFile = new Map<string, CrossFileDuplicateBlockGroup[]>();\n for (const group of crossFileDuplication?.groups ?? []) {\n for (const file of group.files) {\n if (files.has(file)) {\n const groups = groupsByFile.get(file) ?? [];\n groups.push(group);\n groupsByFile.set(file, groups);\n }\n }\n }\n return groupsByFile;\n}\n\n/**\n * One violation per duplicated region of the file: its clone occurrences, within-file and\n * cross-file, that hold at least the code lines of a level (with `hunks`, only those overlapping\n * added lines), with overlapping occurrences merged into one region.\n */\nfunction collectDuplicationViolations(\n { file, metrics, hunks }: CheckedFile,\n crossFileGroups: readonly CrossFileDuplicateBlockGroup[],\n limits: LimitsByLevel,\n codeLineNumbersByFile: CodeLineNumbersByFile\n): Violation[] {\n const findExceededLines = (block: BlockLocation): ExceededLimit[] =>\n findExceeded(\n duplicationThreshold,\n { codeLineCount: countCodeLines(block, codeLineNumbersByFile) },\n limits,\n (value, limit) => value >= limit\n );\n const groups: BlockLocation[][] = [\n ...metrics.duplication.duplicateBlockGroups.map((group) => group.map((occurrence) => ({ ...occurrence, file }))),\n ...crossFileGroups.map((group) => group.occurrences),\n ];\n const isReported = (block: BlockLocation): boolean =>\n block.file === file &&\n findExceededLines(block).length > 0 &&\n (!hunks || hunks.some((hunk) => hunk.headCount > 0 && touchesSpan(hunk, block.startLine, block.endLine)));\n const copiesByBlock = new Map<BlockLocation, BlockLocation[]>();\n for (const group of groups) {\n for (const block of group.filter((occurrence) => isReported(occurrence))) {\n copiesByBlock.set(block, group);\n }\n }\n return mergeOverlapping([...copiesByBlock.keys()]).map(({ merged, sources }) => ({\n ...toViolation(merged, 'duplication', findExceededLines(merged)),\n // The region's own occurrences are among the copies of the groups it belongs to.\n // The region merges every clone that overlaps it, so its copies range from whole copies of it\n // to a few lines matching one fragment; the largest say the most about what to share.\n partners: mergeOverlapping(sources.flatMap((source) => copiesByBlock.get(source) ?? []))\n .map((partner) => partner.merged)\n .filter((partner) => !overlaps(partner, merged))\n .toSorted(\n (left, right) => countCodeLines(right, codeLineNumbersByFile) - countCodeLines(left, codeLineNumbersByFile)\n ),\n }));\n}\n\n/** The locations with those overlapping in the same file merged, ordered by file, then line. */\nfunction mergeOverlapping(locations: readonly BlockLocation[]): { merged: BlockLocation; sources: BlockLocation[] }[] {\n const regions: { merged: BlockLocation; sources: BlockLocation[] }[] = [];\n const ordered = locations.toSorted(\n (left, right) => compareStrings(left.file, right.file) || left.startLine - right.startLine\n );\n for (const location of ordered) {\n const last = regions.at(-1);\n if (last && overlaps(last.merged, location)) {\n last.merged.endLine = Math.max(last.merged.endLine, location.endLine);\n last.sources.push(location);\n } else {\n const { file, startLine, endLine } = location;\n regions.push({ merged: { file, startLine, endLine }, sources: [location] });\n }\n }\n return regions;\n}\n\n/** The length of a duplicated block. */\nfunction countCodeLines(block: BlockLocation, codeLineNumbersByFile: CodeLineNumbersByFile): number {\n const codeLineNumbers = codeLineNumbersByFile.get(block.file);\n assert.ok(codeLineNumbers, `No code lines for ${block.file}, which holds a duplicated block.`);\n let count = 0;\n for (let line = block.startLine; line <= block.endLine; line++) {\n if (codeLineNumbers.has(line)) count += 1;\n }\n return count;\n}\n\nfunction overlaps(left: BlockLocation, right: BlockLocation): boolean {\n return left.file === right.file && left.startLine <= right.endLine && right.startLine <= left.endLine;\n}\n\n/** Code-unit order, so reports do not depend on the locale. */\nfunction compareStrings(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"thresholdCheck.cjs","names":["functionThresholds","metricNameOf","fileThresholds","levels","duplicationThreshold"],"sources":["../src/thresholdCheck.ts"],"sourcesContent":["import assert from 'node:assert';\nimport type {\n CrossFileDuplicateBlockGroup,\n CrossFileDuplicateOccurrence,\n CrossFileDuplicationMetrics,\n} from './crossFileDuplication.js';\nimport type { LineHunk } from './git.js';\nimport {\n duplicationThreshold,\n fileThresholds,\n functionThresholds,\n levels,\n metricNameOf,\n type Level,\n type LimitsByLevel,\n type Threshold,\n} from './thresholds.js';\nimport type { CodeMetrics, CognitiveBlock, FunctionMetrics } from './types.js';\n\n/** A measured file to check, under the path cross-file duplication knows it by. */\nexport interface CheckedFile {\n file: string;\n metrics: CodeMetrics;\n /** The change to the file; when set, only what the change touches is checked. */\n hunks?: readonly LineHunk[];\n}\n\n/** The duplication of the measured files, in which duplicated blocks and their copies are found. */\nexport interface CheckedDuplication {\n crossFile?: CrossFileDuplicationMetrics;\n /** The detection's `minSimilarityPercent`, the share of a block a copy of it holds at least. */\n minSimilarityPercent: number;\n}\n\nexport interface ExceededLimit {\n metric: string;\n /** What reports call the metric (`cognitive complexity`, `duplicated lines`). */\n label: string;\n value: number;\n /** The most severe level whose limit the value violates. */\n level: Level;\n /** That level's limit: the value is allowed up to it; a duplicated block is allowed below it. */\n limit: number;\n}\n\nexport interface BlockLocation {\n file: string;\n startLine: number;\n endLine: number;\n}\n\n/** One copy of a clone group. */\ninterface CloneOccurrence extends BlockLocation {\n /**\n * Its code lines matched in another copy, whose number is the length of a duplicated block. Its\n * line span would also count the lines the copies do not share.\n */\n lineNumbers: readonly number[];\n tokenCount: number;\n}\n\nexport interface Violation extends BlockLocation {\n kind: 'function' | 'file' | 'duplication';\n /** The most severe level among `exceeded`. */\n level: Level;\n /** The function's name, qualified with the class it is a member of, for kind `function`. */\n name?: string;\n exceeded: ExceededLimit[];\n /**\n * The parts adding the most to the cognitive complexity, largest first, for a function that\n * exceeds a limit of it and has such parts: operator sequences and jumps outside any branching\n * construct add to the complexity without being one.\n */\n largestBlocks?: CognitiveBlock[];\n /** The other copies of the block, most matched tokens first, for kind `duplication`. */\n partners?: BlockLocation[];\n}\n\nexport interface CheckResult {\n violations: Violation[];\n checkedFunctionCount: number;\n}\n\nconst violationKinds: readonly Violation['kind'][] = ['file', 'function', 'duplication'];\n\nconst cognitiveComplexityMetric = 'functionCognitiveComplexity';\n\nconst nestedInclusiveMetrics = new Set(\n functionThresholds\n .filter(({ includesNestedFunctions }) => includesNestedFunctions)\n .map((threshold) => metricNameOf(threshold))\n);\n\n/** Every limit the files exceed, errors first, then ordered by path, then line. */\nexport function checkThresholds(\n files: readonly CheckedFile[],\n duplication: CheckedDuplication,\n limitsOf: (language: string) => LimitsByLevel\n): CheckResult {\n const crossFileGroupsByFile = indexGroupsByFile(duplication.crossFile, new Set(files.map(({ file }) => file)));\n const violations: Violation[] = [];\n let checkedFunctionCount = 0;\n for (const checkedFile of files) {\n const { file, metrics, hunks } = checkedFile;\n const limits = limitsOf(metrics.language);\n // A rename or a mode change lists the file as changed without touching any of its lines.\n const fileExceeded = hunks?.length === 0 ? [] : collectExceeded(fileThresholds, metrics, limits);\n if (fileExceeded.length > 0) {\n violations.push(toViolation({ file, startLine: 1, endLine: metrics.lines.total }, 'file', fileExceeded));\n }\n const checkedFunctions = listCheckedFunctions(checkedFile);\n checkedFunctionCount += checkedFunctions.length;\n const exceedingFunctions = checkedFunctions\n .map((fn) => ({ fn, exceeded: collectExceeded(functionThresholds, fn, limits) }))\n .filter(({ exceeded }) => exceeded.length > 0);\n for (const { fn, exceeded } of exceedingFunctions) {\n const ownExceeded = exceeded.filter((limit) => !isReportedByEnclosingFunction(limit, fn, exceedingFunctions));\n if (ownExceeded.length > 0) {\n violations.push(toFunctionViolation(file, fn, ownExceeded));\n }\n }\n violations.push(\n ...collectDuplicationViolations(checkedFile, crossFileGroupsByFile.get(file) ?? [], limits, duplication)\n );\n }\n return { violations: violations.toSorted(compareViolations), checkedFunctionCount };\n}\n\n// The kind is compared before the end line because a file violation's end line is corrected after\n// this ordering (to the last line that exists), and the order must not depend on that correction.\nfunction compareViolations(left: Violation, right: Violation): number {\n return (\n levels.indexOf(right.level) - levels.indexOf(left.level) ||\n compareStrings(left.file, right.file) ||\n left.startLine - right.startLine ||\n violationKinds.indexOf(left.kind) - violationKinds.indexOf(right.kind) ||\n left.endLine - right.endLine\n );\n}\n\n/** The file's functions or, for a changed file, those the change touches. */\nfunction listCheckedFunctions({ metrics, hunks }: CheckedFile): FunctionMetrics[] {\n const endsByIndentation = metrics.language === 'python';\n return hunks\n ? metrics.functions.filter((fn) =>\n hunks.some((hunk) => touchesSpan(hunk, fn.startLine, fn.endLine, endsByIndentation))\n )\n : metrics.functions;\n}\n\n/**\n * Whether a function enclosing `fn` exceeds the same limit with a value that covers `fn`, so\n * reporting it for `fn` too would count the same code twice. The enclosing value is at least that\n * of `fn` and both share the file's limits, so its level is never milder.\n */\nfunction isReportedByEnclosingFunction(\n limit: ExceededLimit,\n fn: FunctionMetrics,\n exceedingFunctions: readonly { fn: FunctionMetrics; exceeded: ExceededLimit[] }[]\n): boolean {\n if (!nestedInclusiveMetrics.has(limit.metric)) {\n return false;\n }\n return exceedingFunctions.some(\n (outer) => outer.fn !== fn && encloses(outer.fn, fn) && outer.exceeded.some(({ metric }) => metric === limit.metric)\n );\n}\n\nfunction encloses(outer: FunctionMetrics, inner: FunctionMetrics): boolean {\n const startsBefore =\n outer.startLine < inner.startLine ||\n (outer.startLine === inner.startLine && outer.startColumn <= inner.startColumn);\n const endsAfter =\n outer.endLine > inner.endLine || (outer.endLine === inner.endLine && outer.endColumn >= inner.endColumn);\n return startsBefore && endsAfter;\n}\n\nfunction toFunctionViolation(file: string, fn: FunctionMetrics, exceeded: ExceededLimit[]): Violation {\n const { startLine, endLine } = fn;\n const exceedsCognitiveComplexity = exceeded.some(({ metric }) => metric === cognitiveComplexityMetric);\n return {\n ...toViolation({ file, startLine, endLine }, 'function', exceeded),\n name: qualifyFunctionName(fn),\n ...(exceedsCognitiveComplexity && fn.cognitiveBlocks.length > 0 && { largestBlocks: fn.cognitiveBlocks }),\n };\n}\n\n/** How reports name a function: with the type or module it is a member of, so that it can be found. */\nexport function qualifyFunctionName({ name = '<anonymous>', containerName }: FunctionMetrics): string {\n return containerName ? `${containerName}.${name}` : name;\n}\n\nfunction toViolation(location: BlockLocation, kind: Violation['kind'], exceeded: ExceededLimit[]): Violation {\n const level = exceeded.some((limit) => limit.level === 'error') ? 'error' : 'warning';\n return { kind, level, ...location, exceeded };\n}\n\nfunction collectExceeded<Subject>(\n thresholds: readonly Threshold<Subject>[],\n subject: Subject,\n limits: LimitsByLevel\n): ExceededLimit[] {\n return thresholds.flatMap((threshold) => findExceeded(threshold, subject, limits, (value, limit) => value > limit));\n}\n\n/** The most severe violated limit of the threshold, if any. */\nfunction findExceeded<Subject>(\n threshold: Threshold<Subject>,\n subject: Subject,\n limits: LimitsByLevel,\n violates: (value: number, limit: number) => boolean\n): ExceededLimit[] {\n const value = threshold.measure(subject);\n const level = levels.findLast((candidate) => violates(value, limits[candidate][threshold.key] as number));\n return level\n ? [\n {\n metric: metricNameOf(threshold),\n label: threshold.label,\n value,\n level,\n limit: limits[level][threshold.key] as number,\n },\n ]\n : [];\n}\n\n/**\n * Whether the hunk changes the head lines [startLine, endLine]. A hunk without head lines (a pure\n * deletion) sits between its headStart line and the next one. Where indentation ends a span, its\n * last line is a statement rather than a closing delimiter, so a deletion right after that line may\n * have removed the span's tail; `endsByIndentation` counts it as touching, since the head alone\n * cannot tell it from a deletion after the span.\n */\nfunction touchesSpan(hunk: LineHunk, startLine: number, endLine: number, endsByIndentation = false): boolean {\n return hunk.headCount === 0\n ? hunk.headStart >= startLine && (hunk.headStart < endLine || (endsByIndentation && hunk.headStart === endLine))\n : hunk.headStart <= endLine && hunk.headStart + hunk.headCount > startLine;\n}\n\n/** The cross-file groups with an occurrence in each of the given files. */\nfunction indexGroupsByFile(\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n files: ReadonlySet<string>\n): Map<string, CrossFileDuplicateBlockGroup[]> {\n const groupsByFile = new Map<string, CrossFileDuplicateBlockGroup[]>();\n for (const group of crossFileDuplication?.groups ?? []) {\n for (const file of group.files) {\n if (files.has(file)) {\n const groups = groupsByFile.get(file) ?? [];\n groups.push(group);\n groupsByFile.set(file, groups);\n }\n }\n }\n return groupsByFile;\n}\n\n/**\n * One violation per duplicated region of the file: its clone occurrences, within-file and\n * cross-file, that hold at least the duplicated lines of a level (with `hunks`, only those\n * overlapping added lines), with overlapping occurrences merged into one region unless they are\n * copies of each other.\n */\nfunction collectDuplicationViolations(\n { file, metrics, hunks }: CheckedFile,\n crossFileGroups: readonly CrossFileDuplicateBlockGroup[],\n limits: LimitsByLevel,\n { minSimilarityPercent }: CheckedDuplication\n): Violation[] {\n const findExceededLines = (duplicatedLineCount: number): ExceededLimit[] =>\n findExceeded(duplicationThreshold, { duplicatedLineCount }, limits, (value, limit) => value >= limit);\n const groups: CloneOccurrence[][] = [\n ...metrics.duplication.duplicateBlockGroups.map((group) => group.map((occurrence) => ({ ...occurrence, file }))),\n ...crossFileGroups.map((group) => group.occurrences.map((occurrence) => toCloneOccurrence(occurrence))),\n ];\n const isReported = (block: CloneOccurrence): boolean =>\n block.file === file &&\n findExceededLines(block.lineNumbers.length).length > 0 &&\n (!hunks || hunks.some((hunk) => hunk.headCount > 0 && touchesSpan(hunk, block.startLine, block.endLine)));\n const groupOf = new Map(groups.flatMap((group) => group.map((occurrence) => [occurrence, group] as const)));\n // Copies of one group that share a line (the branches of an `else if` chain, the entries of a\n // list) stay apart: merged, they would be one block without a copy. One lying within another is\n // a repetition inside that block, not a block beside it.\n const areCopies = (left: CloneOccurrence, right: CloneOccurrence): boolean =>\n groupOf.get(left) === groupOf.get(right) && !contains(left, right) && !contains(right, left);\n const reportedBlocks = groups.flat().filter((occurrence) => isReported(occurrence));\n return mergeOverlapping(reportedBlocks, areCopies).map(({ merged, sources }) => {\n const listCopies = (minTokenPercent: number): BlockLocation[] =>\n mergeOverlapping(\n sources.flatMap((source) => {\n // The region merges every clone that overlaps it, so a copy of an occurrence lying\n // within a larger one is measured against the larger one.\n const tokenCount = Math.max(\n ...sources.filter((outer) => contains(outer, source)).map((outer) => outer.tokenCount)\n );\n return (groupOf.get(source) ?? []).filter(\n (copy) =>\n copy.tokenCount * 100 >= tokenCount * minTokenPercent &&\n // The region's own occurrences are among the copies of the groups it belongs to, and\n // so may be an occurrence within one of them that is not reported itself: a\n // repetition inside the block, not another place.\n !sources.some((outer) => contains(outer, copy))\n );\n }),\n areCopies\n )\n // The largest say the most about what to share.\n .toSorted((left, right) => sumTokens(right.sources) - sumTokens(left.sources))\n .map((partner) => partner.merged);\n // A group joins a block with every place repeating a part of it: a function another file\n // copies whole is grouped with each file holding a few of its lines. Such a fragment is not a\n // copy of the block: sharing it would not remove the block, and a report listing it reads as\n // one more copy. Sizes are compared in tokens, since the same code may be laid out over any\n // number of lines.\n const copies = listCopies(minSimilarityPercent);\n return {\n ...toViolation(\n merged,\n 'duplication',\n // Occurrences that overlap share lines, which count once.\n findExceededLines(new Set(sources.flatMap((source) => source.lineNumbers)).size)\n ),\n // A block whose parts were matched with different places has no copy of its size. The\n // places then are all that locates what it repeats.\n partners: copies.length > 0 ? copies : listCopies(0),\n };\n });\n}\n\nfunction toCloneOccurrence(occurrence: CrossFileDuplicateOccurrence): CloneOccurrence {\n const { lineNumbers } = occurrence;\n assert.ok(lineNumbers, `No matched lines for ${occurrence.file}, a scanned file.`);\n return { ...occurrence, lineNumbers };\n}\n\nfunction sumTokens(occurrences: readonly CloneOccurrence[]): number {\n return occurrences.reduce((sum, occurrence) => sum + occurrence.tokenCount, 0);\n}\n\n/**\n * The locations with those overlapping in the same file merged, except those `areCopies` holds\n * for, ordered by file, then line.\n */\nfunction mergeOverlapping(\n locations: readonly CloneOccurrence[],\n areCopies: (left: CloneOccurrence, right: CloneOccurrence) => boolean\n): { merged: BlockLocation; sources: CloneOccurrence[] }[] {\n const regions: { merged: BlockLocation; sources: CloneOccurrence[] }[] = [];\n const ordered = locations.toSorted(\n (left, right) => compareStrings(left.file, right.file) || left.startLine - right.startLine\n );\n for (const location of ordered) {\n const last = regions.at(-1);\n // A block containing the location holds it whatever it is a copy of.\n const joins =\n last &&\n overlaps(last.merged, location) &&\n (last.sources.some((source) => contains(source, location)) ||\n !last.sources.some((source) => areCopies(source, location)));\n if (joins) {\n last.merged.endLine = Math.max(last.merged.endLine, location.endLine);\n last.sources.push(location);\n } else {\n const { file, startLine, endLine } = location;\n regions.push({ merged: { file, startLine, endLine }, sources: [location] });\n }\n }\n return regions;\n}\n\n/** Whether `outer` spans every line of `inner`; a block contains itself. */\nfunction contains(outer: BlockLocation, inner: BlockLocation): boolean {\n return outer.file === inner.file && outer.startLine <= inner.startLine && inner.endLine <= outer.endLine;\n}\n\nfunction overlaps(left: BlockLocation, right: BlockLocation): boolean {\n return left.file === right.file && left.startLine <= right.endLine && right.startLine <= left.endLine;\n}\n\n/** Code-unit order, so reports do not depend on the locale. */\nfunction compareStrings(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"],"mappings":"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"}
|
package/dist/thresholdCheck.d.ts
CHANGED
|
@@ -9,14 +9,16 @@ export interface CheckedFile {
|
|
|
9
9
|
/** The change to the file; when set, only what the change touches is checked. */
|
|
10
10
|
hunks?: readonly LineHunk[];
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
/** The duplication of the measured files, in which duplicated blocks and their copies are found. */
|
|
13
|
+
export interface CheckedDuplication {
|
|
14
|
+
crossFile?: CrossFileDuplicationMetrics;
|
|
15
|
+
/** The detection's `minSimilarityPercent`, the share of a block a copy of it holds at least. */
|
|
16
|
+
minSimilarityPercent: number;
|
|
17
|
+
}
|
|
18
18
|
export interface ExceededLimit {
|
|
19
19
|
metric: string;
|
|
20
|
+
/** What reports call the metric (`cognitive complexity`, `duplicated lines`). */
|
|
21
|
+
label: string;
|
|
20
22
|
value: number;
|
|
21
23
|
/** The most severe level whose limit the value violates. */
|
|
22
24
|
level: Level;
|
|
@@ -41,7 +43,7 @@ export interface Violation extends BlockLocation {
|
|
|
41
43
|
* construct add to the complexity without being one.
|
|
42
44
|
*/
|
|
43
45
|
largestBlocks?: CognitiveBlock[];
|
|
44
|
-
/** The other copies of the block, most
|
|
46
|
+
/** The other copies of the block, most matched tokens first, for kind `duplication`. */
|
|
45
47
|
partners?: BlockLocation[];
|
|
46
48
|
}
|
|
47
49
|
export interface CheckResult {
|
|
@@ -49,6 +51,6 @@ export interface CheckResult {
|
|
|
49
51
|
checkedFunctionCount: number;
|
|
50
52
|
}
|
|
51
53
|
/** Every limit the files exceed, errors first, then ordered by path, then line. */
|
|
52
|
-
export declare function checkThresholds(files: readonly CheckedFile[],
|
|
54
|
+
export declare function checkThresholds(files: readonly CheckedFile[], duplication: CheckedDuplication, limitsOf: (language: string) => LimitsByLevel): CheckResult;
|
|
53
55
|
/** How reports name a function: with the type or module it is a member of, so that it can be found. */
|
|
54
56
|
export declare function qualifyFunctionName({ name, containerName }: FunctionMetrics): string;
|
package/dist/thresholdCheck.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{duplicationThreshold as e,fileThresholds as t,functionThresholds as n,levels as r,metricNameOf as i}from"./thresholds.js";import a from"node:assert";const o=[`file`,`function`,`duplication`],s=new Set(n.filter(({includesNestedFunctions:e})=>e).map(e=>i(e)));function c(e,r,i
|
|
1
|
+
import{duplicationThreshold as e,fileThresholds as t,functionThresholds as n,levels as r,metricNameOf as i}from"./thresholds.js";import a from"node:assert";const o=[`file`,`function`,`duplication`],s=new Set(n.filter(({includesNestedFunctions:e})=>e).map(e=>i(e)));function c(e,r,i){let a=y(r.crossFile,new Set(e.map(({file:e})=>e))),o=[],s=0;for(let c of e){let{file:e,metrics:l,hunks:f}=c,m=i(l.language),_=f?.length===0?[]:g(t,l,m);_.length>0&&o.push(h({file:e,startLine:1,endLine:l.lines.total},`file`,_));let v=u(c);s+=v.length;let y=v.map(e=>({fn:e,exceeded:g(n,e,m)})).filter(({exceeded:e})=>e.length>0);for(let{fn:t,exceeded:n}of y){let r=n.filter(e=>!d(e,t,y));r.length>0&&o.push(p(e,t,r))}o.push(...b(c,a.get(e)??[],m,r))}return{violations:o.toSorted(l),checkedFunctionCount:s}}function l(e,t){return r.indexOf(t.level)-r.indexOf(e.level)||E(e.file,t.file)||e.startLine-t.startLine||o.indexOf(e.kind)-o.indexOf(t.kind)||e.endLine-t.endLine}function u({metrics:e,hunks:t}){let n=e.language===`python`;return t?e.functions.filter(e=>t.some(t=>v(t,e.startLine,e.endLine,n))):e.functions}function d(e,t,n){return s.has(e.metric)?n.some(n=>n.fn!==t&&f(n.fn,t)&&n.exceeded.some(({metric:t})=>t===e.metric)):!1}function f(e,t){let n=e.startLine<t.startLine||e.startLine===t.startLine&&e.startColumn<=t.startColumn,r=e.endLine>t.endLine||e.endLine===t.endLine&&e.endColumn>=t.endColumn;return n&&r}function p(e,t,n){let{startLine:r,endLine:i}=t,a=n.some(({metric:e})=>e===`functionCognitiveComplexity`);return{...h({file:e,startLine:r,endLine:i},`function`,n),name:m(t),...a&&t.cognitiveBlocks.length>0&&{largestBlocks:t.cognitiveBlocks}}}function m({name:e=`<anonymous>`,containerName:t}){return t?`${t}.${e}`:e}function h(e,t,n){return{kind:t,level:n.some(e=>e.level===`error`)?`error`:`warning`,...e,exceeded:n}}function g(e,t,n){return e.flatMap(e=>_(e,t,n,(e,t)=>e>t))}function _(e,t,n,a){let o=e.measure(t),s=r.findLast(t=>a(o,n[t][e.key]));return s?[{metric:i(e),label:e.label,value:o,level:s,limit:n[s][e.key]}]:[]}function v(e,t,n,r=!1){return e.headCount===0?e.headStart>=t&&(e.headStart<n||r&&e.headStart===n):e.headStart<=n&&e.headStart+e.headCount>t}function y(e,t){let n=/* @__PURE__ */ new Map;for(let r of e?.groups??[])for(let e of r.files)if(t.has(e)){let t=n.get(e)??[];t.push(r),n.set(e,t)}return n}function b({file:t,metrics:n,hunks:r},i,a,{minSimilarityPercent:o}){let s=t=>_(e,{duplicatedLineCount:t},a,(e,t)=>e>=t),c=[...n.duplication.duplicateBlockGroups.map(e=>e.map(e=>({...e,file:t}))),...i.map(e=>e.occurrences.map(e=>x(e)))],l=e=>e.file===t&&s(e.lineNumbers.length).length>0&&(!r||r.some(t=>t.headCount>0&&v(t,e.startLine,e.endLine))),u=new Map(c.flatMap(e=>e.map(t=>[t,e]))),d=(e,t)=>u.get(e)===u.get(t)&&!w(e,t)&&!w(t,e);return C(c.flat().filter(e=>l(e)),d).map(({merged:e,sources:t})=>{let n=e=>C(t.flatMap(n=>{let r=Math.max(...t.filter(e=>w(e,n)).map(e=>e.tokenCount));return(u.get(n)??[]).filter(n=>n.tokenCount*100>=r*e&&!t.some(e=>w(e,n)))}),d).toSorted((e,t)=>S(t.sources)-S(e.sources)).map(e=>e.merged),r=n(o);return{...h(e,`duplication`,s(new Set(t.flatMap(e=>e.lineNumbers)).size)),partners:r.length>0?r:n(0)}})}function x(e){let{lineNumbers:t}=e;return a.ok(t,`No matched lines for ${e.file}, a scanned file.`),{...e,lineNumbers:t}}function S(e){return e.reduce((e,t)=>e+t.tokenCount,0)}function C(e,t){let n=[],r=e.toSorted((e,t)=>E(e.file,t.file)||e.startLine-t.startLine);for(let e of r){let r=n.at(-1);if(r&&T(r.merged,e)&&(r.sources.some(t=>w(t,e))||!r.sources.some(n=>t(n,e))))r.merged.endLine=Math.max(r.merged.endLine,e.endLine),r.sources.push(e);else{let{file:t,startLine:r,endLine:i}=e;n.push({merged:{file:t,startLine:r,endLine:i},sources:[e]})}}return n}function w(e,t){return e.file===t.file&&e.startLine<=t.startLine&&t.endLine<=e.endLine}function T(e,t){return e.file===t.file&&e.startLine<=t.endLine&&t.startLine<=e.endLine}function E(e,t){return e<t?-1:+(e>t)}export{c as checkThresholds,m as qualifyFunctionName};
|
|
2
2
|
//# sourceMappingURL=thresholdCheck.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thresholdCheck.js","names":[],"sources":["../src/thresholdCheck.ts"],"sourcesContent":["import assert from 'node:assert';\nimport type { CrossFileDuplicateBlockGroup, CrossFileDuplicationMetrics } from './crossFileDuplication.js';\nimport type { LineHunk } from './git.js';\nimport {\n duplicationThreshold,\n fileThresholds,\n functionThresholds,\n levels,\n metricNameOf,\n type Level,\n type LimitsByLevel,\n type Threshold,\n} from './thresholds.js';\nimport type { CodeMetrics, CognitiveBlock, FunctionMetrics } from './types.js';\n\n/** A measured file to check, under the path cross-file duplication knows it by. */\nexport interface CheckedFile {\n file: string;\n metrics: CodeMetrics;\n /** The change to the file; when set, only what the change touches is checked. */\n hunks?: readonly LineHunk[];\n}\n\n/**\n * By file path, the 1-based lines that are neither blank nor comment-only, by which duplicated\n * blocks are measured and their copies ranked: of the checked files and of every other measured\n * file a copy may lie in.\n */\nexport type CodeLineNumbersByFile = ReadonlyMap<string, ReadonlySet<number>>;\n\nexport interface ExceededLimit {\n metric: string;\n value: number;\n /** The most severe level whose limit the value violates. */\n level: Level;\n /** That level's limit: the value is allowed up to it; a duplicated block is allowed below it. */\n limit: number;\n}\n\nexport interface BlockLocation {\n file: string;\n startLine: number;\n endLine: number;\n}\n\nexport interface Violation extends BlockLocation {\n kind: 'function' | 'file' | 'duplication';\n /** The most severe level among `exceeded`. */\n level: Level;\n /** The function's name, qualified with the class it is a member of, for kind `function`. */\n name?: string;\n exceeded: ExceededLimit[];\n /**\n * The parts adding the most to the cognitive complexity, largest first, for a function that\n * exceeds a limit of it and has such parts: operator sequences and jumps outside any branching\n * construct add to the complexity without being one.\n */\n largestBlocks?: CognitiveBlock[];\n /** The other copies of the block, most code lines first, for kind `duplication`. */\n partners?: BlockLocation[];\n}\n\nexport interface CheckResult {\n violations: Violation[];\n checkedFunctionCount: number;\n}\n\nconst violationKinds: readonly Violation['kind'][] = ['file', 'function', 'duplication'];\n\nconst cognitiveComplexityMetric = 'functionCognitiveComplexity';\n\nconst nestedInclusiveMetrics = new Set(\n functionThresholds\n .filter(({ includesNestedFunctions }) => includesNestedFunctions)\n .map((threshold) => metricNameOf(threshold))\n);\n\n/** Every limit the files exceed, errors first, then ordered by path, then line. */\nexport function checkThresholds(\n files: readonly CheckedFile[],\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n limitsOf: (language: string) => LimitsByLevel,\n codeLineNumbersByFile: CodeLineNumbersByFile\n): CheckResult {\n const crossFileGroupsByFile = indexGroupsByFile(crossFileDuplication, new Set(files.map(({ file }) => file)));\n const violations: Violation[] = [];\n let checkedFunctionCount = 0;\n for (const checkedFile of files) {\n const { file, metrics, hunks } = checkedFile;\n const limits = limitsOf(metrics.language);\n // A rename or a mode change lists the file as changed without touching any of its lines.\n const fileExceeded = hunks?.length === 0 ? [] : collectExceeded(fileThresholds, metrics, limits);\n if (fileExceeded.length > 0) {\n violations.push(toViolation({ file, startLine: 1, endLine: metrics.lines.total }, 'file', fileExceeded));\n }\n const checkedFunctions = listCheckedFunctions(checkedFile);\n checkedFunctionCount += checkedFunctions.length;\n const exceedingFunctions = checkedFunctions\n .map((fn) => ({ fn, exceeded: collectExceeded(functionThresholds, fn, limits) }))\n .filter(({ exceeded }) => exceeded.length > 0);\n for (const { fn, exceeded } of exceedingFunctions) {\n const ownExceeded = exceeded.filter((limit) => !isReportedByEnclosingFunction(limit, fn, exceedingFunctions));\n if (ownExceeded.length > 0) {\n violations.push(toFunctionViolation(file, fn, ownExceeded));\n }\n }\n violations.push(\n ...collectDuplicationViolations(checkedFile, crossFileGroupsByFile.get(file) ?? [], limits, codeLineNumbersByFile)\n );\n }\n return { violations: violations.toSorted(compareViolations), checkedFunctionCount };\n}\n\n// The kind is compared before the end line because a file violation's end line is corrected after\n// this ordering (to the last line that exists), and the order must not depend on that correction.\nfunction compareViolations(left: Violation, right: Violation): number {\n return (\n levels.indexOf(right.level) - levels.indexOf(left.level) ||\n compareStrings(left.file, right.file) ||\n left.startLine - right.startLine ||\n violationKinds.indexOf(left.kind) - violationKinds.indexOf(right.kind) ||\n left.endLine - right.endLine\n );\n}\n\n/** The file's functions or, for a changed file, those the change touches. */\nfunction listCheckedFunctions({ metrics, hunks }: CheckedFile): FunctionMetrics[] {\n const endsByIndentation = metrics.language === 'python';\n return hunks\n ? metrics.functions.filter((fn) =>\n hunks.some((hunk) => touchesSpan(hunk, fn.startLine, fn.endLine, endsByIndentation))\n )\n : metrics.functions;\n}\n\n/**\n * Whether a function enclosing `fn` exceeds the same limit with a value that covers `fn`, so\n * reporting it for `fn` too would count the same code twice. The enclosing value is at least that\n * of `fn` and both share the file's limits, so its level is never milder.\n */\nfunction isReportedByEnclosingFunction(\n limit: ExceededLimit,\n fn: FunctionMetrics,\n exceedingFunctions: readonly { fn: FunctionMetrics; exceeded: ExceededLimit[] }[]\n): boolean {\n if (!nestedInclusiveMetrics.has(limit.metric)) {\n return false;\n }\n return exceedingFunctions.some(\n (outer) => outer.fn !== fn && encloses(outer.fn, fn) && outer.exceeded.some(({ metric }) => metric === limit.metric)\n );\n}\n\nfunction encloses(outer: FunctionMetrics, inner: FunctionMetrics): boolean {\n const startsBefore =\n outer.startLine < inner.startLine ||\n (outer.startLine === inner.startLine && outer.startColumn <= inner.startColumn);\n const endsAfter =\n outer.endLine > inner.endLine || (outer.endLine === inner.endLine && outer.endColumn >= inner.endColumn);\n return startsBefore && endsAfter;\n}\n\nfunction toFunctionViolation(file: string, fn: FunctionMetrics, exceeded: ExceededLimit[]): Violation {\n const { startLine, endLine } = fn;\n const exceedsCognitiveComplexity = exceeded.some(({ metric }) => metric === cognitiveComplexityMetric);\n return {\n ...toViolation({ file, startLine, endLine }, 'function', exceeded),\n name: qualifyFunctionName(fn),\n ...(exceedsCognitiveComplexity && fn.cognitiveBlocks.length > 0 && { largestBlocks: fn.cognitiveBlocks }),\n };\n}\n\n/** How reports name a function: with the type or module it is a member of, so that it can be found. */\nexport function qualifyFunctionName({ name = '<anonymous>', containerName }: FunctionMetrics): string {\n return containerName ? `${containerName}.${name}` : name;\n}\n\nfunction toViolation(location: BlockLocation, kind: Violation['kind'], exceeded: ExceededLimit[]): Violation {\n const level = exceeded.some((limit) => limit.level === 'error') ? 'error' : 'warning';\n return { kind, level, ...location, exceeded };\n}\n\nfunction collectExceeded<Subject>(\n thresholds: readonly Threshold<Subject>[],\n subject: Subject,\n limits: LimitsByLevel\n): ExceededLimit[] {\n return thresholds.flatMap((threshold) => findExceeded(threshold, subject, limits, (value, limit) => value > limit));\n}\n\n/** The most severe violated limit of the threshold, if any. */\nfunction findExceeded<Subject>(\n threshold: Threshold<Subject>,\n subject: Subject,\n limits: LimitsByLevel,\n violates: (value: number, limit: number) => boolean\n): ExceededLimit[] {\n const value = threshold.measure(subject);\n const level = levels.findLast((candidate) => violates(value, limits[candidate][threshold.key] as number));\n return level\n ? [{ metric: metricNameOf(threshold), value, level, limit: limits[level][threshold.key] as number }]\n : [];\n}\n\n/**\n * Whether the hunk changes the head lines [startLine, endLine]. A hunk without head lines (a pure\n * deletion) sits between its headStart line and the next one. Where indentation ends a span, its\n * last line is a statement rather than a closing delimiter, so a deletion right after that line may\n * have removed the span's tail; `endsByIndentation` counts it as touching, since the head alone\n * cannot tell it from a deletion after the span.\n */\nfunction touchesSpan(hunk: LineHunk, startLine: number, endLine: number, endsByIndentation = false): boolean {\n return hunk.headCount === 0\n ? hunk.headStart >= startLine && (hunk.headStart < endLine || (endsByIndentation && hunk.headStart === endLine))\n : hunk.headStart <= endLine && hunk.headStart + hunk.headCount > startLine;\n}\n\n/** The cross-file groups with an occurrence in each of the given files. */\nfunction indexGroupsByFile(\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n files: ReadonlySet<string>\n): Map<string, CrossFileDuplicateBlockGroup[]> {\n const groupsByFile = new Map<string, CrossFileDuplicateBlockGroup[]>();\n for (const group of crossFileDuplication?.groups ?? []) {\n for (const file of group.files) {\n if (files.has(file)) {\n const groups = groupsByFile.get(file) ?? [];\n groups.push(group);\n groupsByFile.set(file, groups);\n }\n }\n }\n return groupsByFile;\n}\n\n/**\n * One violation per duplicated region of the file: its clone occurrences, within-file and\n * cross-file, that hold at least the code lines of a level (with `hunks`, only those overlapping\n * added lines), with overlapping occurrences merged into one region.\n */\nfunction collectDuplicationViolations(\n { file, metrics, hunks }: CheckedFile,\n crossFileGroups: readonly CrossFileDuplicateBlockGroup[],\n limits: LimitsByLevel,\n codeLineNumbersByFile: CodeLineNumbersByFile\n): Violation[] {\n const findExceededLines = (block: BlockLocation): ExceededLimit[] =>\n findExceeded(\n duplicationThreshold,\n { codeLineCount: countCodeLines(block, codeLineNumbersByFile) },\n limits,\n (value, limit) => value >= limit\n );\n const groups: BlockLocation[][] = [\n ...metrics.duplication.duplicateBlockGroups.map((group) => group.map((occurrence) => ({ ...occurrence, file }))),\n ...crossFileGroups.map((group) => group.occurrences),\n ];\n const isReported = (block: BlockLocation): boolean =>\n block.file === file &&\n findExceededLines(block).length > 0 &&\n (!hunks || hunks.some((hunk) => hunk.headCount > 0 && touchesSpan(hunk, block.startLine, block.endLine)));\n const copiesByBlock = new Map<BlockLocation, BlockLocation[]>();\n for (const group of groups) {\n for (const block of group.filter((occurrence) => isReported(occurrence))) {\n copiesByBlock.set(block, group);\n }\n }\n return mergeOverlapping([...copiesByBlock.keys()]).map(({ merged, sources }) => ({\n ...toViolation(merged, 'duplication', findExceededLines(merged)),\n // The region's own occurrences are among the copies of the groups it belongs to.\n // The region merges every clone that overlaps it, so its copies range from whole copies of it\n // to a few lines matching one fragment; the largest say the most about what to share.\n partners: mergeOverlapping(sources.flatMap((source) => copiesByBlock.get(source) ?? []))\n .map((partner) => partner.merged)\n .filter((partner) => !overlaps(partner, merged))\n .toSorted(\n (left, right) => countCodeLines(right, codeLineNumbersByFile) - countCodeLines(left, codeLineNumbersByFile)\n ),\n }));\n}\n\n/** The locations with those overlapping in the same file merged, ordered by file, then line. */\nfunction mergeOverlapping(locations: readonly BlockLocation[]): { merged: BlockLocation; sources: BlockLocation[] }[] {\n const regions: { merged: BlockLocation; sources: BlockLocation[] }[] = [];\n const ordered = locations.toSorted(\n (left, right) => compareStrings(left.file, right.file) || left.startLine - right.startLine\n );\n for (const location of ordered) {\n const last = regions.at(-1);\n if (last && overlaps(last.merged, location)) {\n last.merged.endLine = Math.max(last.merged.endLine, location.endLine);\n last.sources.push(location);\n } else {\n const { file, startLine, endLine } = location;\n regions.push({ merged: { file, startLine, endLine }, sources: [location] });\n }\n }\n return regions;\n}\n\n/** The length of a duplicated block. */\nfunction countCodeLines(block: BlockLocation, codeLineNumbersByFile: CodeLineNumbersByFile): number {\n const codeLineNumbers = codeLineNumbersByFile.get(block.file);\n assert.ok(codeLineNumbers, `No code lines for ${block.file}, which holds a duplicated block.`);\n let count = 0;\n for (let line = block.startLine; line <= block.endLine; line++) {\n if (codeLineNumbers.has(line)) count += 1;\n }\n return count;\n}\n\nfunction overlaps(left: BlockLocation, right: BlockLocation): boolean {\n return left.file === right.file && left.startLine <= right.endLine && right.startLine <= left.endLine;\n}\n\n/** Code-unit order, so reports do not depend on the locale. */\nfunction compareStrings(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"thresholdCheck.js","names":[],"sources":["../src/thresholdCheck.ts"],"sourcesContent":["import assert from 'node:assert';\nimport type {\n CrossFileDuplicateBlockGroup,\n CrossFileDuplicateOccurrence,\n CrossFileDuplicationMetrics,\n} from './crossFileDuplication.js';\nimport type { LineHunk } from './git.js';\nimport {\n duplicationThreshold,\n fileThresholds,\n functionThresholds,\n levels,\n metricNameOf,\n type Level,\n type LimitsByLevel,\n type Threshold,\n} from './thresholds.js';\nimport type { CodeMetrics, CognitiveBlock, FunctionMetrics } from './types.js';\n\n/** A measured file to check, under the path cross-file duplication knows it by. */\nexport interface CheckedFile {\n file: string;\n metrics: CodeMetrics;\n /** The change to the file; when set, only what the change touches is checked. */\n hunks?: readonly LineHunk[];\n}\n\n/** The duplication of the measured files, in which duplicated blocks and their copies are found. */\nexport interface CheckedDuplication {\n crossFile?: CrossFileDuplicationMetrics;\n /** The detection's `minSimilarityPercent`, the share of a block a copy of it holds at least. */\n minSimilarityPercent: number;\n}\n\nexport interface ExceededLimit {\n metric: string;\n /** What reports call the metric (`cognitive complexity`, `duplicated lines`). */\n label: string;\n value: number;\n /** The most severe level whose limit the value violates. */\n level: Level;\n /** That level's limit: the value is allowed up to it; a duplicated block is allowed below it. */\n limit: number;\n}\n\nexport interface BlockLocation {\n file: string;\n startLine: number;\n endLine: number;\n}\n\n/** One copy of a clone group. */\ninterface CloneOccurrence extends BlockLocation {\n /**\n * Its code lines matched in another copy, whose number is the length of a duplicated block. Its\n * line span would also count the lines the copies do not share.\n */\n lineNumbers: readonly number[];\n tokenCount: number;\n}\n\nexport interface Violation extends BlockLocation {\n kind: 'function' | 'file' | 'duplication';\n /** The most severe level among `exceeded`. */\n level: Level;\n /** The function's name, qualified with the class it is a member of, for kind `function`. */\n name?: string;\n exceeded: ExceededLimit[];\n /**\n * The parts adding the most to the cognitive complexity, largest first, for a function that\n * exceeds a limit of it and has such parts: operator sequences and jumps outside any branching\n * construct add to the complexity without being one.\n */\n largestBlocks?: CognitiveBlock[];\n /** The other copies of the block, most matched tokens first, for kind `duplication`. */\n partners?: BlockLocation[];\n}\n\nexport interface CheckResult {\n violations: Violation[];\n checkedFunctionCount: number;\n}\n\nconst violationKinds: readonly Violation['kind'][] = ['file', 'function', 'duplication'];\n\nconst cognitiveComplexityMetric = 'functionCognitiveComplexity';\n\nconst nestedInclusiveMetrics = new Set(\n functionThresholds\n .filter(({ includesNestedFunctions }) => includesNestedFunctions)\n .map((threshold) => metricNameOf(threshold))\n);\n\n/** Every limit the files exceed, errors first, then ordered by path, then line. */\nexport function checkThresholds(\n files: readonly CheckedFile[],\n duplication: CheckedDuplication,\n limitsOf: (language: string) => LimitsByLevel\n): CheckResult {\n const crossFileGroupsByFile = indexGroupsByFile(duplication.crossFile, new Set(files.map(({ file }) => file)));\n const violations: Violation[] = [];\n let checkedFunctionCount = 0;\n for (const checkedFile of files) {\n const { file, metrics, hunks } = checkedFile;\n const limits = limitsOf(metrics.language);\n // A rename or a mode change lists the file as changed without touching any of its lines.\n const fileExceeded = hunks?.length === 0 ? [] : collectExceeded(fileThresholds, metrics, limits);\n if (fileExceeded.length > 0) {\n violations.push(toViolation({ file, startLine: 1, endLine: metrics.lines.total }, 'file', fileExceeded));\n }\n const checkedFunctions = listCheckedFunctions(checkedFile);\n checkedFunctionCount += checkedFunctions.length;\n const exceedingFunctions = checkedFunctions\n .map((fn) => ({ fn, exceeded: collectExceeded(functionThresholds, fn, limits) }))\n .filter(({ exceeded }) => exceeded.length > 0);\n for (const { fn, exceeded } of exceedingFunctions) {\n const ownExceeded = exceeded.filter((limit) => !isReportedByEnclosingFunction(limit, fn, exceedingFunctions));\n if (ownExceeded.length > 0) {\n violations.push(toFunctionViolation(file, fn, ownExceeded));\n }\n }\n violations.push(\n ...collectDuplicationViolations(checkedFile, crossFileGroupsByFile.get(file) ?? [], limits, duplication)\n );\n }\n return { violations: violations.toSorted(compareViolations), checkedFunctionCount };\n}\n\n// The kind is compared before the end line because a file violation's end line is corrected after\n// this ordering (to the last line that exists), and the order must not depend on that correction.\nfunction compareViolations(left: Violation, right: Violation): number {\n return (\n levels.indexOf(right.level) - levels.indexOf(left.level) ||\n compareStrings(left.file, right.file) ||\n left.startLine - right.startLine ||\n violationKinds.indexOf(left.kind) - violationKinds.indexOf(right.kind) ||\n left.endLine - right.endLine\n );\n}\n\n/** The file's functions or, for a changed file, those the change touches. */\nfunction listCheckedFunctions({ metrics, hunks }: CheckedFile): FunctionMetrics[] {\n const endsByIndentation = metrics.language === 'python';\n return hunks\n ? metrics.functions.filter((fn) =>\n hunks.some((hunk) => touchesSpan(hunk, fn.startLine, fn.endLine, endsByIndentation))\n )\n : metrics.functions;\n}\n\n/**\n * Whether a function enclosing `fn` exceeds the same limit with a value that covers `fn`, so\n * reporting it for `fn` too would count the same code twice. The enclosing value is at least that\n * of `fn` and both share the file's limits, so its level is never milder.\n */\nfunction isReportedByEnclosingFunction(\n limit: ExceededLimit,\n fn: FunctionMetrics,\n exceedingFunctions: readonly { fn: FunctionMetrics; exceeded: ExceededLimit[] }[]\n): boolean {\n if (!nestedInclusiveMetrics.has(limit.metric)) {\n return false;\n }\n return exceedingFunctions.some(\n (outer) => outer.fn !== fn && encloses(outer.fn, fn) && outer.exceeded.some(({ metric }) => metric === limit.metric)\n );\n}\n\nfunction encloses(outer: FunctionMetrics, inner: FunctionMetrics): boolean {\n const startsBefore =\n outer.startLine < inner.startLine ||\n (outer.startLine === inner.startLine && outer.startColumn <= inner.startColumn);\n const endsAfter =\n outer.endLine > inner.endLine || (outer.endLine === inner.endLine && outer.endColumn >= inner.endColumn);\n return startsBefore && endsAfter;\n}\n\nfunction toFunctionViolation(file: string, fn: FunctionMetrics, exceeded: ExceededLimit[]): Violation {\n const { startLine, endLine } = fn;\n const exceedsCognitiveComplexity = exceeded.some(({ metric }) => metric === cognitiveComplexityMetric);\n return {\n ...toViolation({ file, startLine, endLine }, 'function', exceeded),\n name: qualifyFunctionName(fn),\n ...(exceedsCognitiveComplexity && fn.cognitiveBlocks.length > 0 && { largestBlocks: fn.cognitiveBlocks }),\n };\n}\n\n/** How reports name a function: with the type or module it is a member of, so that it can be found. */\nexport function qualifyFunctionName({ name = '<anonymous>', containerName }: FunctionMetrics): string {\n return containerName ? `${containerName}.${name}` : name;\n}\n\nfunction toViolation(location: BlockLocation, kind: Violation['kind'], exceeded: ExceededLimit[]): Violation {\n const level = exceeded.some((limit) => limit.level === 'error') ? 'error' : 'warning';\n return { kind, level, ...location, exceeded };\n}\n\nfunction collectExceeded<Subject>(\n thresholds: readonly Threshold<Subject>[],\n subject: Subject,\n limits: LimitsByLevel\n): ExceededLimit[] {\n return thresholds.flatMap((threshold) => findExceeded(threshold, subject, limits, (value, limit) => value > limit));\n}\n\n/** The most severe violated limit of the threshold, if any. */\nfunction findExceeded<Subject>(\n threshold: Threshold<Subject>,\n subject: Subject,\n limits: LimitsByLevel,\n violates: (value: number, limit: number) => boolean\n): ExceededLimit[] {\n const value = threshold.measure(subject);\n const level = levels.findLast((candidate) => violates(value, limits[candidate][threshold.key] as number));\n return level\n ? [\n {\n metric: metricNameOf(threshold),\n label: threshold.label,\n value,\n level,\n limit: limits[level][threshold.key] as number,\n },\n ]\n : [];\n}\n\n/**\n * Whether the hunk changes the head lines [startLine, endLine]. A hunk without head lines (a pure\n * deletion) sits between its headStart line and the next one. Where indentation ends a span, its\n * last line is a statement rather than a closing delimiter, so a deletion right after that line may\n * have removed the span's tail; `endsByIndentation` counts it as touching, since the head alone\n * cannot tell it from a deletion after the span.\n */\nfunction touchesSpan(hunk: LineHunk, startLine: number, endLine: number, endsByIndentation = false): boolean {\n return hunk.headCount === 0\n ? hunk.headStart >= startLine && (hunk.headStart < endLine || (endsByIndentation && hunk.headStart === endLine))\n : hunk.headStart <= endLine && hunk.headStart + hunk.headCount > startLine;\n}\n\n/** The cross-file groups with an occurrence in each of the given files. */\nfunction indexGroupsByFile(\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n files: ReadonlySet<string>\n): Map<string, CrossFileDuplicateBlockGroup[]> {\n const groupsByFile = new Map<string, CrossFileDuplicateBlockGroup[]>();\n for (const group of crossFileDuplication?.groups ?? []) {\n for (const file of group.files) {\n if (files.has(file)) {\n const groups = groupsByFile.get(file) ?? [];\n groups.push(group);\n groupsByFile.set(file, groups);\n }\n }\n }\n return groupsByFile;\n}\n\n/**\n * One violation per duplicated region of the file: its clone occurrences, within-file and\n * cross-file, that hold at least the duplicated lines of a level (with `hunks`, only those\n * overlapping added lines), with overlapping occurrences merged into one region unless they are\n * copies of each other.\n */\nfunction collectDuplicationViolations(\n { file, metrics, hunks }: CheckedFile,\n crossFileGroups: readonly CrossFileDuplicateBlockGroup[],\n limits: LimitsByLevel,\n { minSimilarityPercent }: CheckedDuplication\n): Violation[] {\n const findExceededLines = (duplicatedLineCount: number): ExceededLimit[] =>\n findExceeded(duplicationThreshold, { duplicatedLineCount }, limits, (value, limit) => value >= limit);\n const groups: CloneOccurrence[][] = [\n ...metrics.duplication.duplicateBlockGroups.map((group) => group.map((occurrence) => ({ ...occurrence, file }))),\n ...crossFileGroups.map((group) => group.occurrences.map((occurrence) => toCloneOccurrence(occurrence))),\n ];\n const isReported = (block: CloneOccurrence): boolean =>\n block.file === file &&\n findExceededLines(block.lineNumbers.length).length > 0 &&\n (!hunks || hunks.some((hunk) => hunk.headCount > 0 && touchesSpan(hunk, block.startLine, block.endLine)));\n const groupOf = new Map(groups.flatMap((group) => group.map((occurrence) => [occurrence, group] as const)));\n // Copies of one group that share a line (the branches of an `else if` chain, the entries of a\n // list) stay apart: merged, they would be one block without a copy. One lying within another is\n // a repetition inside that block, not a block beside it.\n const areCopies = (left: CloneOccurrence, right: CloneOccurrence): boolean =>\n groupOf.get(left) === groupOf.get(right) && !contains(left, right) && !contains(right, left);\n const reportedBlocks = groups.flat().filter((occurrence) => isReported(occurrence));\n return mergeOverlapping(reportedBlocks, areCopies).map(({ merged, sources }) => {\n const listCopies = (minTokenPercent: number): BlockLocation[] =>\n mergeOverlapping(\n sources.flatMap((source) => {\n // The region merges every clone that overlaps it, so a copy of an occurrence lying\n // within a larger one is measured against the larger one.\n const tokenCount = Math.max(\n ...sources.filter((outer) => contains(outer, source)).map((outer) => outer.tokenCount)\n );\n return (groupOf.get(source) ?? []).filter(\n (copy) =>\n copy.tokenCount * 100 >= tokenCount * minTokenPercent &&\n // The region's own occurrences are among the copies of the groups it belongs to, and\n // so may be an occurrence within one of them that is not reported itself: a\n // repetition inside the block, not another place.\n !sources.some((outer) => contains(outer, copy))\n );\n }),\n areCopies\n )\n // The largest say the most about what to share.\n .toSorted((left, right) => sumTokens(right.sources) - sumTokens(left.sources))\n .map((partner) => partner.merged);\n // A group joins a block with every place repeating a part of it: a function another file\n // copies whole is grouped with each file holding a few of its lines. Such a fragment is not a\n // copy of the block: sharing it would not remove the block, and a report listing it reads as\n // one more copy. Sizes are compared in tokens, since the same code may be laid out over any\n // number of lines.\n const copies = listCopies(minSimilarityPercent);\n return {\n ...toViolation(\n merged,\n 'duplication',\n // Occurrences that overlap share lines, which count once.\n findExceededLines(new Set(sources.flatMap((source) => source.lineNumbers)).size)\n ),\n // A block whose parts were matched with different places has no copy of its size. The\n // places then are all that locates what it repeats.\n partners: copies.length > 0 ? copies : listCopies(0),\n };\n });\n}\n\nfunction toCloneOccurrence(occurrence: CrossFileDuplicateOccurrence): CloneOccurrence {\n const { lineNumbers } = occurrence;\n assert.ok(lineNumbers, `No matched lines for ${occurrence.file}, a scanned file.`);\n return { ...occurrence, lineNumbers };\n}\n\nfunction sumTokens(occurrences: readonly CloneOccurrence[]): number {\n return occurrences.reduce((sum, occurrence) => sum + occurrence.tokenCount, 0);\n}\n\n/**\n * The locations with those overlapping in the same file merged, except those `areCopies` holds\n * for, ordered by file, then line.\n */\nfunction mergeOverlapping(\n locations: readonly CloneOccurrence[],\n areCopies: (left: CloneOccurrence, right: CloneOccurrence) => boolean\n): { merged: BlockLocation; sources: CloneOccurrence[] }[] {\n const regions: { merged: BlockLocation; sources: CloneOccurrence[] }[] = [];\n const ordered = locations.toSorted(\n (left, right) => compareStrings(left.file, right.file) || left.startLine - right.startLine\n );\n for (const location of ordered) {\n const last = regions.at(-1);\n // A block containing the location holds it whatever it is a copy of.\n const joins =\n last &&\n overlaps(last.merged, location) &&\n (last.sources.some((source) => contains(source, location)) ||\n !last.sources.some((source) => areCopies(source, location)));\n if (joins) {\n last.merged.endLine = Math.max(last.merged.endLine, location.endLine);\n last.sources.push(location);\n } else {\n const { file, startLine, endLine } = location;\n regions.push({ merged: { file, startLine, endLine }, sources: [location] });\n }\n }\n return regions;\n}\n\n/** Whether `outer` spans every line of `inner`; a block contains itself. */\nfunction contains(outer: BlockLocation, inner: BlockLocation): boolean {\n return outer.file === inner.file && outer.startLine <= inner.startLine && inner.endLine <= outer.endLine;\n}\n\nfunction overlaps(left: BlockLocation, right: BlockLocation): boolean {\n return left.file === right.file && left.startLine <= right.endLine && right.startLine <= left.endLine;\n}\n\n/** Code-unit order, so reports do not depend on the locale. */\nfunction compareStrings(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"],"mappings":"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"}
|
package/dist/thresholds.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=[`warning`,`error`],t=[{key:`maxFunctionCognitiveComplexity`,label:`cognitive complexity`,defaultLimits:{warning:15,error:30},hint:`flatten nested branching with early returns and extract nested blocks into named functions.`,measure:e=>e.cognitiveComplexity,includesNestedFunctions:!0},{key:`maxFunctionCyclomaticComplexity`,label:`cyclomatic complexity`,defaultLimits:{warning:1/0,error:1/0},hint:`the function has too many independent paths; split it by decision or replace condition chains with a lookup table.`,measure:e=>e.cyclomaticComplexity},{key:`maxFunctionNcss`,label:`NCSS`,defaultLimits:{warning:30,error:60},hint:`move each group of statements that forms one step into a named function.`,measure:e=>e.ncss,includesNestedFunctions:!0},{key:`maxFunctionNestingDepth`,label:`nesting depth`,defaultLimits:{warning:1/0,error:1/0},hint:`replace nested conditions with guard clauses or move inner blocks into functions.`,measure:e=>e.nestingDepth},{key:`maxFunctionParameterCount`,label:`parameters`,defaultLimits:{warning:6,error:1/0},hint:`group related parameters into one object or split the function.`,measure:e=>e.parameterCount},{key:`maxFunctionHalsteadVolume`,label:`Halstead volume`,defaultLimits:{warning:1/0,error:1/0},hint:`the function holds too many operators and operands; move cohesive parts into helper functions.`,measure:e=>e.halstead.volume,includesNestedFunctions:!0},{key:`maxFunctionHalsteadDifficulty`,label:`Halstead difficulty`,defaultLimits:{warning:1/0,error:1/0},hint:`the function reuses the same operands many times; split it so each part handles fewer values.`,measure:e=>e.halstead.difficulty},{key:`maxFunctionHalsteadEffort`,label:`Halstead effort`,defaultLimits:{warning:1/0,error:1/0},hint:`the function is both large and dense; split it into smaller functions with fewer values each.`,measure:e=>e.halstead.effort},{key:`maxFunctionDepDegree`,label:`DepDegree`,defaultLimits:{warning:1/0,error:1/0},hint:`too many values flow between the variables of the function; split it so each part works on fewer variables.`,measure:e=>e.depDegree,includesNestedFunctions:!0}],n=[{key:`maxFileNcss`,label:`file NCSS`,defaultLimits:{warning:400,error:1e3},hint:`split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.`,measure:e=>e.ncssCount}],r={key:`minDuplicateLines`,label:`duplicated lines`,defaultLimits:{warning:15,error:1/0},hint:`extract the repeated code into one shared function or module and call it from every location.`,measure:e=>e.
|
|
1
|
+
"use strict";const e=[`warning`,`error`],t=[{key:`maxFunctionCognitiveComplexity`,label:`cognitive complexity`,defaultLimits:{warning:15,error:30},hint:`flatten nested branching with early returns and extract nested blocks into named functions.`,measure:e=>e.cognitiveComplexity,includesNestedFunctions:!0},{key:`maxFunctionCyclomaticComplexity`,label:`cyclomatic complexity`,defaultLimits:{warning:1/0,error:1/0},hint:`the function has too many independent paths; split it by decision or replace condition chains with a lookup table.`,measure:e=>e.cyclomaticComplexity},{key:`maxFunctionNcss`,label:`NCSS`,defaultLimits:{warning:30,error:60},hint:`move each group of statements that forms one step into a named function.`,measure:e=>e.ncss,includesNestedFunctions:!0},{key:`maxFunctionNestingDepth`,label:`nesting depth`,defaultLimits:{warning:1/0,error:1/0},hint:`replace nested conditions with guard clauses or move inner blocks into functions.`,measure:e=>e.nestingDepth},{key:`maxFunctionParameterCount`,label:`parameters`,defaultLimits:{warning:6,error:1/0},hint:`group related parameters into one object or split the function.`,measure:e=>e.parameterCount},{key:`maxFunctionHalsteadVolume`,label:`Halstead volume`,defaultLimits:{warning:1/0,error:1/0},hint:`the function holds too many operators and operands; move cohesive parts into helper functions.`,measure:e=>e.halstead.volume,includesNestedFunctions:!0},{key:`maxFunctionHalsteadDifficulty`,label:`Halstead difficulty`,defaultLimits:{warning:1/0,error:1/0},hint:`the function reuses the same operands many times; split it so each part handles fewer values.`,measure:e=>e.halstead.difficulty},{key:`maxFunctionHalsteadEffort`,label:`Halstead effort`,defaultLimits:{warning:1/0,error:1/0},hint:`the function is both large and dense; split it into smaller functions with fewer values each.`,measure:e=>e.halstead.effort},{key:`maxFunctionDepDegree`,label:`DepDegree`,defaultLimits:{warning:1/0,error:1/0},hint:`too many values flow between the variables of the function; split it so each part works on fewer variables.`,measure:e=>e.depDegree,includesNestedFunctions:!0}],n=[{key:`maxFileNcss`,label:`file NCSS`,defaultLimits:{warning:400,error:1e3},hint:`split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.`,measure:e=>e.ncssCount}],r={key:`minDuplicateLines`,label:`duplicated lines`,defaultLimits:{warning:15,error:1/0},hint:`extract the repeated code into one shared function or module and call it from every location.`,measure:e=>e.duplicatedLineCount},i=[...t,...n,r];function a(e){let t=e.key.slice(3);return t.charAt(0).toLowerCase()+t.slice(1)}function o(e,t){let n=(n,r)=>{let a=t?.[n];return{...Object.fromEntries(i.map(({key:e,defaultLimits:t})=>[e,t[n]])),...a?.limits,...a&&Object.hasOwn(a.languages,r)?a.languages[r]:void 0,...e[n]}};return e=>({warning:n(`warning`,e),error:n(`error`,e)})}exports.duplicationThreshold=r,exports.fileThresholds=n,exports.functionThresholds=t,exports.levels=e,exports.metricNameOf=a,exports.resolveLimits=o,exports.thresholds=i;
|
|
2
2
|
//# sourceMappingURL=thresholds.cjs.map
|
package/dist/thresholds.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thresholds.cjs","names":[],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 30, error: 60 },\n hint: 'move each group of statements that forms one step into a named function.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 6, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 400, error: 1000 },\n hint: 'split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/**\n * A duplicated block violates when it holds at least this many code lines: comment-only and blank
|
|
1
|
+
{"version":3,"file":"thresholds.cjs","names":[],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 30, error: 60 },\n hint: 'move each group of statements that forms one step into a named function.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 6, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 400, error: 1000 },\n hint: 'split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/**\n * A duplicated block violates when it holds at least this many duplicated lines, its code lines\n * matched in another copy: comment-only and blank lines do not count, so a copy reads the same\n * length whether or not it is commented, and neither do the lines the copies do not share, those\n * between the matched parts of an edited copy and those a near-miss copy adds or rewrites.\n */\nexport const duplicationThreshold: Threshold<{ duplicatedLineCount: number }> = {\n key: 'minDuplicateLines',\n label: 'duplicated lines',\n defaultLimits: { warning: 15, error: Infinity },\n hint: 'extract the repeated code into one shared function or module and call it from every location.',\n measure: (block) => block.duplicatedLineCount,\n};\n\nexport const thresholds: readonly Threshold[] = [...functionThresholds, ...fileThresholds, duplicationThreshold];\n\n/** The name a report gives a threshold's measured value: its key without the `max`/`min` prefix. */\nexport function metricNameOf(threshold: Threshold): string {\n const name = threshold.key.slice(3);\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** One level of the `thresholds` config section; a missing key falls back to the less specific layer. */\nexport interface LevelThresholdsConfig {\n limits: Limits;\n /** Overrides for the files of one language, keyed by language name. */\n languages: Readonly<Record<string, Limits>>;\n}\n\nexport type ThresholdsConfig = Readonly<Partial<Record<Level, LevelThresholdsConfig>>>;\n\n/**\n * The limits of each level applying to files of each language, with precedence command-line\n * options > the config's per-language overrides > the config's limits > built-in defaults.\n */\nexport function resolveLimits(\n cliLimits: LimitsByLevel,\n config: ThresholdsConfig | undefined\n): (language: string) => LimitsByLevel {\n const resolveLevel = (level: Level, language: string): Limits => {\n const levelConfig = config?.[level];\n return {\n ...Object.fromEntries(thresholds.map(({ key, defaultLimits }) => [key, defaultLimits[level]])),\n ...levelConfig?.limits,\n // Object.hasOwn: a language named like an Object.prototype member must not read an inherited value.\n ...(levelConfig && Object.hasOwn(levelConfig.languages, language) ? levelConfig.languages[language] : undefined),\n ...cliLimits[level],\n };\n };\n return (language) => ({ warning: resolveLevel('warning', language), error: resolveLevel('error', language) });\n}\n"],"mappings":"aAGA,MAAa,EAAS,CAAC,UAAW,OAAO,EA4B5B,EAA4D,CACvE,CACE,IAAK,iCACL,MAAO,uBACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,8FACN,QAAU,GAAO,EAAG,oBACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,kCACL,MAAO,wBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,qHACN,QAAU,GAAO,EAAG,oBACtB,EACA,CACE,IAAK,kBACL,MAAO,OACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,2EACN,QAAU,GAAO,EAAG,KACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,0BACL,MAAO,gBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,oFACN,QAAU,GAAO,EAAG,YACtB,EACA,CACE,IAAK,4BACL,MAAO,aACP,cAAe,CAAE,QAAS,EAAG,MAAO,GAAS,EAC7C,KAAM,kEACN,QAAU,GAAO,EAAG,cACtB,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,iGACN,QAAU,GAAO,EAAG,SAAS,OAC7B,wBAAyB,EAC3B,EACA,CACE,IAAK,gCACL,MAAO,sBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAO,EAAG,SAAS,UAC/B,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAO,EAAG,SAAS,MAC/B,EACA,CACE,IAAK,uBACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,8GACN,QAAU,GAAO,EAAG,UACpB,wBAAyB,EAC3B,CACF,EAEa,EAAoD,CAC/D,CACE,IAAK,cACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAK,MAAO,GAAK,EAC3C,KAAM,gKACN,QAAU,GAAS,EAAK,SAC1B,CACF,EAQa,EAAmE,CAC9E,IAAK,oBACL,MAAO,mBACP,cAAe,CAAE,QAAS,GAAI,MAAO,GAAS,EAC9C,KAAM,gGACN,QAAU,GAAU,EAAM,mBAC5B,EAEa,EAAmC,CAAC,GAAG,EAAoB,GAAG,EAAgB,CAAoB,EAG/G,SAAgB,EAAa,EAA8B,CACzD,IAAM,EAAO,EAAU,IAAI,MAAM,CAAC,EAClC,OAAO,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,EAAI,EAAK,MAAM,CAAC,CACpD,CAeA,SAAgB,EACd,EACA,EACqC,CACrC,IAAM,GAAgB,EAAc,IAA6B,CAC/D,IAAM,EAAc,IAAS,GAC7B,MAAO,CACL,GAAG,OAAO,YAAY,EAAW,KAAK,CAAE,MAAK,mBAAoB,CAAC,EAAK,EAAc,EAAM,CAAC,CAAC,EAC7F,GAAG,GAAa,OAEhB,GAAI,GAAe,OAAO,OAAO,EAAY,UAAW,CAAQ,EAAI,EAAY,UAAU,GAAY,IAAA,GACtG,GAAG,EAAU,EACf,CACF,EACA,MAAQ,KAAc,CAAE,QAAS,EAAa,UAAW,CAAQ,EAAG,MAAO,EAAa,QAAS,CAAQ,CAAE,EAC7G"}
|
package/dist/thresholds.d.ts
CHANGED
|
@@ -28,11 +28,13 @@ export type LimitsByLevel = Readonly<Record<Level, Limits>>;
|
|
|
28
28
|
export declare const functionThresholds: readonly Threshold<FunctionMetrics>[];
|
|
29
29
|
export declare const fileThresholds: readonly Threshold<CodeMetrics>[];
|
|
30
30
|
/**
|
|
31
|
-
* A duplicated block violates when it holds at least this many
|
|
32
|
-
* lines do not count, so a copy reads the same
|
|
31
|
+
* A duplicated block violates when it holds at least this many duplicated lines, its code lines
|
|
32
|
+
* matched in another copy: comment-only and blank lines do not count, so a copy reads the same
|
|
33
|
+
* length whether or not it is commented, and neither do the lines the copies do not share, those
|
|
34
|
+
* between the matched parts of an edited copy and those a near-miss copy adds or rewrites.
|
|
33
35
|
*/
|
|
34
36
|
export declare const duplicationThreshold: Threshold<{
|
|
35
|
-
|
|
37
|
+
duplicatedLineCount: number;
|
|
36
38
|
}>;
|
|
37
39
|
export declare const thresholds: readonly Threshold[];
|
|
38
40
|
/** The name a report gives a threshold's measured value: its key without the `max`/`min` prefix. */
|
package/dist/thresholds.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=[`warning`,`error`],t=[{key:`maxFunctionCognitiveComplexity`,label:`cognitive complexity`,defaultLimits:{warning:15,error:30},hint:`flatten nested branching with early returns and extract nested blocks into named functions.`,measure:e=>e.cognitiveComplexity,includesNestedFunctions:!0},{key:`maxFunctionCyclomaticComplexity`,label:`cyclomatic complexity`,defaultLimits:{warning:1/0,error:1/0},hint:`the function has too many independent paths; split it by decision or replace condition chains with a lookup table.`,measure:e=>e.cyclomaticComplexity},{key:`maxFunctionNcss`,label:`NCSS`,defaultLimits:{warning:30,error:60},hint:`move each group of statements that forms one step into a named function.`,measure:e=>e.ncss,includesNestedFunctions:!0},{key:`maxFunctionNestingDepth`,label:`nesting depth`,defaultLimits:{warning:1/0,error:1/0},hint:`replace nested conditions with guard clauses or move inner blocks into functions.`,measure:e=>e.nestingDepth},{key:`maxFunctionParameterCount`,label:`parameters`,defaultLimits:{warning:6,error:1/0},hint:`group related parameters into one object or split the function.`,measure:e=>e.parameterCount},{key:`maxFunctionHalsteadVolume`,label:`Halstead volume`,defaultLimits:{warning:1/0,error:1/0},hint:`the function holds too many operators and operands; move cohesive parts into helper functions.`,measure:e=>e.halstead.volume,includesNestedFunctions:!0},{key:`maxFunctionHalsteadDifficulty`,label:`Halstead difficulty`,defaultLimits:{warning:1/0,error:1/0},hint:`the function reuses the same operands many times; split it so each part handles fewer values.`,measure:e=>e.halstead.difficulty},{key:`maxFunctionHalsteadEffort`,label:`Halstead effort`,defaultLimits:{warning:1/0,error:1/0},hint:`the function is both large and dense; split it into smaller functions with fewer values each.`,measure:e=>e.halstead.effort},{key:`maxFunctionDepDegree`,label:`DepDegree`,defaultLimits:{warning:1/0,error:1/0},hint:`too many values flow between the variables of the function; split it so each part works on fewer variables.`,measure:e=>e.depDegree,includesNestedFunctions:!0}],n=[{key:`maxFileNcss`,label:`file NCSS`,defaultLimits:{warning:400,error:1e3},hint:`split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.`,measure:e=>e.ncssCount}],r={key:`minDuplicateLines`,label:`duplicated lines`,defaultLimits:{warning:15,error:1/0},hint:`extract the repeated code into one shared function or module and call it from every location.`,measure:e=>e.
|
|
1
|
+
const e=[`warning`,`error`],t=[{key:`maxFunctionCognitiveComplexity`,label:`cognitive complexity`,defaultLimits:{warning:15,error:30},hint:`flatten nested branching with early returns and extract nested blocks into named functions.`,measure:e=>e.cognitiveComplexity,includesNestedFunctions:!0},{key:`maxFunctionCyclomaticComplexity`,label:`cyclomatic complexity`,defaultLimits:{warning:1/0,error:1/0},hint:`the function has too many independent paths; split it by decision or replace condition chains with a lookup table.`,measure:e=>e.cyclomaticComplexity},{key:`maxFunctionNcss`,label:`NCSS`,defaultLimits:{warning:30,error:60},hint:`move each group of statements that forms one step into a named function.`,measure:e=>e.ncss,includesNestedFunctions:!0},{key:`maxFunctionNestingDepth`,label:`nesting depth`,defaultLimits:{warning:1/0,error:1/0},hint:`replace nested conditions with guard clauses or move inner blocks into functions.`,measure:e=>e.nestingDepth},{key:`maxFunctionParameterCount`,label:`parameters`,defaultLimits:{warning:6,error:1/0},hint:`group related parameters into one object or split the function.`,measure:e=>e.parameterCount},{key:`maxFunctionHalsteadVolume`,label:`Halstead volume`,defaultLimits:{warning:1/0,error:1/0},hint:`the function holds too many operators and operands; move cohesive parts into helper functions.`,measure:e=>e.halstead.volume,includesNestedFunctions:!0},{key:`maxFunctionHalsteadDifficulty`,label:`Halstead difficulty`,defaultLimits:{warning:1/0,error:1/0},hint:`the function reuses the same operands many times; split it so each part handles fewer values.`,measure:e=>e.halstead.difficulty},{key:`maxFunctionHalsteadEffort`,label:`Halstead effort`,defaultLimits:{warning:1/0,error:1/0},hint:`the function is both large and dense; split it into smaller functions with fewer values each.`,measure:e=>e.halstead.effort},{key:`maxFunctionDepDegree`,label:`DepDegree`,defaultLimits:{warning:1/0,error:1/0},hint:`too many values flow between the variables of the function; split it so each part works on fewer variables.`,measure:e=>e.depDegree,includesNestedFunctions:!0}],n=[{key:`maxFileNcss`,label:`file NCSS`,defaultLimits:{warning:400,error:1e3},hint:`split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.`,measure:e=>e.ncssCount}],r={key:`minDuplicateLines`,label:`duplicated lines`,defaultLimits:{warning:15,error:1/0},hint:`extract the repeated code into one shared function or module and call it from every location.`,measure:e=>e.duplicatedLineCount},i=[...t,...n,r];function a(e){let t=e.key.slice(3);return t.charAt(0).toLowerCase()+t.slice(1)}function o(e,t){let n=(n,r)=>{let a=t?.[n];return{...Object.fromEntries(i.map(({key:e,defaultLimits:t})=>[e,t[n]])),...a?.limits,...a&&Object.hasOwn(a.languages,r)?a.languages[r]:void 0,...e[n]}};return e=>({warning:n(`warning`,e),error:n(`error`,e)})}export{r as duplicationThreshold,n as fileThresholds,t as functionThresholds,e as levels,a as metricNameOf,o as resolveLimits,i as thresholds};
|
|
2
2
|
//# sourceMappingURL=thresholds.js.map
|
package/dist/thresholds.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thresholds.js","names":[],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 30, error: 60 },\n hint: 'move each group of statements that forms one step into a named function.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 6, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 400, error: 1000 },\n hint: 'split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/**\n * A duplicated block violates when it holds at least this many code lines: comment-only and blank
|
|
1
|
+
{"version":3,"file":"thresholds.js","names":[],"sources":["../src/thresholds.ts"],"sourcesContent":["import type { CodeMetrics, FunctionMetrics } from './types.js';\n\n/** The severities of a violation, mildest first. */\nexport const levels = ['warning', 'error'] as const;\nexport type Level = (typeof levels)[number];\n\n/**\n * One threshold of `code-gauge check`. Its `key` is the config setting, the per-language override\n * setting, and (kebab-cased after a level prefix) the command-line options, so a new entry here is\n * all a new thresholded metric needs.\n */\nexport interface Threshold<Subject = never> {\n key: string;\n /** How the report names the measured value. */\n label: string;\n /** `Infinity` leaves the level unchecked unless the config or the command line sets a limit. */\n defaultLimits: Readonly<Record<Level, number>>;\n /** What to change in the code when the limit is exceeded. */\n hint: string;\n measure: (subject: Subject) => number;\n /**\n * The value of a function is at least that of each function nested in it, whose code it counts\n * too. Not so for Halstead difficulty and effort: a ratio can be higher for the nested part alone.\n */\n includesNestedFunctions?: true;\n}\n\n/** Limits by threshold key; `Infinity` disables a limit. */\nexport type Limits = Readonly<Record<string, number>>;\nexport type LimitsByLevel = Readonly<Record<Level, Limits>>;\n\nexport const functionThresholds: readonly Threshold<FunctionMetrics>[] = [\n {\n key: 'maxFunctionCognitiveComplexity',\n label: 'cognitive complexity',\n defaultLimits: { warning: 15, error: 30 },\n hint: 'flatten nested branching with early returns and extract nested blocks into named functions.',\n measure: (fn) => fn.cognitiveComplexity,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionCyclomaticComplexity',\n label: 'cyclomatic complexity',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function has too many independent paths; split it by decision or replace condition chains with a lookup table.',\n measure: (fn) => fn.cyclomaticComplexity,\n },\n {\n key: 'maxFunctionNcss',\n label: 'NCSS',\n defaultLimits: { warning: 30, error: 60 },\n hint: 'move each group of statements that forms one step into a named function.',\n measure: (fn) => fn.ncss,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionNestingDepth',\n label: 'nesting depth',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'replace nested conditions with guard clauses or move inner blocks into functions.',\n measure: (fn) => fn.nestingDepth,\n },\n {\n key: 'maxFunctionParameterCount',\n label: 'parameters',\n defaultLimits: { warning: 6, error: Infinity },\n hint: 'group related parameters into one object or split the function.',\n measure: (fn) => fn.parameterCount,\n },\n {\n key: 'maxFunctionHalsteadVolume',\n label: 'Halstead volume',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function holds too many operators and operands; move cohesive parts into helper functions.',\n measure: (fn) => fn.halstead.volume,\n includesNestedFunctions: true,\n },\n {\n key: 'maxFunctionHalsteadDifficulty',\n label: 'Halstead difficulty',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function reuses the same operands many times; split it so each part handles fewer values.',\n measure: (fn) => fn.halstead.difficulty,\n },\n {\n key: 'maxFunctionHalsteadEffort',\n label: 'Halstead effort',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'the function is both large and dense; split it into smaller functions with fewer values each.',\n measure: (fn) => fn.halstead.effort,\n },\n {\n key: 'maxFunctionDepDegree',\n label: 'DepDegree',\n defaultLimits: { warning: Infinity, error: Infinity },\n hint: 'too many values flow between the variables of the function; split it so each part works on fewer variables.',\n measure: (fn) => fn.depDegree,\n includesNestedFunctions: true,\n },\n];\n\nexport const fileThresholds: readonly Threshold<CodeMetrics>[] = [\n {\n key: 'maxFileNcss',\n label: 'file NCSS',\n defaultLimits: { warning: 400, error: 1000 },\n hint: 'split the file into modules with one responsibility each, for example by moving a class or the helpers that serve only one function into a file of their own.',\n measure: (file) => file.ncssCount,\n },\n];\n\n/**\n * A duplicated block violates when it holds at least this many duplicated lines, its code lines\n * matched in another copy: comment-only and blank lines do not count, so a copy reads the same\n * length whether or not it is commented, and neither do the lines the copies do not share, those\n * between the matched parts of an edited copy and those a near-miss copy adds or rewrites.\n */\nexport const duplicationThreshold: Threshold<{ duplicatedLineCount: number }> = {\n key: 'minDuplicateLines',\n label: 'duplicated lines',\n defaultLimits: { warning: 15, error: Infinity },\n hint: 'extract the repeated code into one shared function or module and call it from every location.',\n measure: (block) => block.duplicatedLineCount,\n};\n\nexport const thresholds: readonly Threshold[] = [...functionThresholds, ...fileThresholds, duplicationThreshold];\n\n/** The name a report gives a threshold's measured value: its key without the `max`/`min` prefix. */\nexport function metricNameOf(threshold: Threshold): string {\n const name = threshold.key.slice(3);\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** One level of the `thresholds` config section; a missing key falls back to the less specific layer. */\nexport interface LevelThresholdsConfig {\n limits: Limits;\n /** Overrides for the files of one language, keyed by language name. */\n languages: Readonly<Record<string, Limits>>;\n}\n\nexport type ThresholdsConfig = Readonly<Partial<Record<Level, LevelThresholdsConfig>>>;\n\n/**\n * The limits of each level applying to files of each language, with precedence command-line\n * options > the config's per-language overrides > the config's limits > built-in defaults.\n */\nexport function resolveLimits(\n cliLimits: LimitsByLevel,\n config: ThresholdsConfig | undefined\n): (language: string) => LimitsByLevel {\n const resolveLevel = (level: Level, language: string): Limits => {\n const levelConfig = config?.[level];\n return {\n ...Object.fromEntries(thresholds.map(({ key, defaultLimits }) => [key, defaultLimits[level]])),\n ...levelConfig?.limits,\n // Object.hasOwn: a language named like an Object.prototype member must not read an inherited value.\n ...(levelConfig && Object.hasOwn(levelConfig.languages, language) ? levelConfig.languages[language] : undefined),\n ...cliLimits[level],\n };\n };\n return (language) => ({ warning: resolveLevel('warning', language), error: resolveLevel('error', language) });\n}\n"],"mappings":"AAGA,MAAa,EAAS,CAAC,UAAW,OAAO,EA4B5B,EAA4D,CACvE,CACE,IAAK,iCACL,MAAO,uBACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,8FACN,QAAU,GAAO,EAAG,oBACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,kCACL,MAAO,wBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,qHACN,QAAU,GAAO,EAAG,oBACtB,EACA,CACE,IAAK,kBACL,MAAO,OACP,cAAe,CAAE,QAAS,GAAI,MAAO,EAAG,EACxC,KAAM,2EACN,QAAU,GAAO,EAAG,KACpB,wBAAyB,EAC3B,EACA,CACE,IAAK,0BACL,MAAO,gBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,oFACN,QAAU,GAAO,EAAG,YACtB,EACA,CACE,IAAK,4BACL,MAAO,aACP,cAAe,CAAE,QAAS,EAAG,MAAO,GAAS,EAC7C,KAAM,kEACN,QAAU,GAAO,EAAG,cACtB,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,iGACN,QAAU,GAAO,EAAG,SAAS,OAC7B,wBAAyB,EAC3B,EACA,CACE,IAAK,gCACL,MAAO,sBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAO,EAAG,SAAS,UAC/B,EACA,CACE,IAAK,4BACL,MAAO,kBACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,gGACN,QAAU,GAAO,EAAG,SAAS,MAC/B,EACA,CACE,IAAK,uBACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAU,MAAO,GAAS,EACpD,KAAM,8GACN,QAAU,GAAO,EAAG,UACpB,wBAAyB,EAC3B,CACF,EAEa,EAAoD,CAC/D,CACE,IAAK,cACL,MAAO,YACP,cAAe,CAAE,QAAS,IAAK,MAAO,GAAK,EAC3C,KAAM,gKACN,QAAU,GAAS,EAAK,SAC1B,CACF,EAQa,EAAmE,CAC9E,IAAK,oBACL,MAAO,mBACP,cAAe,CAAE,QAAS,GAAI,MAAO,GAAS,EAC9C,KAAM,gGACN,QAAU,GAAU,EAAM,mBAC5B,EAEa,EAAmC,CAAC,GAAG,EAAoB,GAAG,EAAgB,CAAoB,EAG/G,SAAgB,EAAa,EAA8B,CACzD,IAAM,EAAO,EAAU,IAAI,MAAM,CAAC,EAClC,OAAO,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,EAAI,EAAK,MAAM,CAAC,CACpD,CAeA,SAAgB,EACd,EACA,EACqC,CACrC,IAAM,GAAgB,EAAc,IAA6B,CAC/D,IAAM,EAAc,IAAS,GAC7B,MAAO,CACL,GAAG,OAAO,YAAY,EAAW,KAAK,CAAE,MAAK,mBAAoB,CAAC,EAAK,EAAc,EAAM,CAAC,CAAC,EAC7F,GAAG,GAAa,OAEhB,GAAI,GAAe,OAAO,OAAO,EAAY,UAAW,CAAQ,EAAI,EAAY,UAAU,GAAY,IAAA,GACtG,GAAG,EAAU,EACf,CACF,EACA,MAAQ,KAAc,CAAE,QAAS,EAAa,UAAW,CAAQ,EAAG,MAAO,EAAa,QAAS,CAAQ,CAAE,EAC7G"}
|
package/dist/types.d.ts
CHANGED
|
@@ -78,11 +78,21 @@ export interface FunctionMetrics {
|
|
|
78
78
|
* find a like-named member of it (`void gauge::f()` inside `namespace gauge` holding a
|
|
79
79
|
* `struct gauge`).
|
|
80
80
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
81
|
+
* A function written as a value (a lambda, a closure, a function expression) is a member of
|
|
82
|
+
* what the declaration binding it is declared in, as a declared function would be: the class of
|
|
83
|
+
* a field it initializes, the declared namespace or module of a constant (a file is neither, so
|
|
84
|
+
* a constant at its top level has no owner). A member of a JavaScript or TypeScript object literal is reached through the path
|
|
85
|
+
* the object is bound to: `api` for `const api = { load() {} }`, `Rules.handlers` for the field
|
|
86
|
+
* of a class, `api.admin` for the value of a property, `module.exports` for an assignment.
|
|
87
|
+
*
|
|
88
|
+
* Absent for a function without a name, for a function expression with a name of its own (which
|
|
89
|
+
* is no member of what binds the expression), for one passed as an argument or assigned to a member,
|
|
90
|
+
* for a function written as a value inside another function or bound to a plain name in a Ruby
|
|
91
|
+
* class body (a local variable of that body), for a member of an anonymous class
|
|
92
|
+
* or of an object literal bound to no name, and for a function local to an initializer block. So
|
|
93
|
+
* a method of a class declared inside a function has it, as has a member of an object literal a
|
|
94
|
+
* function binds to a local name (`api` for `const api = { run() {} }`), and a closure inside a
|
|
95
|
+
* method does not.
|
|
86
96
|
*/
|
|
87
97
|
containerName?: string;
|
|
88
98
|
/**
|
|
@@ -157,8 +167,8 @@ export interface DuplicationMetrics {
|
|
|
157
167
|
/** Number of distinct lines covered by any counted duplicate occurrence (originals included). */
|
|
158
168
|
duplicateLineCount: number;
|
|
159
169
|
/**
|
|
160
|
-
* The 1-based lines behind duplicateLineCount, sorted ascending:
|
|
161
|
-
*
|
|
170
|
+
* The 1-based lines behind duplicateLineCount, sorted ascending: the union of the occurrences'
|
|
171
|
+
* `lineNumbers`. Exposed so consumers combining within-file and cross-file coverage can union exact
|
|
162
172
|
* line sets instead of over-counting from block bounding ranges (which include the unmatched gap
|
|
163
173
|
* of a merged clone and comment/blank lines).
|
|
164
174
|
*/
|
|
@@ -174,7 +184,19 @@ export interface DuplicationMetrics {
|
|
|
174
184
|
}
|
|
175
185
|
export interface DuplicateBlockOccurrence {
|
|
176
186
|
endLine: number;
|
|
187
|
+
/**
|
|
188
|
+
* The 1-based code lines carrying the occurrence's matched tokens, sorted ascending: the lines
|
|
189
|
+
* from startLine to endLine without comment-only and blank lines, the unmatched gap of a merged
|
|
190
|
+
* clone, what lies between the matched cores of a near-miss block, and the lines of a near-miss
|
|
191
|
+
* block or core no more than half of whose tokens its copies match.
|
|
192
|
+
*/
|
|
193
|
+
lineNumbers: number[];
|
|
177
194
|
startLine: number;
|
|
195
|
+
/**
|
|
196
|
+
* The tokens of its matched runs (the gap of a merged clone is not counted), or of a near-miss
|
|
197
|
+
* block or its matched cores with the edited tokens among them.
|
|
198
|
+
*/
|
|
199
|
+
tokenCount: number;
|
|
178
200
|
}
|
|
179
201
|
export interface CodeMetrics {
|
|
180
202
|
language: LanguageName;
|
package/dist/wasmBinding.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("./nativeMetrics.cjs"),t=new TextEncoder,n=new TextDecoder;function r(e){let t,r,s=a=>{if(!t){if(r)throw r;try{t=i(e)}catch(e){throw r=e,e}}let{exports:o,stderr:s}=t;s.length=0;let c;try{c=a(o)}catch(e){throw t=void 0,Error(`The code-gauge WebAssembly module crashed: ${s.join(``).trim()||String(e)}`,{cause:e})}let l=n.decode(new Uint8Array(o.memory.buffer,o.result_ptr(),o.result_len()));if(c!==0)throw Error(l);return l},c=(e,t,n,r,i,c,l)=>s(s=>s.measure_code(...a(s,e),...a(s,t),Number(n),o(r),o(i),o(c),Number(l)));return{measureCodeNative:c,measureCodeNativeAsync:async(...e)=>c(...e),collectCrossFileDataNative:(e,t,n)=>s(r=>r.collect_cross_file_data(...a(r,e),...a(r,t),o(n))),collectFunctionTokenSequencesNative:(e,t)=>s(n=>n.collect_function_token_sequences(...a(n,e),...a(n,t)))}}function i(t){let n=[],r,i=new WebAssembly.Instance(t,{wasi_snapshot_preview1:c(()=>r,n)}).exports;r=i.memory;let a=i.payload_version();if(a!==
|
|
1
|
+
"use strict";const e=require("./nativeMetrics.cjs"),t=new TextEncoder,n=new TextDecoder;function r(e){let t,r,s=a=>{if(!t){if(r)throw r;try{t=i(e)}catch(e){throw r=e,e}}let{exports:o,stderr:s}=t;s.length=0;let c;try{c=a(o)}catch(e){throw t=void 0,Error(`The code-gauge WebAssembly module crashed: ${s.join(``).trim()||String(e)}`,{cause:e})}let l=n.decode(new Uint8Array(o.memory.buffer,o.result_ptr(),o.result_len()));if(c!==0)throw Error(l);return l},c=(e,t,n,r,i,c,l)=>s(s=>s.measure_code(...a(s,e),...a(s,t),Number(n),o(r),o(i),o(c),Number(l)));return{measureCodeNative:c,measureCodeNativeAsync:async(...e)=>c(...e),collectCrossFileDataNative:(e,t,n)=>s(r=>r.collect_cross_file_data(...a(r,e),...a(r,t),o(n))),collectFunctionTokenSequencesNative:(e,t)=>s(n=>n.collect_function_token_sequences(...a(n,e),...a(n,t)))}}function i(t){let n=[],r,i=new WebAssembly.Instance(t,{wasi_snapshot_preview1:c(()=>r,n)}).exports;r=i.memory;let a=i.payload_version();if(a!==12)throw new e.NativeAddonError(`The code-gauge WebAssembly module has payload version ${a}, but 12 is expected; reinstall code-gauge, or in a code-gauge repository checkout, rebuild it with \`bun run build-wasm\``);return{exports:i,stderr:n}}function a(e,n){let r=t.encode(n),i=e.alloc(r.length);return new Uint8Array(e.memory.buffer,i,r.length).set(r),[i,r.length]}function o(e){return e??-1}const s=65536;function c(e,t){let r=()=>new DataView(e().buffer),i=()=>8;return{environ_get:()=>0,environ_sizes_get:(e,t)=>(r().setUint32(e,0,!0),r().setUint32(t,0,!0),0),clock_time_get:(e,t,n)=>(r().setBigUint64(n,BigInt(Date.now())*1000000n,!0),0),random_get:(t,n)=>{for(let r=0;r<n;r+=s)crypto.getRandomValues(new Uint8Array(e().buffer,t+r,Math.min(s,n-r)));return 0},fd_write:(i,a,o,s)=>{let c=r(),l=0;for(let r=0;r<o;r++){let o=c.getUint32(a+r*8,!0),s=c.getUint32(a+r*8+4,!0);i===2&&t.push(n.decode(new Uint8Array(e().buffer,o,s))),l+=s}return c.setUint32(s,l,!0),0},fd_close:i,fd_fdstat_get:i,fd_fdstat_set_flags:i,fd_read:i,fd_seek:i,proc_exit:e=>{throw Error(`The code-gauge WebAssembly module exited with code ${e}`)}}}exports.createWasmBinding=r;
|
|
2
2
|
//# sourceMappingURL=wasmBinding.cjs.map
|
package/dist/wasmBinding.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{NativeAddonError as e}from"./nativeMetrics.js";const t=new TextEncoder,n=new TextDecoder;function r(e){let t,r,s=a=>{if(!t){if(r)throw r;try{t=i(e)}catch(e){throw r=e,e}}let{exports:o,stderr:s}=t;s.length=0;let c;try{c=a(o)}catch(e){throw t=void 0,Error(`The code-gauge WebAssembly module crashed: ${s.join(``).trim()||String(e)}`,{cause:e})}let l=n.decode(new Uint8Array(o.memory.buffer,o.result_ptr(),o.result_len()));if(c!==0)throw Error(l);return l},c=(e,t,n,r,i,c,l)=>s(s=>s.measure_code(...a(s,e),...a(s,t),Number(n),o(r),o(i),o(c),Number(l)));return{measureCodeNative:c,measureCodeNativeAsync:async(...e)=>c(...e),collectCrossFileDataNative:(e,t,n)=>s(r=>r.collect_cross_file_data(...a(r,e),...a(r,t),o(n))),collectFunctionTokenSequencesNative:(e,t)=>s(n=>n.collect_function_token_sequences(...a(n,e),...a(n,t)))}}function i(t){let n=[],r,i=new WebAssembly.Instance(t,{wasi_snapshot_preview1:c(()=>r,n)}).exports;r=i.memory;let a=i.payload_version();if(a!==
|
|
1
|
+
import{NativeAddonError as e}from"./nativeMetrics.js";const t=new TextEncoder,n=new TextDecoder;function r(e){let t,r,s=a=>{if(!t){if(r)throw r;try{t=i(e)}catch(e){throw r=e,e}}let{exports:o,stderr:s}=t;s.length=0;let c;try{c=a(o)}catch(e){throw t=void 0,Error(`The code-gauge WebAssembly module crashed: ${s.join(``).trim()||String(e)}`,{cause:e})}let l=n.decode(new Uint8Array(o.memory.buffer,o.result_ptr(),o.result_len()));if(c!==0)throw Error(l);return l},c=(e,t,n,r,i,c,l)=>s(s=>s.measure_code(...a(s,e),...a(s,t),Number(n),o(r),o(i),o(c),Number(l)));return{measureCodeNative:c,measureCodeNativeAsync:async(...e)=>c(...e),collectCrossFileDataNative:(e,t,n)=>s(r=>r.collect_cross_file_data(...a(r,e),...a(r,t),o(n))),collectFunctionTokenSequencesNative:(e,t)=>s(n=>n.collect_function_token_sequences(...a(n,e),...a(n,t)))}}function i(t){let n=[],r,i=new WebAssembly.Instance(t,{wasi_snapshot_preview1:c(()=>r,n)}).exports;r=i.memory;let a=i.payload_version();if(a!==12)throw new e(`The code-gauge WebAssembly module has payload version ${a}, but 12 is expected; reinstall code-gauge, or in a code-gauge repository checkout, rebuild it with \`bun run build-wasm\``);return{exports:i,stderr:n}}function a(e,n){let r=t.encode(n),i=e.alloc(r.length);return new Uint8Array(e.memory.buffer,i,r.length).set(r),[i,r.length]}function o(e){return e??-1}const s=65536;function c(e,t){let r=()=>new DataView(e().buffer),i=()=>8;return{environ_get:()=>0,environ_sizes_get:(e,t)=>(r().setUint32(e,0,!0),r().setUint32(t,0,!0),0),clock_time_get:(e,t,n)=>(r().setBigUint64(n,BigInt(Date.now())*1000000n,!0),0),random_get:(t,n)=>{for(let r=0;r<n;r+=s)crypto.getRandomValues(new Uint8Array(e().buffer,t+r,Math.min(s,n-r)));return 0},fd_write:(i,a,o,s)=>{let c=r(),l=0;for(let r=0;r<o;r++){let o=c.getUint32(a+r*8,!0),s=c.getUint32(a+r*8+4,!0);i===2&&t.push(n.decode(new Uint8Array(e().buffer,o,s))),l+=s}return c.setUint32(s,l,!0),0},fd_close:i,fd_fdstat_get:i,fd_fdstat_set_flags:i,fd_read:i,fd_seek:i,proc_exit:e=>{throw Error(`The code-gauge WebAssembly module exited with code ${e}`)}}}export{r as createWasmBinding};
|
|
2
2
|
//# sourceMappingURL=wasmBinding.js.map
|