code-gauge 3.1.0 → 4.1.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.
Files changed (63) hide show
  1. package/README.md +16 -15
  2. package/dist/crossFileDuplication.cjs +1 -1
  3. package/dist/crossFileDuplication.cjs.map +1 -1
  4. package/dist/crossFileDuplication.js +1 -1
  5. package/dist/crossFileDuplication.js.map +1 -1
  6. package/dist/diffCommand.cjs +1 -1
  7. package/dist/diffCommand.cjs.map +1 -1
  8. package/dist/diffCommand.js +1 -1
  9. package/dist/diffCommand.js.map +1 -1
  10. package/dist/duplication.cjs +1 -1
  11. package/dist/duplication.cjs.map +1 -1
  12. package/dist/duplication.d.ts +14 -28
  13. package/dist/duplication.js +1 -1
  14. package/dist/duplication.js.map +1 -1
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.js +1 -1
  18. package/dist/languages.cjs +1 -1
  19. package/dist/languages.cjs.map +1 -1
  20. package/dist/languages.d.ts +5 -0
  21. package/dist/languages.js +1 -1
  22. package/dist/languages.js.map +1 -1
  23. package/dist/metrics.cjs +1 -1
  24. package/dist/metrics.cjs.map +1 -1
  25. package/dist/metrics.d.ts +14 -12
  26. package/dist/metrics.js +1 -1
  27. package/dist/metrics.js.map +1 -1
  28. package/dist/nativeMetrics.cjs +3 -1
  29. package/dist/nativeMetrics.cjs.map +1 -1
  30. package/dist/nativeMetrics.d.ts +24 -9
  31. package/dist/nativeMetrics.js +3 -1
  32. package/dist/nativeMetrics.js.map +1 -1
  33. package/dist/scan.cjs +1 -1
  34. package/dist/scan.cjs.map +1 -1
  35. package/dist/scan.js +1 -1
  36. package/dist/scan.js.map +1 -1
  37. package/dist/types.d.ts +5 -13
  38. package/native/Cargo.lock +523 -0
  39. package/native/Cargo.toml +45 -0
  40. package/native/build.rs +3 -0
  41. package/native/src/complexity.rs +627 -0
  42. package/native/src/dep_degree.rs +253 -0
  43. package/native/src/duplication.rs +2007 -0
  44. package/native/src/functions.rs +345 -0
  45. package/native/src/languages.rs +647 -0
  46. package/native/src/lib.rs +101 -0
  47. package/native/src/measure.rs +590 -0
  48. package/native/src/ncss.rs +263 -0
  49. package/native/src/types.rs +135 -0
  50. package/native/src/util.rs +139 -0
  51. package/package.json +17 -19
  52. package/scripts/buildNative.mjs +25 -0
  53. package/scripts/installNative.mjs +96 -0
  54. package/dist/depDegree.cjs +0 -2
  55. package/dist/depDegree.cjs.map +0 -1
  56. package/dist/depDegree.d.ts +0 -12
  57. package/dist/depDegree.js +0 -2
  58. package/dist/depDegree.js.map +0 -1
  59. package/dist/ncss.cjs +0 -2
  60. package/dist/ncss.cjs.map +0 -1
  61. package/dist/ncss.d.ts +0 -17
  62. package/dist/ncss.js +0 -2
  63. package/dist/ncss.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"duplication.js","names":[],"sources":["../src/duplication.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport { dedupeByRegion, selectMaximalGroups } from './duplicateSelection.js';\nimport type { DuplicationMetrics, DuplicationOptions } from './types.js';\n\n/**\n * Block-like nodes considered as whole-subtree duplicate candidates. Detection itself is\n * token-based, so this set only decides which subtrees are compared; Ruby's keyword-like node\n * types (`if`, `case`, ...) are safe here because only named nodes become candidates.\n */\nconst duplicateBlockTypes = new Set([\n 'statement_block',\n 'block',\n 'compound_statement',\n 'body_statement',\n 'constructor_body',\n 'do_block',\n 'if_statement',\n 'for_statement',\n 'for_in_statement',\n 'enhanced_for_statement',\n 'for_range_loop',\n 'while_statement',\n 'do_statement',\n 'try_statement',\n 'try_with_resources_statement',\n 'with_statement',\n 'switch_statement',\n 'switch_expression',\n 'switch_case',\n 'switch_block_statement_group',\n 'switch_rule',\n 'case_clause',\n 'case_statement',\n 'match_statement',\n 'match_arm',\n 'except_clause',\n 'catch_clause',\n 'finally_clause',\n 'elif_clause',\n 'ensure',\n 'expression_statement',\n 'return_statement',\n 'return_expression',\n 'if_expression',\n 'for_expression',\n 'while_expression',\n 'loop_expression',\n 'match_expression',\n 'jsx_element',\n 'jsx_self_closing_element',\n // Ruby\n 'if',\n 'unless',\n 'case',\n 'case_match',\n 'while',\n 'until',\n 'for',\n 'begin',\n 'when',\n]);\n\n/** Nodes whose direct named children form statement sequences scanned for copy-pasted runs. */\nconst statementContainerTypes = new Set([\n 'program',\n 'source_file',\n 'translation_unit',\n 'module',\n 'statement_block',\n 'block',\n 'compound_statement',\n 'body_statement',\n 'constructor_body',\n 'class_body',\n 'block_body',\n 'do_block',\n // Ruby loop bodies are a named `do` node, and `ensure` holds statements directly.\n 'do',\n 'ensure',\n 'then',\n 'else',\n // Case-like nodes hold their statements directly, without an inner block.\n 'case_statement',\n 'switch_block_statement_group',\n 'switch_rule',\n 'expression_case',\n 'type_case',\n 'communication_case',\n 'default_case',\n]);\n\n/**\n * Identifier leaves anonymized by occurrence order so consistently renamed copies still match.\n * Member/type names (`property_identifier`, `field_identifier`, `type_identifier`, ...) are kept\n * verbatim instead: calling a different API is a semantic difference, not a rename.\n */\nconst anonymizedIdentifierTypes = new Set([\n 'identifier',\n 'constant',\n 'instance_variable',\n 'class_variable',\n 'global_variable',\n]);\n\n/**\n * JS shorthand properties (`{ alpha }`) both emit the property name (semantic output shape) and\n * reference the binding, so they tokenize as the desugared `name: binding` — one verbatim text\n * token plus one anonymized id token — matching how the explicit form is tokenized.\n */\nconst shorthandPropertyTypes = new Set(['shorthand_property_identifier', 'shorthand_property_identifier_pattern']);\n\n/** Literal leaves normalized to a kind tag so copies differing only in literal values still match. */\nconst literalKindByType = new Map([\n ['number', '#num'],\n ['number_literal', '#num'],\n ['integer', '#num'],\n ['float', '#num'],\n ['integer_literal', '#num'],\n ['float_literal', '#num'],\n ['int_literal', '#num'],\n ['rune_literal', '#char'],\n ['imaginary_literal', '#num'],\n ['decimal_integer_literal', '#num'],\n ['hex_integer_literal', '#num'],\n ['octal_integer_literal', '#num'],\n ['binary_integer_literal', '#num'],\n ['decimal_floating_point_literal', '#num'],\n ['hex_floating_point_literal', '#num'],\n ['string_fragment', '#str'],\n ['multiline_string_fragment', '#str'],\n ['string_content', '#str'],\n ['raw_string_content', '#str'],\n ['heredoc_content', '#str'],\n // Heredoc marker names (`<<~SQL` vs `<<~QUERY`) have no string-value significance.\n ['heredoc_beginning', '#heredoc'],\n ['heredoc_end', '#heredoc'],\n // Strings are leaves in some grammars (Go/Rust) and fragment containers in others.\n ['string', '#str'],\n ['template_string', '#str'],\n ['string_literal', '#str'],\n ['interpreted_string_literal', '#str'],\n ['raw_string_literal', '#str'],\n ['raw_string', '#str'],\n ['escape_sequence', '#str'],\n ['char_literal', '#char'],\n ['character_literal', '#char'],\n ['character', '#char'],\n ['regex_pattern', '#regex'],\n]);\n\n/**\n * Kind tags whose raw source text re-enters the fingerprint in literal-dense (data-like) regions.\n * `#heredoc` is excluded: heredoc marker names are naming choices, not data values.\n */\nconst valueCarryingLiteralKinds = new Set(['#num', '#str', '#char', '#regex']);\n\nconst commentTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n/**\n * String children that carry actual content, i.e. stringFragmentTypes minus the delimiter nodes\n * (Python's `string_start`/`string_end`), for delimiter-independent literal values.\n */\nconst stringContentFragmentTypes = new Set([\n 'string_fragment',\n 'multiline_string_fragment',\n 'string_content',\n 'raw_string_content',\n 'escape_sequence',\n 'heredoc_content',\n]);\n\n/** Children of a string node that carry only literal content; anything else is interpolation. */\nconst stringFragmentTypes = new Set([\n 'string_fragment',\n 'multiline_string_fragment',\n 'string_content',\n 'raw_string_content',\n 'escape_sequence',\n 'heredoc_content',\n // Python string delimiters are named children; they never carry interpolation.\n 'string_start',\n 'string_end',\n]);\n\n/**\n * Where a grammar names callees/members with a plain `identifier` (Java `method_invocation.name`,\n * Ruby `call.method`, Python `attribute.attribute`, plain calls elsewhere), the leaf in that field\n * must stay verbatim like `property_identifier` does: calling a different API is a semantic\n * difference, not a rename.\n */\nconst semanticNameFieldByParentType = new Map([\n ['call_expression', 'function'],\n ['method_invocation', 'name'],\n ['call', 'method'],\n ['attribute', 'attribute'],\n ['macro_invocation', 'macro'],\n // Java names accessed fields with a plain identifier in the `field` field.\n ['field_access', 'field'],\n // JS/TS `new Foo(...)` names the constructed API in the `constructor` field.\n ['new_expression', 'constructor'],\n // Python `f(timeout=...)` and Java `@Anno(key=...)` name parameters of the callee's API.\n ['keyword_argument', 'name'],\n ['element_value_pair', 'key'],\n // Rust turbofish and C++ template callees (`compute::<u32>(...)`); type arguments stay\n // anonymized via their own node types.\n ['generic_function', 'function'],\n ['template_function', 'name'],\n]);\n\nexport const defaultDuplicationOptions: Required<DuplicationOptions> = {\n minTokens: 40,\n maxGapTokens: 30,\n minSimilarityPercent: 70,\n};\n\n/**\n * N-gram size for the near-miss candidate index. 5 is NIL's published default (Nakagawa et al.,\n * ESEC/FSE 2021): short enough that edited clones still share many n-grams, long enough that\n * unrelated blocks rarely collide.\n */\nconst nearMissNgramSize = 5;\n/**\n * Blocks sharing fewer than this percentage of the smaller block's distinct n-grams skip LCS\n * verification entirely (NIL's filtration phase, default 10%): a pair below it cannot reach any\n * useful similarity, and the cheap set-overlap check prunes the quadratic candidate space.\n */\nconst nearMissFiltrationPercent = 10;\n/**\n * Token-level LCS over normalized code is dominated by punctuation and keywords, so a structural\n * 70% match alone is weak evidence of copying: two same-skeleton functions calling entirely\n * different APIs can exceed it. A near-miss pair must therefore also share at least half of the\n * larger block's content-bearing tokens (verbatim-kept names and literal values — the tokens that\n * distinguish WHAT the code does rather than how it is shaped). The bound is exclusive: a\n * value-mapping predicate pair (`x.type === 'a' || ...` with equal branch counts and zero shared\n * values) shares exactly half its content — the repeated member name — and must not pass.\n */\nconst minContentSimilarityPercent = 50;\n\n/** Minimum consecutive statements for a statement-sequence duplicate candidate. */\nconst minSequenceStatementCount = 2;\n/**\n * Caps the window length so statement-sequence enumeration stays linear in the statement count.\n * Heterogeneous clones longer than the cap are reported as capped windows (a deliberate\n * conservative undercount trading completeness for bounded discovery cost).\n */\nconst maxSequenceStatementCount = 100;\n\n/**\n * A region whose normalized tokens are at least 20% literal values is data-like (a lookup table, a\n * constant list, a value-mapping switch), not logic: literal values re-enter its fingerprint so\n * tables that merely share their shape stop counting as copy-paste. Logic-heavy code sits well\n * below the bound (5-10% literals) while object/array tables sit above it (25-50%); punctuation\n * and member names dilute tables, which is why the bound is far below half. Compared in integer\n * math (5 * literals >= total) so the TypeScript and native backends cannot disagree on the\n * boundary.\n */\nfunction isLiteralDense(literalCount: number, tokenCount: number): boolean {\n return literalCount * 5 >= tokenCount;\n}\n\nexport interface Token {\n /** Normalization target: identifiers to anonymize, literal kind tags, or the raw token text. */\n kind: 'id' | 'text';\n text: string;\n /**\n * Two INDEPENDENT hashes of `text` (djb2 and FNV-1a), precomputed so fingerprinting nested\n * regions never re-hashes a token. Feeding the same per-token hash to both fingerprint\n * accumulators would collapse the key to 32 effective bits: one djb2 collision between two\n * token texts would then equate whole regions.\n */\n textHash: number;\n textHash2: number;\n /** Hash pair of a value-carrying literal's value, folded into data-like region fingerprints. */\n literalHash?: number;\n literalHash2?: number;\n /**\n * True for verbatim-kept NAMES (member/callee/type names, named grammar leaves): together with\n * value-carrying literals these are the content-bearing tokens the near-miss content gate\n * counts. Keywords, operators, and punctuation come from unnamed nodes and stay false.\n */\n isName?: boolean;\n /** 0-based source rows the token occupies, so line coverage counts only matched-token lines. */\n startRow: number;\n endRow: number;\n}\n\n/** A token span with its source position; plain data so cross-file matching can retain it. */\nexport interface TokenRange {\n startTokenIndex: number;\n endTokenIndex: number;\n startIndex: number;\n endIndex: number;\n startLine: number;\n endLine: number;\n}\n\n/** A contiguous run of matched tokens; gapped (merged) duplicates carry several per occurrence. */\ninterface TokenSegment {\n startTokenIndex: number;\n endTokenIndex: number;\n}\n\ninterface DuplicateCandidate {\n fingerprint: string;\n tokenCount: number;\n startTokenIndex: number;\n endTokenIndex: number;\n startIndex: number;\n endIndex: number;\n startLine: number;\n endLine: number;\n}\n\nexport interface CountedOccurrence {\n /** Matched token runs; more than one once gapped groups are merged. */\n segments: TokenSegment[];\n /**\n * Set on a retained group's occurrences that a partial gapped merge also paired into a merged\n * group: their spans are counted there, so block counting must not count them again.\n */\n sharedWithMergedGroup?: boolean;\n /** Sum of segment token counts (the gap tokens are not matched content). */\n tokenCount: number;\n startTokenIndex: number;\n endTokenIndex: number;\n startIndex: number;\n endIndex: number;\n startLine: number;\n endLine: number;\n}\n\n/** A duplicate region found in one file, exported for cross-file matching by fingerprint. */\nexport interface CrossFileDuplicateCandidate {\n /** Content key: equal fingerprints mean equal normalized token sequences (up to hash collision). */\n fingerprint: string;\n tokenCount: number;\n /** Token positions within the owning file, for cross-file gapped (Type-3) merging. */\n startTokenIndex: number;\n endTokenIndex: number;\n startIndex: number;\n endIndex: number;\n startLine: number;\n endLine: number;\n}\n\n/**\n * One file's contribution to cross-file clone detection: catalogued candidates plus the normalized\n * token stream and statement structure, so the project-level pass can match partial statement runs\n * (windows that a single file cannot know repeat elsewhere) and merge gap-adjacent groups.\n */\nexport interface CrossFileDuplicationFileData {\n candidates: CrossFileDuplicateCandidate[];\n tokens: Token[];\n containerStatements: TokenRange[][];\n /**\n * 1-based lines that are neither blank nor comment-only, so cross-file line coverage counts only\n * code lines (blank rows inside multi-row tokens such as template literals carry no content).\n * Optional for backward compatibility; without it, every matched-token row counts.\n */\n codeLineNumbers?: Set<number>;\n}\n\n/**\n * Detects copy-pasted regions within a file. Regions are compared by their normalized token\n * sequence: identifiers are anonymized consistently by first-occurrence order (`a.f(a, b)` matches\n * `x.f(x, y)` but not `x.f(y, z)`), literals are normalized by kind, and member/type names and all\n * keywords/operators are kept verbatim. Literal-dense (data-like) regions additionally require\n * equal literal values. Candidates are whole block-like subtrees plus runs of consecutive sibling\n * statements, so a copy pasted into the middle of a longer block is still found. Only maximal,\n * non-overlapping regions are counted, and adjacent groups separated by a small token gap merge\n * into one gapped (Type-3) clone group. Blocks the exact pipeline misses are additionally compared\n * by similarity (n-gram filtration then token-level LCS, NiCad/NIL-style), so near-miss (Type-3)\n * clones whose edits fragment them below `minTokens` are still reported.\n */\nexport function measureDuplication(\n root: Parser.SyntaxNode,\n codeLineNumbers: Set<number>,\n options?: DuplicationOptions\n): DuplicationMetrics {\n const minTokens = options?.minTokens ?? defaultDuplicationOptions.minTokens;\n const maxGapTokens = options?.maxGapTokens ?? defaultDuplicationOptions.maxGapTokens;\n const minSimilarityPercent = options?.minSimilarityPercent ?? defaultDuplicationOptions.minSimilarityPercent;\n const tokens: Token[] = [];\n const blockRanges: TokenRange[] = [];\n const containerStatementRanges: TokenRange[][] = [];\n collectTokens(root, tokens, blockRanges, containerStatementRanges);\n const literalCountPrefix = buildLiteralCountPrefix(tokens);\n\n const candidates = [\n ...collectBlockCandidates(tokens, literalCountPrefix, blockRanges, minTokens),\n ...collectSequenceCandidates(tokens, literalCountPrefix, containerStatementRanges, minTokens),\n ];\n const counted = selectMaximalGroups(candidates, (group) => group.length >= 2);\n const groups = mergeAdjacentGroups(toCountedGroups(counted), maxGapTokens);\n const nearMissGroups = collectNearMissGroups(\n tokens,\n literalCountPrefix,\n blockRanges,\n minTokens,\n minSimilarityPercent,\n groups\n );\n // Near-miss clustering can merge exact groups away, leaving empty entries behind.\n const reportedGroups = [...groups.filter((group) => group.length > 0), ...nearMissGroups];\n return summarizeDuplicates(reportedGroups, codeLineNumbers, tokens);\n}\n\n/**\n * Collects this file's contribution to cross-file clone detection: fingerprinted candidates for\n * whole block-like subtrees plus each statement container's full run (so wholly copied files and\n * class bodies match even when no inner block clears the threshold on its own), together with the\n * normalized token stream and statement structure that let measureCrossFileDuplication match\n * partial statement runs and merge gap-adjacent groups project-wide. Nested and overlapping\n * candidates are all returned; the project-level selection keeps only maximal ones.\n */\nexport function collectCrossFileDuplicateCandidates(\n root: Parser.SyntaxNode,\n options?: DuplicationOptions\n): CrossFileDuplicationFileData {\n const minTokens = options?.minTokens ?? defaultDuplicationOptions.minTokens;\n const tokens: Token[] = [];\n const blockRanges: TokenRange[] = [];\n const containerStatementRanges: TokenRange[][] = [];\n collectTokens(root, tokens, blockRanges, containerStatementRanges);\n const literalCountPrefix = buildLiteralCountPrefix(tokens);\n\n const candidates = collectBlockCandidates(tokens, literalCountPrefix, blockRanges, minTokens);\n for (const statements of containerStatementRanges) {\n const first = statements[0];\n const last = statements.at(-1);\n if (!first || !last) {\n continue;\n }\n const tokenCount = last.endTokenIndex - first.startTokenIndex;\n if (tokenCount < minTokens) {\n continue;\n }\n candidates.push(\n toCandidate(\n `s:${fingerprintKey(tokens, literalCountPrefix, first.startTokenIndex, last.endTokenIndex)}`,\n first.startTokenIndex,\n last.endTokenIndex,\n first,\n last\n )\n );\n }\n return { candidates: dedupeByRegion(candidates), tokens, containerStatements: containerStatementRanges };\n}\n\nfunction collectTokens(\n root: Parser.SyntaxNode,\n tokens: Token[],\n blockRanges: TokenRange[],\n containerStatementRanges: TokenRange[][]\n): void {\n function visit(node: Parser.SyntaxNode): TokenRange {\n const startTokenIndex = tokens.length;\n const atomicKind = node.childCount === 0 ? undefined : atomicLiteralKind(node);\n if (node.childCount === 0) {\n appendLeafToken(node, tokens);\n } else if (atomicKind !== undefined) {\n // Interpolation-free strings collapse to their kind tag so copies differing only in quote\n // style or content still match; delimiter tokens would otherwise break the equivalence.\n tokens.push(\n makeTextToken(atomicKind, literalValueText(node, atomicKind), node.startPosition.row, node.endPosition.row)\n );\n } else if (!commentTypes.has(node.type)) {\n const statementRanges: TokenRange[] = [];\n const isContainer = node.isNamed && statementContainerTypes.has(node.type);\n for (const child of node.children) {\n const childRange = visit(child);\n if (isContainer && child.isNamed && !commentTypes.has(child.type)) {\n statementRanges.push(childRange);\n }\n }\n // Single-statement containers are recorded too: within-file window enumeration needs two\n // statements and simply yields nothing for them, but cross-file matching must still see a\n // file whose only top-level statement is not a catalogued block type (a lone exported table).\n if (isContainer && statementRanges.length > 0) {\n containerStatementRanges.push(statementRanges);\n }\n }\n\n const range = {\n startTokenIndex,\n endTokenIndex: tokens.length,\n startIndex: node.startIndex,\n endIndex: node.endIndex,\n startLine: node.startPosition.row + 1,\n endLine: node.endPosition.row + 1,\n };\n if (node.isNamed && duplicateBlockTypes.has(node.type)) {\n blockRanges.push(range);\n }\n return range;\n }\n\n visit(root);\n}\n\n/** The kind tag of a string-like node with no interpolation, or undefined to descend normally. */\nfunction atomicLiteralKind(node: Parser.SyntaxNode): string | undefined {\n const kind = node.isNamed ? literalKindByType.get(node.type) : undefined;\n if (kind === undefined) {\n return undefined;\n }\n return node.namedChildren.every((child) => stringFragmentTypes.has(child.type)) ? kind : undefined;\n}\n\nfunction appendLeafToken(node: Parser.SyntaxNode, tokens: Token[]): void {\n if (commentTypes.has(node.type)) {\n return;\n }\n\n const startRow = node.startPosition.row;\n const endRow = node.endPosition.row;\n if (node.isNamed && shorthandPropertyTypes.has(node.type)) {\n tokens.push(\n makeTextToken(node.text, undefined, startRow, endRow, true),\n makeTextToken(':', undefined, startRow, endRow),\n { kind: 'id', text: node.text, textHash: 0, textHash2: 0, startRow, endRow }\n );\n return;\n }\n\n if (node.isNamed && anonymizedIdentifierTypes.has(node.type) && !isSemanticNameLeaf(node)) {\n tokens.push({ kind: 'id', text: node.text, textHash: 0, textHash2: 0, startRow, endRow });\n return;\n }\n\n // Anything else keeps its text: keywords, operators, punctuation, and semantic names such as\n // `property_identifier`/`type_identifier`, which must distinguish otherwise-identical structures.\n const literalKind = node.isNamed ? literalKindByType.get(node.type) : undefined;\n if (literalKind === undefined) {\n tokens.push(makeTextToken(node.text, undefined, startRow, endRow, node.isNamed));\n } else {\n tokens.push(makeTextToken(literalKind, literalValueText(node, literalKind), startRow, endRow));\n }\n}\n\nfunction makeTextToken(\n text: string,\n literalValueText: string | undefined,\n startRow: number,\n endRow: number,\n isName = false\n): Token {\n const token: Token = { kind: 'text', text, textHash: hashText(text), textHash2: hashText2(text), startRow, endRow };\n if (literalValueText !== undefined && valueCarryingLiteralKinds.has(text)) {\n token.literalHash = hashText(literalValueText);\n token.literalHash2 = hashText2(literalValueText);\n }\n if (isName) {\n token.isName = true;\n }\n return token;\n}\n\n/**\n * The value of a literal as folded into literal-dense fingerprints. Strings hash their CONTENT,\n * not their source spelling: formatters rewrite quote style on paste (`'one'` vs `\"one\"`), so\n * delimiters must not make two copied tables differ. Content comes from the fragment children when\n * the grammar provides them (which also drops Python's `string_start`/`string_end` delimiter\n * nodes), else from the text with one matching pair of surrounding quotes stripped. Numbers keep\n * their raw text: formatters preserve numeric spelling, and canonicalizing values (`0x10` vs `16`)\n * identically in JavaScript and Rust would be far riskier than the rare mismatch it would unify.\n */\nfunction literalValueText(node: Parser.SyntaxNode, kind: string): string {\n if (kind !== '#str' && kind !== '#char') {\n return node.text;\n }\n // Fragment leaves (string_fragment, escape_sequence, heredoc_content, ...) already carry bare\n // content; a quote appearing there is content, not a delimiter.\n if (stringContentFragmentTypes.has(node.type)) {\n return node.text;\n }\n const fragments = node.namedChildren.filter((child) => stringContentFragmentTypes.has(child.type));\n if (fragments.length > 0) {\n return fragments.map((child) => child.text).join('');\n }\n return stripMatchingQuotes(node.text);\n}\n\nconst quoteCharacters = new Set(['\"', \"'\", '`']);\n\nfunction stripMatchingQuotes(text: string): string {\n const first = text[0];\n return text.length >= 2 && first !== undefined && quoteCharacters.has(first) && text.endsWith(first)\n ? text.slice(1, -1)\n : text;\n}\n\n/** literalCountPrefix[i] = value-carrying literal tokens in tokens[0..i), for O(1) density checks. */\nexport function buildLiteralCountPrefix(tokens: Token[]): Int32Array {\n const prefix = new Int32Array(tokens.length + 1);\n for (const [index, token] of tokens.entries()) {\n prefix[index + 1] = (prefix[index] ?? 0) + (token.literalHash === undefined ? 0 : 1);\n }\n return prefix;\n}\n\nfunction isSemanticNameLeaf(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n\n // Java method references (`Foo::bar`) name their identifiers without grammar fields; both the\n // type/object and the referenced method are semantic.\n if (parent.type === 'method_reference') {\n return true;\n }\n\n // `call` names its callee `method` in Ruby but `function` in Python; accept both fields.\n if (parent.type === 'call' && parent.childForFieldName('function')?.id === node.id) {\n return true;\n }\n\n // A Ruby constant receiving a call (`Alpha.new(...)`) names the invoked API; constants used as\n // plain values stay anonymized so renamed clones referencing constants still match.\n if (node.type === 'constant' && parent.type === 'call' && parent.childForFieldName('receiver')?.id === node.id) {\n return true;\n }\n\n // Java static receivers (`Alpha.run(...)`) name the invoked type. The tokenizer has no symbol\n // table, so PascalCase — Java's universal type-naming convention — is the discriminator;\n // camelCase instance receivers stay anonymized for rename tolerance.\n if (\n parent.type === 'method_invocation' &&\n parent.childForFieldName('object')?.id === node.id &&\n /^\\p{Lu}/u.test(node.text)\n ) {\n return true;\n }\n\n // Qualified callees (Rust `crate::alpha::make(...)`, C++ `detail::make(...)`) and generic\n // callees (`compute::<u32>(...)`, `compute<int>(...)`) wrap their identifiers arbitrarily deep;\n // every path/name segment is semantic there — but only in call position, so renamed clones that\n // merely reference scoped constants or `use` paths still match.\n if (\n (parent.type === 'scoped_identifier' || parent.type === 'qualified_identifier') &&\n (parent.childForFieldName('name')?.id === node.id || parent.childForFieldName('path')?.id === node.id)\n ) {\n let outer = parent;\n while (\n outer.parent &&\n (outer.parent.type === 'scoped_identifier' ||\n outer.parent.type === 'qualified_identifier' ||\n outer.parent.type === 'generic_function' ||\n outer.parent.type === 'template_function')\n ) {\n outer = outer.parent;\n }\n if (outer.parent?.type === 'call_expression' && outer.parent.childForFieldName('function')?.id === outer.id) {\n return true;\n }\n }\n\n // Go struct-literal keys (`Config{Timeout: ...}`) have no `key` field in the grammar: the key\n // is the keyed_element's first named child, a literal_element wrapping the identifier.\n if (\n parent.type === 'literal_element' &&\n parent.parent?.type === 'keyed_element' &&\n parent.parent.namedChild(0)?.id === parent.id\n ) {\n return true;\n }\n\n const field = semanticNameFieldByParentType.get(parent.type);\n return field !== undefined && parent.childForFieldName(field)?.id === node.id;\n}\n\nfunction collectBlockCandidates(\n tokens: Token[],\n literalCountPrefix: Int32Array,\n blockRanges: TokenRange[],\n minTokens: number\n): DuplicateCandidate[] {\n const candidates: DuplicateCandidate[] = [];\n for (const range of blockRanges) {\n const tokenCount = range.endTokenIndex - range.startTokenIndex;\n if (tokenCount < minTokens) {\n continue;\n }\n candidates.push(\n toCandidate(\n `b:${fingerprintKey(tokens, literalCountPrefix, range.startTokenIndex, range.endTokenIndex)}`,\n range.startTokenIndex,\n range.endTokenIndex,\n range,\n range\n )\n );\n }\n return candidates;\n}\n\ninterface WindowOccurrences {\n count: number;\n /** -1 once occurrences span more than one container. */\n containerIndex: number;\n /** -1 once occurrences span more than one context (file). */\n contextIndex: number;\n minStart: number;\n maxStart: number;\n}\n\ninterface SequenceWindow {\n containerIndex: number;\n start: number;\n length: number;\n}\n\nfunction collectSequenceCandidates(\n tokens: Token[],\n literalCountPrefix: Int32Array,\n containers: TokenRange[][],\n minTokens: number\n): DuplicateCandidate[] {\n return collectSequenceWindowCandidates([{ tokens, literalCountPrefix, containers }], minTokens, false).map(\n ({ candidate }) => candidate\n );\n}\n\n/** One file's token stream and statement containers, as a window-matching context. */\nexport interface SequenceWindowContext {\n tokens: Token[];\n literalCountPrefix: Int32Array;\n containers: TokenRange[][];\n}\n\nexport interface ContextualSequenceCandidate {\n candidate: DuplicateCandidate;\n contextIndex: number;\n}\n\n/**\n * Enumerates runs of consecutive sibling statements over one or more contexts (files). Every\n * container statement participates; only the window length is capped, so enumeration stays linear\n * in the statement count. Windows are grouped by a cheap rolling hash of per-statement\n * fingerprints, and only locally maximal repeated windows — those whose one-statement extensions\n * stop repeating — become candidates with an exact (window-consistent) fingerprint. Without the\n * maximality filter a degenerate file of near-identical statements would fingerprint every\n * sub-window of every repeated region. With `requireMultipleContexts` a window only counts as\n * repeated when its occurrences span at least two contexts (CPD-style cross-file matching): a\n * repeat confined to one file is that file's own concern, and emitting it here would flood the\n * project-level selection with unusable single-file groups.\n */\nexport function collectSequenceWindowCandidates(\n contexts: SequenceWindowContext[],\n minTokens: number,\n requireMultipleContexts: boolean\n): ContextualSequenceCandidate[] {\n const candidates: ContextualSequenceCandidate[] = [];\n const contextIndexByContainer: number[] = [];\n const containers: TokenRange[][] = [];\n for (const [contextIndex, context] of contexts.entries()) {\n for (const statements of context.containers) {\n contextIndexByContainer.push(contextIndex);\n containers.push(statements);\n }\n }\n const contextAt = (containerIndex: number): SequenceWindowContext | undefined =>\n contexts[contextIndexByContainer[containerIndex] ?? 0];\n const occurrencesByWindowKey = new Map<number, WindowOccurrences>();\n const containerWindows = containers.map((statements, containerIndex) =>\n enumerateContainerWindows(contextAt(containerIndex)?.tokens ?? [], statements, minTokens)\n );\n for (const [containerIndex, windows] of containerWindows.entries()) {\n const contextIndex = contextIndexByContainer[containerIndex] ?? 0;\n for (const [start, row] of windows.windowKeysByStart.entries()) {\n for (const windowKey of row) {\n if (windowKey === undefined) {\n continue;\n }\n const occurrences = occurrencesByWindowKey.get(windowKey);\n if (occurrences) {\n occurrences.count += 1;\n if (occurrences.containerIndex !== containerIndex) {\n occurrences.containerIndex = -1;\n }\n if (occurrences.contextIndex !== contextIndex) {\n occurrences.contextIndex = -1;\n }\n occurrences.minStart = Math.min(occurrences.minStart, start);\n occurrences.maxStart = Math.max(occurrences.maxStart, start);\n } else {\n occurrencesByWindowKey.set(windowKey, {\n count: 1,\n containerIndex,\n contextIndex,\n minStart: start,\n maxStart: start,\n });\n }\n }\n }\n }\n\n // A window only \"repeats\" when two of its occurrences can coexist without overlapping: sliding\n // matches inside a homogeneous run (start spread smaller than the window length) can never both\n // be counted and must neither qualify a window nor dominate its sub-windows. Cross-context\n // matching instead requires occurrences in two contexts, which coexist by construction.\n const repeats = (windowKey: number | undefined, length: number): boolean => {\n if (windowKey === undefined) {\n return false;\n }\n const occurrences = occurrencesByWindowKey.get(windowKey);\n if (occurrences === undefined || occurrences.count < 2) {\n return false;\n }\n if (requireMultipleContexts) {\n return occurrences.contextIndex === -1;\n }\n return occurrences.containerIndex === -1 || occurrences.maxStart - occurrences.minStart >= length;\n };\n\n // A window whose statements all share one normalized shape (sixteen `let x = 0;` declarations,\n // a constant table) is a homogeneous preamble, not a copy-paste: requiring two distinct\n // per-statement shapes keeps such runs out of duplicate groups and the duplication ratio.\n const hasDistinctStatements = (window: SequenceWindow): boolean => {\n const hashes = containerWindows[window.containerIndex]?.statementHashes ?? [];\n const firstHash = hashes[window.start];\n for (let index = window.start + 1; index < window.start + window.length; index += 1) {\n if (hashes[index] !== firstHash) {\n return true;\n }\n }\n return false;\n };\n\n const maximalWindows: SequenceWindow[] = [];\n for (const [containerIndex, windows] of containerWindows.entries()) {\n for (const [start, row] of windows.windowKeysByStart.entries()) {\n for (const [length, windowKey] of row.entries()) {\n if (!repeats(windowKey, length) || !hasDistinctStatements({ containerIndex, start, length })) {\n continue;\n }\n // Dominated windows are skipped: the one-statement extension also repeats, so a larger\n // candidate covering this window exists.\n const extendedRight = windows.windowKeysByStart[start]?.[length + 1];\n const extendedLeft = windows.windowKeysByStart[start - 1]?.[length + 1];\n if (repeats(extendedRight, length + 1) || repeats(extendedLeft, length + 1)) {\n continue;\n }\n maximalWindows.push({ containerIndex, start, length });\n }\n }\n }\n\n // The rolling hash anonymizes identifiers per statement, so a window can look repeated coarsely\n // while its exact (window-consistent) fingerprints differ, and a longer window's match can\n // dominate sub-windows that other copies still need (three copies where only two extend one\n // statement further). Every emitted window therefore exposes its repeating, unvisited\n // sub-windows; `visited` bounds the worklist and lengths strictly decrease, so it terminates.\n const visited = new Set(maximalWindows.map(windowId));\n let frontier = maximalWindows;\n while (frontier.length > 0) {\n const emitted: SequenceWindow[] = [];\n for (const window of frontier) {\n const statements = containers[window.containerIndex];\n const first = statements?.[window.start];\n const last = statements?.[window.start + window.length - 1];\n const context = contextAt(window.containerIndex);\n if (!first || !last || !context) {\n continue;\n }\n const fingerprint = `s:${fingerprintKey(context.tokens, context.literalCountPrefix, first.startTokenIndex, last.endTokenIndex)}`;\n candidates.push({\n candidate: toCandidate(fingerprint, first.startTokenIndex, last.endTokenIndex, first, last),\n contextIndex: contextIndexByContainer[window.containerIndex] ?? 0,\n });\n emitted.push(window);\n }\n frontier = [];\n for (const window of emitted) {\n for (const start of [window.start, window.start + 1]) {\n const subWindow = { containerIndex: window.containerIndex, start, length: window.length - 1 };\n const subWindowKey = containerWindows[window.containerIndex]?.windowKeysByStart[start]?.[subWindow.length];\n if (\n visited.has(windowId(subWindow)) ||\n !repeats(subWindowKey, subWindow.length) ||\n !hasDistinctStatements(subWindow)\n ) {\n continue;\n }\n visited.add(windowId(subWindow));\n frontier.push(subWindow);\n }\n }\n }\n return candidates;\n}\n\nfunction windowId(window: SequenceWindow): string {\n return `${window.containerIndex}:${window.start}:${window.length}`;\n}\n\ninterface ContainerWindows {\n /** windowKeysByStart[start][length] is the rolling-hash key of the window, or undefined if it is below the size thresholds. */\n windowKeysByStart: (number | undefined)[][];\n /** Per-statement fingerprint hashes, for the distinct-shape requirement on windows. */\n statementHashes: number[];\n}\n\nfunction enumerateContainerWindows(tokens: Token[], statements: TokenRange[], minTokens: number): ContainerWindows {\n const statementHashes = statements.map((statement) =>\n fingerprintHash(tokens, statement.startTokenIndex, statement.endTokenIndex)\n );\n const windowKeysByStart: (number | undefined)[][] = [];\n for (let start = 0; start < statements.length; start += 1) {\n const row: (number | undefined)[] = [];\n let hash = 5381;\n let tokenCount = 0;\n const maxEnd = Math.min(statements.length, start + maxSequenceStatementCount);\n for (let end = start; end < maxEnd; end += 1) {\n const statement = statements[end];\n const statementHash = statementHashes[end];\n if (!statement || statementHash === undefined) {\n break;\n }\n hash = combineHashes(hash, statementHash);\n tokenCount += statement.endTokenIndex - statement.startTokenIndex;\n const statementCount = end - start + 1;\n row[statementCount] =\n statementCount >= minSequenceStatementCount && tokenCount >= minTokens\n ? combineHashes(hash, statementCount)\n : undefined;\n }\n windowKeysByStart.push(row);\n }\n return { windowKeysByStart, statementHashes };\n}\n\n/**\n * Detects near-miss (Type-3) clone groups among block candidates the exact pipeline left\n * unreported, following the locate-filter-verify design of NIL (ESEC/FSE 2021) with NiCad's\n * per-fragment similarity semantics: an inverted index of 5-grams over normalized tokens proposes\n * candidate pairs, a cheap shared-n-gram ratio prunes them, and token-level LCS verifies that the\n * pair is at least `minSimilarityPercent` similar relative to the LARGER block (so a small block\n * embedded in a big one does not count), with a content gate requiring shared names/literal values\n * so same-skeleton code calling different APIs does not pair on punctuation alone. Verified pairs\n * are clustered transitively; each cluster becomes one clone group whose occurrences span whole\n * blocks. Only blocks that are not literal-dense (the data-table guard) participate, and wrappers\n * containing several comparable sub-blocks (a `describe(...)` call, a class body) are descended\n * through. Blocks already covered by an exactly-reported region still take part as ANCHORS — two\n * identical copies plus one edited copy is the commonest copy-paste-then-edit shape, and without\n * anchors the exact pair would swallow both comparison partners of the edited copy — but they are\n * never re-reported: an edited copy verified against an anchor is appended to the anchor's\n * fully-clustered exact group, so no reported region ever overlaps another. Reported occurrences\n * cover their whole block: unlike exact matches, a near-miss occurrence includes its edited\n * tokens, which is how NiCad-style detectors report Type-3 fragments.\n */\nfunction collectNearMissGroups(\n tokens: Token[],\n literalCountPrefix: Int32Array,\n blockRanges: TokenRange[],\n minTokens: number,\n minSimilarityPercent: number,\n reportedGroups: CountedOccurrence[][]\n): CountedOccurrence[][] {\n if (minSimilarityPercent >= 100) {\n return [];\n }\n const eligible = blockRanges\n .filter((range) => {\n const tokenCount = range.endTokenIndex - range.startTokenIndex;\n const literalCount =\n (literalCountPrefix[range.endTokenIndex] ?? 0) - (literalCountPrefix[range.startTokenIndex] ?? 0);\n return tokenCount >= minTokens && !isLiteralDense(literalCount, tokenCount);\n })\n .toSorted(\n (left, right) => left.startTokenIndex - right.startTokenIndex || right.endTokenIndex - left.endTokenIndex\n );\n const comparable = selectComparableBlocks(eligible);\n if (comparable.length < 2) {\n return [];\n }\n\n // Reported-group indices whose occurrences overlap each comparable block: such blocks anchor\n // near-miss comparisons but are never re-reported.\n const touchedGroupsByBlock = comparable.map((range) => {\n const touched: number[] = [];\n for (const [groupIndex, group] of reportedGroups.entries()) {\n if (\n group.some(\n (occurrence) =>\n occurrence.startTokenIndex < range.endTokenIndex && range.startTokenIndex < occurrence.endTokenIndex\n )\n ) {\n touched.push(groupIndex);\n }\n }\n return touched;\n });\n\n // Interned per call so a file's symbol ids (and thus its n-gram hashes) never depend on which\n // other files the process measured before it.\n const symbolIdByTokenHashes = new Map<string, number>();\n const sequences = comparable.map((range) => normalizeBlockSequence(tokens, range, symbolIdByTokenHashes));\n const ngramSets = sequences.map(({ sequence }) => collectNgramSet(sequence));\n const sharedNgramCounts = countSharedNgrams(ngramSets);\n\n const parent = comparable.map((_, index) => index);\n const find = (index: number): number => {\n let root = index;\n while (parent[root] !== root) {\n root = parent[root] ?? root;\n }\n while (parent[index] !== root) {\n const next = parent[index] ?? root;\n parent[index] = root;\n index = next;\n }\n return root;\n };\n for (const [pairKey, shared] of sharedNgramCounts) {\n // Decoded with the same modulus countSharedNgrams encodes with.\n const leftIndex = Math.floor(pairKey / ngramSets.length);\n const rightIndex = pairKey % ngramSets.length;\n const left = sequences[leftIndex];\n const right = sequences[rightIndex];\n const leftNgrams = ngramSets[leftIndex];\n const rightNgrams = ngramSets[rightIndex];\n if (!left || !right || !leftNgrams || !rightNgrams) {\n continue;\n }\n // Two already-reported blocks have nothing new to contribute to each other.\n if ((touchedGroupsByBlock[leftIndex]?.length ?? 0) > 0 && (touchedGroupsByBlock[rightIndex]?.length ?? 0) > 0) {\n continue;\n }\n if (shared * 100 < nearMissFiltrationPercent * Math.min(leftNgrams.size, rightNgrams.size)) {\n continue;\n }\n // A structural match must be backed by shared content (names and literal values), or two\n // same-skeleton blocks calling entirely different APIs would pair on punctuation alone.\n if (\n contentOverlap(left, right) * 100 <=\n minContentSimilarityPercent * Math.max(left.contentTotal, right.contentTotal)\n ) {\n continue;\n }\n // Per-fragment similarity against the larger block (NiCad semantics): both blocks must be\n // mostly covered by the common subsequence, which is stricter than NIL's min-denominator and\n // keeps a generic small block from \"matching\" inside every big one.\n if (\n lcsLength(left.sequence, right.sequence) * 100 >=\n minSimilarityPercent * Math.max(left.sequence.length, right.sequence.length)\n ) {\n const leftRoot = find(leftIndex);\n const rightRoot = find(rightIndex);\n parent[Math.max(leftRoot, rightRoot)] = Math.min(leftRoot, rightRoot);\n }\n }\n\n const membersByRoot = new Map<number, number[]>();\n for (const index of comparable.keys()) {\n const root = find(index);\n const members = membersByRoot.get(root) ?? [];\n members.push(index);\n membersByRoot.set(root, members);\n }\n const groups: CountedOccurrence[][] = [];\n for (const members of membersByRoot.values()) {\n if (members.length < 2) {\n continue;\n }\n const uncovered = members.filter((index) => (touchedGroupsByBlock[index]?.length ?? 0) === 0);\n const covered = members.filter((index) => (touchedGroupsByBlock[index]?.length ?? 0) > 0);\n if (covered.length === 0) {\n groups.push(members.flatMap((index) => (comparable[index] ? [toNearMissOccurrence(comparable[index])] : [])));\n continue;\n }\n if (uncovered.length === 0) {\n continue;\n }\n // An anchored cluster extends a reported group only when that group lies entirely inside the\n // cluster (every occurrence overlaps a member); appending the edited copies there keeps one\n // group per clone family and keeps reported regions disjoint. A group that also has\n // occurrences elsewhere is left untouched, and the uncovered copies stand alone if they can.\n const overlapsMember = (occurrence: CountedOccurrence): boolean =>\n members.some((index) => {\n const range = comparable[index];\n return (\n range !== undefined &&\n occurrence.startTokenIndex < range.endTokenIndex &&\n range.startTokenIndex < occurrence.endTokenIndex\n );\n });\n const fullyClustered = [...new Set(covered.flatMap((index) => touchedGroupsByBlock[index] ?? []))]\n .filter((groupIndex) => {\n const group = reportedGroups[groupIndex];\n return group !== undefined && group.length > 0 && group.every(overlapsMember);\n })\n .toSorted((leftIndex, rightIndex) => leftIndex - rightIndex);\n const [targetIndex, ...sourceIndexes] = fullyClustered;\n if (targetIndex !== undefined) {\n // Every fully-clustered group belongs to this verified component: rebuild them as ONE group\n // with one occurrence per member block. Occurrences landing in the same member (a copy's\n // exact prefix and suffix fragments, split by an over-large edited middle) coalesce into a\n // single multi-segment occurrence — they are one copy, not two — and each uncovered member\n // contributes its whole-block occurrence. Merged-away entries are left empty for the caller\n // to drop.\n const consumed = new Set<CountedOccurrence>();\n const merged: CountedOccurrence[] = [];\n for (const memberIndex of members) {\n const range = comparable[memberIndex];\n if (!range) {\n continue;\n }\n // Occurrences of ONE group are distinct copies (a repeated run inside the block); only\n // fragments from DIFFERENT groups (a copy's exact prefix in one group and its suffix in\n // another) belong to the same copy. Walking the member's fragments in position order and\n // starting a new copy whenever a source group repeats keeps same-group copies separate,\n // and — because the fragments are disjoint and each copy takes a consecutive slice —\n // guarantees the coalesced spans never overlap, even when groups contribute unequal\n // fragment counts to this member.\n const fragments: { occurrence: CountedOccurrence; groupIndex: number }[] = [];\n for (const groupIndex of fullyClustered) {\n for (const occurrence of reportedGroups[groupIndex] ?? []) {\n if (\n !consumed.has(occurrence) &&\n occurrence.startTokenIndex < range.endTokenIndex &&\n range.startTokenIndex < occurrence.endTokenIndex\n ) {\n consumed.add(occurrence);\n fragments.push({ occurrence, groupIndex });\n }\n }\n }\n fragments.sort(\n (left, right) =>\n left.occurrence.startTokenIndex - right.occurrence.startTokenIndex ||\n left.occurrence.endTokenIndex - right.occurrence.endTokenIndex\n );\n let copyParts: CountedOccurrence[] = [];\n const copyGroups = new Set<number>();\n for (const { occurrence, groupIndex } of fragments) {\n if (copyGroups.has(groupIndex)) {\n merged.push(coalesceOccurrences(copyParts));\n copyParts = [];\n copyGroups.clear();\n }\n copyParts.push(occurrence);\n copyGroups.add(groupIndex);\n }\n if (copyParts.length > 0) {\n merged.push(coalesceOccurrences(copyParts));\n }\n if (fragments.length === 0 && (touchedGroupsByBlock[memberIndex]?.length ?? 0) === 0) {\n merged.push(toNearMissOccurrence(range));\n }\n }\n merged.sort(\n (left, right) => left.startTokenIndex - right.startTokenIndex || left.endTokenIndex - right.endTokenIndex\n );\n // The rebuild consumed every fully-clustered group, so shared-span marks from earlier\n // partial merges no longer point at a separate merged group; the rebuilt group counts alone.\n for (const occurrence of merged) {\n occurrence.sharedWithMergedGroup = undefined;\n }\n reportedGroups[targetIndex] = merged;\n for (const sourceIndex of sourceIndexes) {\n reportedGroups[sourceIndex] = [];\n }\n } else if (uncovered.length >= 2) {\n groups.push(uncovered.flatMap((index) => (comparable[index] ? [toNearMissOccurrence(comparable[index])] : [])));\n }\n }\n groups.sort(compareGroups);\n return groups;\n}\n\n/**\n * Keeps the block ranges the near-miss phase compares. Block ranges nest or are disjoint, so they\n * form a forest; a candidate whose subtree branches into two or more disjoint eligible sub-blocks\n * is a WRAPPER (a `describe(...)` statement, an IIFE, a class body) and is descended through —\n * otherwise a file whose code sits inside one enclosing construct would yield a single candidate\n * and silently disable near-miss detection. A linear chain keeps its top (the most context), and\n * the kept set is an antichain, so reported near-miss occurrences stay disjoint by construction.\n */\nfunction selectComparableBlocks(eligible: TokenRange[]): TokenRange[] {\n const roots: ForestNode[] = [];\n const stack: ForestNode[] = [];\n for (const range of eligible) {\n while (stack.length > 0 && (stack.at(-1) as ForestNode).range.endTokenIndex <= range.startTokenIndex) {\n stack.pop();\n }\n const top = stack.at(-1);\n // Equal spans (two node types covering the same tokens) collapse into the first.\n if (top && top.range.startTokenIndex === range.startTokenIndex && top.range.endTokenIndex === range.endTokenIndex) {\n continue;\n }\n const node: ForestNode = { range, children: [] };\n if (top) {\n top.children.push(node);\n } else {\n roots.push(node);\n }\n stack.push(node);\n }\n\n const kept: TokenRange[] = [];\n const visit = (node: ForestNode): void => {\n if (branches(node)) {\n for (const child of node.children) {\n visit(child);\n }\n } else {\n kept.push(node.range);\n }\n };\n for (const root of roots) {\n visit(root);\n }\n return kept;\n}\n\ninterface ForestNode {\n range: TokenRange;\n children: ForestNode[];\n}\n\n/** Whether the node's subtree splits into two or more disjoint eligible sub-blocks. */\nfunction branches(node: ForestNode): boolean {\n const [firstChild] = node.children;\n return node.children.length >= 2 || (firstChild !== undefined && branches(firstChild));\n}\n\n/** One copy's fragments (an exact prefix and suffix split by a large edit) as one occurrence. */\nfunction coalesceOccurrences(occurrences: CountedOccurrence[]): CountedOccurrence {\n const first = occurrences[0];\n if (!first || occurrences.length === 1) {\n return first ?? toEmptyOccurrence();\n }\n // A partial gapped merge retains the leftover group with ALL its occurrences, so a copy's\n // fragments can arrive both standalone and embedded in a merged occurrence: union overlapping\n // segments so no token span is reported or counted twice.\n const segments: TokenSegment[] = [];\n for (const segment of occurrences\n .flatMap((occurrence) => occurrence.segments)\n .toSorted(\n (left, right) => left.startTokenIndex - right.startTokenIndex || left.endTokenIndex - right.endTokenIndex\n )) {\n const last = segments.at(-1);\n if (last && segment.startTokenIndex < last.endTokenIndex) {\n last.endTokenIndex = Math.max(last.endTokenIndex, segment.endTokenIndex);\n } else {\n segments.push({ ...segment });\n }\n }\n return {\n segments,\n tokenCount: segments.reduce((sum, segment) => sum + segment.endTokenIndex - segment.startTokenIndex, 0),\n startTokenIndex: Math.min(...occurrences.map((occurrence) => occurrence.startTokenIndex)),\n endTokenIndex: Math.max(...occurrences.map((occurrence) => occurrence.endTokenIndex)),\n startIndex: Math.min(...occurrences.map((occurrence) => occurrence.startIndex)),\n endIndex: Math.max(...occurrences.map((occurrence) => occurrence.endIndex)),\n startLine: Math.min(...occurrences.map((occurrence) => occurrence.startLine)),\n endLine: Math.max(...occurrences.map((occurrence) => occurrence.endLine)),\n };\n}\n\n/** Unreachable fallback keeping coalesceOccurrences total without a non-null assertion. */\nfunction toEmptyOccurrence(): CountedOccurrence {\n return {\n segments: [],\n tokenCount: 0,\n startTokenIndex: 0,\n endTokenIndex: 0,\n startIndex: 0,\n endIndex: 0,\n startLine: 0,\n endLine: 0,\n };\n}\n\n/** A near-miss occurrence spans its whole block as one segment, edited tokens included. */\nfunction toNearMissOccurrence(range: TokenRange): CountedOccurrence {\n return {\n segments: [{ startTokenIndex: range.startTokenIndex, endTokenIndex: range.endTokenIndex }],\n tokenCount: range.endTokenIndex - range.startTokenIndex,\n startTokenIndex: range.startTokenIndex,\n endTokenIndex: range.endTokenIndex,\n startIndex: range.startIndex,\n endIndex: range.endIndex,\n startLine: range.startLine,\n endLine: range.endLine,\n };\n}\n\ninterface NormalizedBlock {\n sequence: Int32Array;\n /** Occurrences per content-bearing symbol (names and literal values), for the content gate. */\n contentCountBySymbol: Map<number, number>;\n contentTotal: number;\n}\n\n/**\n * A block's tokens as comparable integers: anonymized identifiers become negative first-occurrence\n * indexes (per block, mirroring the exact fingerprint's rename tolerance) and every other token\n * becomes a non-negative id interned over BOTH 32-bit text hashes — with literal VALUES folded in,\n * unlike the exact fingerprint's kind tags — so one hash collision cannot equate two different\n * tokens and a fuzzy 70% match cannot mistake same-shape tables or dispatch predicates with\n * entirely different values for copies.\n */\nfunction normalizeBlockSequence(\n tokens: Token[],\n range: TokenRange,\n symbolIdByTokenHashes: Map<string, number>\n): NormalizedBlock {\n const sequence = new Int32Array(range.endTokenIndex - range.startTokenIndex);\n const indexByIdentifier = new Map<string, number>();\n const contentCountBySymbol = new Map<number, number>();\n let contentTotal = 0;\n for (let index = range.startTokenIndex; index < range.endTokenIndex; index += 1) {\n const token = tokens[index];\n if (!token) {\n continue;\n }\n let value: number;\n if (token.kind === 'id') {\n let identifierIndex = indexByIdentifier.get(token.text);\n if (identifierIndex === undefined) {\n identifierIndex = indexByIdentifier.size;\n indexByIdentifier.set(token.text, identifierIndex);\n }\n value = -(identifierIndex + 1);\n } else {\n const key = `${token.textHash}:${token.textHash2}:${token.literalHash ?? 0}:${token.literalHash2 ?? 0}`;\n let id = symbolIdByTokenHashes.get(key);\n if (id === undefined) {\n id = symbolIdByTokenHashes.size;\n symbolIdByTokenHashes.set(key, id);\n }\n value = id;\n if (token.isName === true || token.literalHash !== undefined) {\n contentCountBySymbol.set(value, (contentCountBySymbol.get(value) ?? 0) + 1);\n contentTotal += 1;\n }\n }\n sequence[index - range.startTokenIndex] = value;\n }\n return { sequence, contentCountBySymbol, contentTotal };\n}\n\n/** Multiset overlap of two blocks' content-bearing symbols, for the content gate. */\nfunction contentOverlap(left: NormalizedBlock, right: NormalizedBlock): number {\n const [smaller, larger] =\n left.contentCountBySymbol.size <= right.contentCountBySymbol.size ? [left, right] : [right, left];\n let overlap = 0;\n for (const [symbol, count] of smaller.contentCountBySymbol) {\n overlap += Math.min(count, larger.contentCountBySymbol.get(symbol) ?? 0);\n }\n return overlap;\n}\n\n/** The distinct 5-gram hashes of a normalized block sequence, for the filtration set overlap. */\nfunction collectNgramSet(sequence: Int32Array): Set<number> {\n const ngrams = new Set<number>();\n for (let start = 0; start + nearMissNgramSize <= sequence.length; start += 1) {\n let hash = 5381;\n for (let offset = 0; offset < nearMissNgramSize; offset += 1) {\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- `| 0` wraps the sum to int32 to match the native backend's wrapping i32 arithmetic.\n hash = (Math.imul(hash, 31) + (sequence[start + offset] ?? 0)) | 0;\n }\n ngrams.add(hash);\n }\n return ngrams;\n}\n\n/** Shared distinct-n-gram counts per block pair, keyed `leftIndex * blockCount + rightIndex`. */\nfunction countSharedNgrams(ngramSets: Set<number>[]): Map<number, number> {\n const blocksByNgram = new Map<number, number[]>();\n for (const [blockIndex, ngrams] of ngramSets.entries()) {\n // Get-or-create: this loop runs once per distinct n-gram of every block, so the redundant\n // `set` on already-present keys is worth avoiding here.\n for (const ngram of ngrams) {\n let blocks = blocksByNgram.get(ngram);\n if (!blocks) {\n blocks = [];\n blocksByNgram.set(ngram, blocks);\n }\n blocks.push(blockIndex);\n }\n }\n const sharedCounts = new Map<number, number>();\n for (const blocks of blocksByNgram.values()) {\n // Index-based loops: slicing here would allocate per pair in what can be a hot loop.\n for (let leftPosition = 0; leftPosition < blocks.length; leftPosition += 1) {\n const leftIndex = blocks[leftPosition] ?? 0;\n for (let rightPosition = leftPosition + 1; rightPosition < blocks.length; rightPosition += 1) {\n const pairKey = leftIndex * ngramSets.length + (blocks[rightPosition] ?? 0);\n sharedCounts.set(pairKey, (sharedCounts.get(pairKey) ?? 0) + 1);\n }\n }\n }\n return sharedCounts;\n}\n\n/**\n * Longest-common-subsequence LENGTH of two symbol sequences via the Allison–Dix bit-parallel\n * recurrence (O(|a|/32 · |b|) words): per symbol of `b`, `x = match | v` and\n * `v = x & ~(x - ((v << 1) | 1))` over multi-word bit vectors; the set bits of `v` count the LCS.\n * Only the length is needed (similarity is a ratio), and LCS length is algorithm-independent, so\n * the native backend may use a different word size and still agree bit-for-bit.\n */\nexport function lcsLength(a: Int32Array, b: Int32Array): number {\n const wordCount = (a.length + 31) >>> 5;\n const positionMasks = new Map<number, Uint32Array>();\n for (const [index, symbol] of a.entries()) {\n let mask = positionMasks.get(symbol);\n if (!mask) {\n mask = new Uint32Array(wordCount);\n positionMasks.set(symbol, mask);\n }\n const word = index >>> 5;\n // The Uint32Array store wraps the signed int32 bit pattern to unsigned. The `?? 0` guards in\n // this function are required by noUncheckedIndexedAccess (typed-array reads type as\n // `number | undefined`), not redundancy: every index is in bounds.\n mask[word] = (mask[word] ?? 0) | (1 << (index & 31));\n }\n\n const v = new Uint32Array(wordCount);\n for (const symbol of b) {\n const matchMask = positionMasks.get(symbol);\n // `(v << 1) | 1` shifts a carry bit across words; subtraction borrows across words.\n let shiftCarry = 1;\n let borrow = 0;\n for (let word = 0; word < wordCount; word += 1) {\n const previous = v[word] ?? 0;\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- `>>> 0` reinterprets the signed int32 bit pattern as unsigned so the borrow subtraction below compares magnitudes; Math.trunc would keep it negative.\n const x = ((matchMask?.[word] ?? 0) | previous) >>> 0;\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- same unsigned reinterpretation as `x`.\n const shifted = ((previous << 1) | shiftCarry) >>> 0;\n shiftCarry = previous >>> 31;\n const difference = x - shifted - borrow;\n borrow = difference < 0 ? 1 : 0;\n // The Uint32Array store wraps the signed int32 bit pattern to unsigned.\n v[word] = x & ~difference;\n }\n }\n\n let length = 0;\n for (const word of v) {\n length += popCount(word);\n }\n return length;\n}\n\nfunction popCount(value: number): number {\n let count = value - ((value >>> 1) & 0x55_55_55_55);\n count = (count & 0x33_33_33_33) + ((count >>> 2) & 0x33_33_33_33);\n return (Math.imul((count + (count >>> 4)) & 0x0F_0F_0F_0F, 0x01_01_01_01) >>> 24) & 0xFF;\n}\n\nfunction toCandidate(\n fingerprint: string,\n startTokenIndex: number,\n endTokenIndex: number,\n first: TokenRange,\n last: TokenRange\n): DuplicateCandidate {\n return {\n fingerprint,\n tokenCount: endTokenIndex - startTokenIndex,\n startTokenIndex,\n endTokenIndex,\n startIndex: first.startIndex,\n endIndex: last.endIndex,\n startLine: first.startLine,\n endLine: last.endLine,\n };\n}\n\n/** Caches of hashText/hashText2 over '$0', '$1', ... so anonymized identifiers hash without allocating. */\nconst anonymizedIndexHashes: number[] = [];\nconst anonymizedIndexHashes2: number[] = [];\n\nfunction anonymizedIndexHash(index: number): number {\n let hash = anonymizedIndexHashes[index];\n if (hash === undefined) {\n hash = hashText(`$${index}`);\n anonymizedIndexHashes[index] = hash;\n }\n return hash;\n}\n\nfunction anonymizedIndexHash2(index: number): number {\n let hash = anonymizedIndexHashes2[index];\n if (hash === undefined) {\n hash = hashText2(`$${index}`);\n anonymizedIndexHashes2[index] = hash;\n }\n return hash;\n}\n\n/**\n * Content key of a token range: two independent 32-bit hashes over the normalized token sequence\n * (identifiers anonymized consistently by first-occurrence order) plus the token count. Regions\n * with equal keys are treated as equal content; a collision would need both 32-bit hashes and the\n * length to coincide, which is negligible for a metrics report. Hashing per-token instead of\n * serializing the whole range to a string keeps fingerprinting allocation-free for nested regions.\n */\nfunction fingerprintKey(\n tokens: Token[],\n literalCountPrefix: Int32Array,\n startTokenIndex: number,\n endTokenIndex: number\n): string {\n const literalCount = (literalCountPrefix[endTokenIndex] ?? 0) - (literalCountPrefix[startTokenIndex] ?? 0);\n const literalDense = isLiteralDense(literalCount, endTokenIndex - startTokenIndex);\n const [primary, secondary] = fingerprintHashPair(tokens, startTokenIndex, endTokenIndex, literalDense);\n return `${primary}:${secondary}:${endTokenIndex - startTokenIndex}`;\n}\n\n/**\n * A single 32-bit summary of a range for the coarse rolling-hash phase. Deliberately\n * density-agnostic: density is a property of the final candidate REGION, and folding literal\n * values into per-statement hashes would make a dense statement inside a logic-heavy window\n * (`const weights = [1, 2, 3];`) block the window from ever being enumerated. The coarse phase\n * over-approximates on shape alone; the exact region fingerprint still applies the density rule.\n */\nfunction fingerprintHash(tokens: Token[], startTokenIndex: number, endTokenIndex: number): number {\n const [primary, secondary] = fingerprintHashPair(tokens, startTokenIndex, endTokenIndex, false);\n // XOR already coerces to int32, matching the native backend's i32 arithmetic.\n return primary ^ Math.imul(secondary, 31);\n}\n\nfunction fingerprintHashPair(\n tokens: Token[],\n startTokenIndex: number,\n endTokenIndex: number,\n foldLiteralValues: boolean\n): [number, number] {\n const indexByIdentifier = new Map<string, number>();\n let primary = 5381;\n let secondary = 52_711;\n for (let index = startTokenIndex; index < endTokenIndex; index += 1) {\n const token = tokens[index];\n if (!token) {\n continue;\n }\n // Each accumulator consumes its own independent per-token hash: sharing one would collapse\n // the key to 32 effective bits (a single djb2 collision would equate whole regions).\n let part: number;\n let part2: number;\n if (token.kind === 'id') {\n let identifierIndex = indexByIdentifier.get(token.text);\n if (identifierIndex === undefined) {\n identifierIndex = indexByIdentifier.size;\n indexByIdentifier.set(token.text, identifierIndex);\n }\n part = anonymizedIndexHash(identifierIndex);\n part2 = anonymizedIndexHash2(identifierIndex);\n } else {\n part = token.textHash;\n part2 = token.textHash2;\n }\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- `| 0` wraps the sum to int32 (Math.trunc does not), which must match the native backend's wrapping i32 arithmetic.\n primary = (Math.imul(primary, 31) + part) | 0;\n secondary = Math.imul(secondary, 37) ^ part2;\n if (foldLiteralValues && token.literalHash !== undefined && token.literalHash2 !== undefined) {\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- `| 0` wraps the sum to int32 (Math.trunc does not), which must match the native backend's wrapping i32 arithmetic.\n primary = (Math.imul(primary, 31) + token.literalHash) | 0;\n secondary = Math.imul(secondary, 37) ^ token.literalHash2;\n }\n }\n return [primary, secondary];\n}\n\n/** djb2-style hash; XOR keeps the value in signed 32-bit range, which is fine for a grouping key. */\nexport function hashText(text: string): number {\n let hash = 5381;\n for (let index = 0; index < text.length; index += 1) {\n // oxlint-disable-next-line unicorn/prefer-code-point -- djb2 hashes UTF-16 code units; codePointAt would hash surrogate pairs twice (full code point, then the lone low surrogate).\n hash = Math.imul(hash, 33) ^ text.charCodeAt(index);\n }\n return hash;\n}\n\n/** FNV-1a over UTF-16 code units: independent of hashText so the two accumulators never share input. */\nfunction hashText2(text: string): number {\n let hash = -2_128_831_035; // 2166136261 as int32 (the FNV-1a offset basis)\n for (let index = 0; index < text.length; index += 1) {\n // oxlint-disable-next-line unicorn/prefer-code-point -- hashes UTF-16 code units like hashText.\n hash = Math.imul(hash ^ text.charCodeAt(index), 16_777_619);\n }\n return hash;\n}\n\nfunction combineHashes(hash: number, value: number): number {\n return Math.imul(hash, 31) + value;\n}\n\nfunction toCountedGroups(counted: Map<string, DuplicateCandidate[]>): CountedOccurrence[][] {\n const groups: CountedOccurrence[][] = [];\n for (const group of counted.values()) {\n const occurrences = group.map((candidate) => ({\n segments: [{ startTokenIndex: candidate.startTokenIndex, endTokenIndex: candidate.endTokenIndex }],\n tokenCount: candidate.tokenCount,\n startTokenIndex: candidate.startTokenIndex,\n endTokenIndex: candidate.endTokenIndex,\n startIndex: candidate.startIndex,\n endIndex: candidate.endIndex,\n startLine: candidate.startLine,\n endLine: candidate.endLine,\n }));\n occurrences.sort(\n (left, right) => left.startTokenIndex - right.startTokenIndex || left.endTokenIndex - right.endTokenIndex\n );\n groups.push(occurrences);\n }\n return groups;\n}\n\n/**\n * Merges duplicate groups separated by a small token gap into one gapped (Type-3) clone group: a\n * copy edited in one spot splits into two exact groups whose occurrences sit side by side in the\n * same order. Occurrences are paired greedily in source order; a merge happens when the pairing\n * fully consumes at least one group with at least two pairs. Equal-cardinality groups whose\n * occurrences all pair merge into one group as before. When cardinalities differ (a fragment also\n * occurs standalone: prefix ×3, suffix ×2), the fully-paired group is subsumed into the merged\n * gapped group while the other group is RETAINED with ALL its occurrences: dropping the leftover\n * would lose duplicated-line coverage, and reporting it alone would make a single-occurrence group\n * (contradicting duplicateBlockGroupCount's \"appears more than once\" meaning). Line coverage\n * unions ranges, so the overlap between the retained exact group and the merged group is harmless.\n * Merging repeats to a fixpoint so a clone edited in several spots still reassembles; it\n * terminates because a full merge shrinks the group count and a partial merge keeps it while\n * strictly growing the bounded total span of merged occurrences. Gap tokens are not matched\n * content: line coverage and sizes count only the matched segments. Generic so cross-file merging\n * can thread file identity through occurrences.\n */\nexport function mergeAdjacentGroups<T extends CountedOccurrence>(groups: T[][], maxGapTokens: number): T[][] {\n if (maxGapTokens <= 0 || groups.length < 2) {\n return groups;\n }\n // Deterministic processing order (mirrored by the native backend): by first occurrence position.\n groups.sort(compareGroups);\n for (let restart = true; restart;) {\n restart = false;\n for (let leftIndex = 0; leftIndex < groups.length && !restart; leftIndex += 1) {\n for (let rightIndex = leftIndex + 1; rightIndex < groups.length; rightIndex += 1) {\n const left = groups[leftIndex];\n const right = groups[rightIndex];\n if (!left || !right) {\n continue;\n }\n const forward = mergeGroups(left, right, maxGapTokens);\n const result = forward ?? mergeGroups(right, left, maxGapTokens);\n if (!result) {\n continue;\n }\n const leftConsumed = forward ? result.firstConsumed : result.secondConsumed;\n const rightConsumed = forward ? result.secondConsumed : result.firstConsumed;\n if (leftConsumed && rightConsumed) {\n groups[leftIndex] = result.merged;\n groups.splice(rightIndex, 1);\n } else if (rightConsumed) {\n groups[rightIndex] = result.merged;\n } else {\n groups[leftIndex] = result.merged;\n }\n // A partial merge retains the not-fully-consumed group with ALL its occurrences (line\n // coverage must not shrink, and a reported group must keep >= 2 occurrences), so its\n // paired occurrences now also live inside the merged group's occurrences: mark them so\n // duplicateBlockCount counts each token span once.\n for (const occurrence of result.pairedRetained) {\n occurrence.sharedWithMergedGroup = true;\n }\n groups.sort(compareGroups);\n restart = true;\n break;\n }\n }\n }\n return groups;\n}\n\nfunction compareGroups(left: CountedOccurrence[], right: CountedOccurrence[]): number {\n const leftFirst = left[0];\n const rightFirst = right[0];\n return (\n (leftFirst?.startTokenIndex ?? 0) - (rightFirst?.startTokenIndex ?? 0) ||\n (leftFirst?.endTokenIndex ?? 0) - (rightFirst?.endTokenIndex ?? 0)\n );\n}\n\ninterface MergeResult<T> {\n merged: T[];\n /** Whether every occurrence of the respective input group was paired into the merge. */\n firstConsumed: boolean;\n secondConsumed: boolean;\n /** The retained (not fully consumed) group's occurrences that were paired into the merge. */\n pairedRetained: T[];\n}\n\n/**\n * Pairs `second` occurrences with gap-preceding `first` occurrences, greedily in source order:\n * each trailing occurrence takes the earliest unused leading occurrence within the gap, and a\n * pair's leading must start at or after the previous pair's trailing end so merged spans never\n * overlap. A merge needs at least two pairs (a merged group must still mean \"appears more than\n * once\") and must fully consume at least one group; for equal cardinalities this reduces to the\n * strict all-pairs merge, so pre-partial-merge behavior is unchanged there.\n */\nfunction mergeGroups<T extends CountedOccurrence>(\n first: T[],\n second: T[],\n maxGapTokens: number\n): MergeResult<T> | undefined {\n // Occurrences a previous partial merge already paired into a merged group must not pair again:\n // their spans already live inside that merged group, so re-pairing them would assemble a second,\n // competing merged group instead of letting the existing merged group extend (and would count\n // the same span twice). Consumption is still judged against the FULL group, so a group holding\n // shared occurrences is never subsumed away.\n const leadings = first.filter((occurrence) => !occurrence.sharedWithMergedGroup);\n const trailings = second.filter((occurrence) => !occurrence.sharedWithMergedGroup);\n const pairs: [T, T][] = [];\n let leadingIndex = 0;\n let previousTrailingEnd = -1;\n for (const trailing of trailings) {\n // Leadings ending too far before this trailing can never pair a later (even farther) one.\n while (leadingIndex < leadings.length) {\n const leading = leadings[leadingIndex];\n if (leading && leading.endTokenIndex + maxGapTokens < trailing.startTokenIndex) {\n leadingIndex += 1;\n } else {\n break;\n }\n }\n const leading = leadings[leadingIndex];\n if (\n leading &&\n leading.endTokenIndex <= trailing.startTokenIndex &&\n leading.startTokenIndex >= previousTrailingEnd\n ) {\n pairs.push([leading, trailing]);\n previousTrailingEnd = trailing.endTokenIndex;\n leadingIndex += 1;\n }\n }\n const firstConsumed = pairs.length === first.length;\n const secondConsumed = pairs.length === second.length;\n if (pairs.length < 2 || (!firstConsumed && !secondConsumed)) {\n return undefined;\n }\n const merged = pairs.map(([leading, trailing]) => ({\n ...leading,\n // A merged occurrence is a fresh span combination; it never inherits shared-span marks.\n sharedWithMergedGroup: undefined,\n segments: [...leading.segments, ...trailing.segments],\n tokenCount: leading.tokenCount + trailing.tokenCount,\n endTokenIndex: trailing.endTokenIndex,\n endIndex: trailing.endIndex,\n endLine: trailing.endLine,\n }));\n const pairedRetained =\n firstConsumed === secondConsumed ? [] : pairs.map(([leading, trailing]) => (firstConsumed ? trailing : leading));\n return { merged, firstConsumed, secondConsumed, pairedRetained };\n}\n\n/**\n * Redundant copies one group adds to duplicateBlockCount. Each redundant occurrence contributes\n * one count per matched fragment, so merging a gapped clone's fragments into one group does not\n * halve duplicateBlockCount: an edited two-fragment pair still counts 2, exactly as its unmerged\n * fragments did. Occurrence shapes can differ within one group (a\n * gap-merged exact pair plus an appended whole-block near-miss copy), so every occurrence's\n * fragments are summed and one representative — the largest — is deducted, keeping the count\n * independent of source order. Occurrences a partial gapped merge also paired into a merged group\n * are skipped: their spans are already counted there, and such a retained group deducts no\n * representative of its own — the merged group's representative already stands for the shared\n * content — so no token span contributes to the count twice.\n */\nexport function countRedundantFragments(group: CountedOccurrence[]): number {\n let fragmentCount = 0;\n let maxFragmentCount = 0;\n let hasSharedOccurrence = false;\n for (const occurrence of group) {\n if (occurrence.sharedWithMergedGroup) {\n hasSharedOccurrence = true;\n continue;\n }\n fragmentCount += occurrence.segments.length;\n maxFragmentCount = Math.max(maxFragmentCount, occurrence.segments.length);\n }\n return hasSharedOccurrence ? fragmentCount : fragmentCount - maxFragmentCount;\n}\n\nfunction summarizeDuplicates(\n groups: CountedOccurrence[][],\n codeLineNumbers: Set<number>,\n tokens: Token[]\n): DuplicationMetrics {\n let duplicateBlockCount = 0;\n let maxDuplicateBlockSize = 0;\n const duplicateBlockGroups: { startLine: number; endLine: number }[][] = [];\n const duplicatedLines = new Set<number>();\n for (const group of groups) {\n duplicateBlockCount += countRedundantFragments(group);\n for (const occurrence of group) {\n maxDuplicateBlockSize = Math.max(maxDuplicateBlockSize, occurrence.tokenCount);\n // Only CODE lines carrying segment tokens count: comments and blank gaps inside an\n // occurrence's bounding range — the unmatched gap of a merged clone, and blank rows inside a\n // multi-row token (heredocs, template literals) — are not duplicated content and would push\n // the ratio past 1. A near-miss occurrence's single segment deliberately spans its WHOLE\n // block, edited tokens included: an LCS has no canonical per-line attribution, and\n // NiCad-style detectors treat the whole near-miss fragment as the clone.\n for (const segment of occurrence.segments) {\n collectSegmentLines(segment, tokens, codeLineNumbers, duplicatedLines);\n }\n }\n duplicateBlockGroups.push(\n group\n .map(({ startLine, endLine }) => ({ startLine, endLine }))\n .toSorted((left, right) => left.startLine - right.startLine)\n );\n }\n duplicateBlockGroups.sort((left, right) => (left[0]?.startLine ?? 0) - (right[0]?.startLine ?? 0));\n\n return {\n duplicateBlockCount,\n duplicateBlockGroupCount: groups.length,\n duplicateBlockGroups,\n duplicateLineCount: duplicatedLines.size,\n duplicateLineNumbers: [...duplicatedLines].toSorted((left, right) => left - right),\n duplicationRatio: codeLineNumbers.size === 0 ? 0 : duplicatedLines.size / codeLineNumbers.size,\n maxDuplicateBlockSize,\n };\n}\n\n/** Adds the 1-based code lines the segment's matched tokens cover; shared with cross-file coverage. */\nexport function collectSegmentLines(\n segment: { startTokenIndex: number; endTokenIndex: number },\n tokens: Token[],\n codeLineNumbers: Set<number> | undefined,\n duplicatedLines: Set<number>\n): void {\n for (let index = segment.startTokenIndex; index < segment.endTokenIndex; index += 1) {\n const token = tokens[index];\n for (let row = token?.startRow ?? 0; row <= (token?.endRow ?? -1); row += 1) {\n if (!codeLineNumbers || codeLineNumbers.has(row + 1)) {\n duplicatedLines.add(row + 1);\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"duplication.js","names":[],"sources":["../src/duplication.ts"],"sourcesContent":["import type { DuplicationOptions } from './types.js';\n\n/**\n * Project-level duplication machinery operating on normalized token streams. Tokenization itself\n * (parsing, identifier anonymization, literal normalization) happens in the Rust addon, which\n * serializes each file's Token stream and statement structure; the helpers here match statement\n * windows across files, merge gap-adjacent groups, and count duplicated lines over that data.\n */\n\nexport const defaultDuplicationOptions: Required<DuplicationOptions> = {\n minTokens: 40,\n maxGapTokens: 30,\n minSimilarityPercent: 70,\n};\n\n/**\n * Fills defaults for absent settings, applying the same normalization as the native boundary's\n * clampToU32 — NaN (e.g. `Number(unsetEnvVariable)`) counts as absent, and other values truncate\n * and clamp to [0, u32::MAX] — so the TypeScript half of cross-file matching cannot diverge from\n * the natively collected candidates on such input.\n */\nexport function resolveDuplicationOptions(options?: DuplicationOptions): Required<DuplicationOptions> {\n return {\n minTokens: resolveOption(options?.minTokens, defaultDuplicationOptions.minTokens),\n maxGapTokens: resolveOption(options?.maxGapTokens, defaultDuplicationOptions.maxGapTokens),\n minSimilarityPercent: resolveOption(options?.minSimilarityPercent, defaultDuplicationOptions.minSimilarityPercent),\n };\n}\n\nfunction resolveOption(value: number | undefined, fallback: number): number {\n return value === undefined || Number.isNaN(value)\n ? fallback\n : Math.min(Math.max(Math.trunc(value), 0), 0xFF_FF_FF_FF);\n}\n\n/** Minimum consecutive statements for a statement-sequence duplicate candidate. */\nconst minSequenceStatementCount = 2;\n/**\n * Caps the window length so statement-sequence enumeration stays linear in the statement count.\n * Heterogeneous clones longer than the cap are reported as capped windows (a deliberate\n * conservative undercount trading completeness for bounded discovery cost).\n */\nconst maxSequenceStatementCount = 100;\n\n/**\n * A region whose normalized tokens are at least 20% literal values is data-like (a lookup table, a\n * constant list, a value-mapping switch), not logic: literal values re-enter its fingerprint so\n * tables that merely share their shape stop counting as copy-paste. Compared in integer math\n * (5 * literals >= total) so the TypeScript and native sides cannot disagree on the boundary.\n */\nfunction isLiteralDense(literalCount: number, tokenCount: number): boolean {\n return literalCount * 5 >= tokenCount;\n}\n\nexport interface Token {\n /** Normalization target: identifiers to anonymize, literal kind tags, or the raw token text. */\n kind: 'id' | 'text';\n text: string;\n /**\n * Two INDEPENDENT hashes of `text` (djb2 and FNV-1a), precomputed so fingerprinting nested\n * regions never re-hashes a token. Feeding the same per-token hash to both fingerprint\n * accumulators would collapse the key to 32 effective bits: one djb2 collision between two\n * token texts would then equate whole regions.\n */\n textHash: number;\n textHash2: number;\n /** Hash pair of a value-carrying literal's value, folded into data-like region fingerprints. */\n literalHash?: number;\n literalHash2?: number;\n /**\n * True for verbatim-kept NAMES (member/callee/type names, named grammar leaves): together with\n * value-carrying literals these are the content-bearing tokens the near-miss content gate\n * counts. Keywords, operators, and punctuation come from unnamed nodes and stay false.\n */\n isName?: boolean;\n /** 0-based source rows the token occupies, so line coverage counts only matched-token lines. */\n startRow: number;\n endRow: number;\n}\n\n/** A token span with its source position; plain data so cross-file matching can retain it. */\nexport interface TokenRange {\n startTokenIndex: number;\n endTokenIndex: number;\n startIndex: number;\n endIndex: number;\n startLine: number;\n endLine: number;\n}\n\n/** A contiguous run of matched tokens; gapped (merged) duplicates carry several per occurrence. */\ninterface TokenSegment {\n startTokenIndex: number;\n endTokenIndex: number;\n}\n\ninterface DuplicateCandidate {\n fingerprint: string;\n tokenCount: number;\n startTokenIndex: number;\n endTokenIndex: number;\n startIndex: number;\n endIndex: number;\n startLine: number;\n endLine: number;\n}\n\nexport interface CountedOccurrence {\n /** Matched token runs; more than one once gapped groups are merged. */\n segments: TokenSegment[];\n /**\n * Set on a retained group's occurrences that a partial gapped merge also paired into a merged\n * group: their spans are counted there, so block counting must not count them again.\n */\n sharedWithMergedGroup?: boolean;\n /** Sum of segment token counts (the gap tokens are not matched content). */\n tokenCount: number;\n startTokenIndex: number;\n endTokenIndex: number;\n startIndex: number;\n endIndex: number;\n startLine: number;\n endLine: number;\n}\n\n/** A duplicate region found in one file, exported for cross-file matching by fingerprint. */\nexport interface CrossFileDuplicateCandidate {\n /** Content key: equal fingerprints mean equal normalized token sequences (up to hash collision). */\n fingerprint: string;\n tokenCount: number;\n /** Token positions within the owning file, for cross-file gapped (Type-3) merging. */\n startTokenIndex: number;\n endTokenIndex: number;\n startIndex: number;\n endIndex: number;\n startLine: number;\n endLine: number;\n}\n\n/**\n * One file's contribution to cross-file clone detection: catalogued candidates plus the normalized\n * token stream and statement structure, so the project-level pass can match partial statement runs\n * (windows that a single file cannot know repeat elsewhere) and merge gap-adjacent groups.\n */\nexport interface CrossFileDuplicationFileData {\n candidates: CrossFileDuplicateCandidate[];\n tokens: Token[];\n containerStatements: TokenRange[][];\n /**\n * 1-based lines that are neither blank nor comment-only, so cross-file line coverage counts only\n * code lines (blank rows inside multi-row tokens such as template literals carry no content).\n * Optional for backward compatibility; without it, every matched-token row counts.\n */\n codeLineNumbers?: Set<number>;\n}\n\n/** literalCountPrefix[i] = value-carrying literal tokens in tokens[0..i), for O(1) density checks. */\nexport function buildLiteralCountPrefix(tokens: Token[]): Int32Array {\n const prefix = new Int32Array(tokens.length + 1);\n for (const [index, token] of tokens.entries()) {\n prefix[index + 1] = (prefix[index] ?? 0) + (token.literalHash === undefined ? 0 : 1);\n }\n return prefix;\n}\n\ninterface WindowOccurrences {\n count: number;\n /** -1 once occurrences span more than one container. */\n containerIndex: number;\n /** -1 once occurrences span more than one context (file). */\n contextIndex: number;\n minStart: number;\n maxStart: number;\n}\n\ninterface SequenceWindow {\n containerIndex: number;\n start: number;\n length: number;\n}\n\n/** One file's token stream and statement containers, as a window-matching context. */\nexport interface SequenceWindowContext {\n tokens: Token[];\n literalCountPrefix: Int32Array;\n containers: TokenRange[][];\n}\n\nexport interface ContextualSequenceCandidate {\n candidate: DuplicateCandidate;\n contextIndex: number;\n}\n\n/**\n * Enumerates runs of consecutive sibling statements over one or more contexts (files). Every\n * container statement participates; only the window length is capped, so enumeration stays linear\n * in the statement count. Windows are grouped by a cheap rolling hash of per-statement\n * fingerprints, and only locally maximal repeated windows — those whose one-statement extensions\n * stop repeating — become candidates with an exact (window-consistent) fingerprint. Without the\n * maximality filter a degenerate file of near-identical statements would fingerprint every\n * sub-window of every repeated region. With `requireMultipleContexts` a window only counts as\n * repeated when its occurrences span at least two contexts (CPD-style cross-file matching): a\n * repeat confined to one file is that file's own concern, and emitting it here would flood the\n * project-level selection with unusable single-file groups.\n */\nexport function collectSequenceWindowCandidates(\n contexts: SequenceWindowContext[],\n minTokens: number,\n requireMultipleContexts: boolean\n): ContextualSequenceCandidate[] {\n const candidates: ContextualSequenceCandidate[] = [];\n const contextIndexByContainer: number[] = [];\n const containers: TokenRange[][] = [];\n for (const [contextIndex, context] of contexts.entries()) {\n for (const statements of context.containers) {\n contextIndexByContainer.push(contextIndex);\n containers.push(statements);\n }\n }\n const contextAt = (containerIndex: number): SequenceWindowContext | undefined =>\n contexts[contextIndexByContainer[containerIndex] ?? 0];\n const occurrencesByWindowKey = new Map<number, WindowOccurrences>();\n const containerWindows = containers.map((statements, containerIndex) =>\n enumerateContainerWindows(contextAt(containerIndex)?.tokens ?? [], statements, minTokens)\n );\n for (const [containerIndex, windows] of containerWindows.entries()) {\n const contextIndex = contextIndexByContainer[containerIndex] ?? 0;\n for (const [start, row] of windows.windowKeysByStart.entries()) {\n for (const windowKey of row) {\n if (windowKey === undefined) {\n continue;\n }\n const occurrences = occurrencesByWindowKey.get(windowKey);\n if (occurrences) {\n occurrences.count += 1;\n if (occurrences.containerIndex !== containerIndex) {\n occurrences.containerIndex = -1;\n }\n if (occurrences.contextIndex !== contextIndex) {\n occurrences.contextIndex = -1;\n }\n occurrences.minStart = Math.min(occurrences.minStart, start);\n occurrences.maxStart = Math.max(occurrences.maxStart, start);\n } else {\n occurrencesByWindowKey.set(windowKey, {\n count: 1,\n containerIndex,\n contextIndex,\n minStart: start,\n maxStart: start,\n });\n }\n }\n }\n }\n\n // A window only \"repeats\" when two of its occurrences can coexist without overlapping: sliding\n // matches inside a homogeneous run (start spread smaller than the window length) can never both\n // be counted and must neither qualify a window nor dominate its sub-windows. Cross-context\n // matching instead requires occurrences in two contexts, which coexist by construction.\n const repeats = (windowKey: number | undefined, length: number): boolean => {\n if (windowKey === undefined) {\n return false;\n }\n const occurrences = occurrencesByWindowKey.get(windowKey);\n if (occurrences === undefined || occurrences.count < 2) {\n return false;\n }\n if (requireMultipleContexts) {\n return occurrences.contextIndex === -1;\n }\n return occurrences.containerIndex === -1 || occurrences.maxStart - occurrences.minStart >= length;\n };\n\n // A window whose statements all share one normalized shape (sixteen `let x = 0;` declarations,\n // a constant table) is a homogeneous preamble, not a copy-paste: requiring two distinct\n // per-statement shapes keeps such runs out of duplicate groups and the duplication ratio.\n const hasDistinctStatements = (window: SequenceWindow): boolean => {\n const hashes = containerWindows[window.containerIndex]?.statementHashes ?? [];\n const firstHash = hashes[window.start];\n for (let index = window.start + 1; index < window.start + window.length; index += 1) {\n if (hashes[index] !== firstHash) {\n return true;\n }\n }\n return false;\n };\n\n const maximalWindows: SequenceWindow[] = [];\n for (const [containerIndex, windows] of containerWindows.entries()) {\n for (const [start, row] of windows.windowKeysByStart.entries()) {\n for (const [length, windowKey] of row.entries()) {\n if (!repeats(windowKey, length) || !hasDistinctStatements({ containerIndex, start, length })) {\n continue;\n }\n // Dominated windows are skipped: the one-statement extension also repeats, so a larger\n // candidate covering this window exists.\n const extendedRight = windows.windowKeysByStart[start]?.[length + 1];\n const extendedLeft = windows.windowKeysByStart[start - 1]?.[length + 1];\n if (repeats(extendedRight, length + 1) || repeats(extendedLeft, length + 1)) {\n continue;\n }\n maximalWindows.push({ containerIndex, start, length });\n }\n }\n }\n\n // The rolling hash anonymizes identifiers per statement, so a window can look repeated coarsely\n // while its exact (window-consistent) fingerprints differ, and a longer window's match can\n // dominate sub-windows that other copies still need (three copies where only two extend one\n // statement further). Every emitted window therefore exposes its repeating, unvisited\n // sub-windows; `visited` bounds the worklist and lengths strictly decrease, so it terminates.\n const visited = new Set(maximalWindows.map(windowId));\n let frontier = maximalWindows;\n while (frontier.length > 0) {\n const emitted: SequenceWindow[] = [];\n for (const window of frontier) {\n const statements = containers[window.containerIndex];\n const first = statements?.[window.start];\n const last = statements?.[window.start + window.length - 1];\n const context = contextAt(window.containerIndex);\n if (!first || !last || !context) {\n continue;\n }\n const fingerprint = `s:${fingerprintKey(context.tokens, context.literalCountPrefix, first.startTokenIndex, last.endTokenIndex)}`;\n candidates.push({\n candidate: toCandidate(fingerprint, first.startTokenIndex, last.endTokenIndex, first, last),\n contextIndex: contextIndexByContainer[window.containerIndex] ?? 0,\n });\n emitted.push(window);\n }\n frontier = [];\n for (const window of emitted) {\n for (const start of [window.start, window.start + 1]) {\n const subWindow = { containerIndex: window.containerIndex, start, length: window.length - 1 };\n const subWindowKey = containerWindows[window.containerIndex]?.windowKeysByStart[start]?.[subWindow.length];\n if (\n visited.has(windowId(subWindow)) ||\n !repeats(subWindowKey, subWindow.length) ||\n !hasDistinctStatements(subWindow)\n ) {\n continue;\n }\n visited.add(windowId(subWindow));\n frontier.push(subWindow);\n }\n }\n }\n return candidates;\n}\n\nfunction windowId(window: SequenceWindow): string {\n return `${window.containerIndex}:${window.start}:${window.length}`;\n}\n\ninterface ContainerWindows {\n /** windowKeysByStart[start][length] is the rolling-hash key of the window, or undefined if it is below the size thresholds. */\n windowKeysByStart: (number | undefined)[][];\n /** Per-statement fingerprint hashes, for the distinct-shape requirement on windows. */\n statementHashes: number[];\n}\n\nfunction enumerateContainerWindows(tokens: Token[], statements: TokenRange[], minTokens: number): ContainerWindows {\n const statementHashes = statements.map((statement) =>\n fingerprintHash(tokens, statement.startTokenIndex, statement.endTokenIndex)\n );\n const windowKeysByStart: (number | undefined)[][] = [];\n for (let start = 0; start < statements.length; start += 1) {\n const row: (number | undefined)[] = [];\n let hash = 5381;\n let tokenCount = 0;\n const maxEnd = Math.min(statements.length, start + maxSequenceStatementCount);\n for (let end = start; end < maxEnd; end += 1) {\n const statement = statements[end];\n const statementHash = statementHashes[end];\n if (!statement || statementHash === undefined) {\n break;\n }\n hash = combineHashes(hash, statementHash);\n tokenCount += statement.endTokenIndex - statement.startTokenIndex;\n const statementCount = end - start + 1;\n row[statementCount] =\n statementCount >= minSequenceStatementCount && tokenCount >= minTokens\n ? combineHashes(hash, statementCount)\n : undefined;\n }\n windowKeysByStart.push(row);\n }\n return { windowKeysByStart, statementHashes };\n}\n\n/**\n * Longest-common-subsequence LENGTH of two symbol sequences via the Allison–Dix bit-parallel\n * recurrence (O(|a|/32 · |b|) words): per symbol of `b`, `x = match | v` and\n * `v = x & ~(x - ((v << 1) | 1))` over multi-word bit vectors; the set bits of `v` count the LCS.\n */\nexport function lcsLength(a: Int32Array, b: Int32Array): number {\n const wordCount = (a.length + 31) >>> 5;\n const positionMasks = new Map<number, Uint32Array>();\n for (const [index, symbol] of a.entries()) {\n let mask = positionMasks.get(symbol);\n if (!mask) {\n mask = new Uint32Array(wordCount);\n positionMasks.set(symbol, mask);\n }\n const word = index >>> 5;\n // The Uint32Array store wraps the signed int32 bit pattern to unsigned. The `?? 0` guards in\n // this function are required by noUncheckedIndexedAccess (typed-array reads type as\n // `number | undefined`), not redundancy: every index is in bounds.\n mask[word] = (mask[word] ?? 0) | (1 << (index & 31));\n }\n\n const v = new Uint32Array(wordCount);\n for (const symbol of b) {\n const matchMask = positionMasks.get(symbol);\n // `(v << 1) | 1` shifts a carry bit across words; subtraction borrows across words.\n let shiftCarry = 1;\n let borrow = 0;\n for (let word = 0; word < wordCount; word += 1) {\n const previous = v[word] ?? 0;\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- `>>> 0` reinterprets the signed int32 bit pattern as unsigned so the borrow subtraction below compares magnitudes; Math.trunc would keep it negative.\n const x = ((matchMask?.[word] ?? 0) | previous) >>> 0;\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- same unsigned reinterpretation as `x`.\n const shifted = ((previous << 1) | shiftCarry) >>> 0;\n shiftCarry = previous >>> 31;\n const difference = x - shifted - borrow;\n borrow = difference < 0 ? 1 : 0;\n // The Uint32Array store wraps the signed int32 bit pattern to unsigned.\n v[word] = x & ~difference;\n }\n }\n\n let length = 0;\n for (const word of v) {\n length += popCount(word);\n }\n return length;\n}\n\nfunction popCount(value: number): number {\n let count = value - ((value >>> 1) & 0x55_55_55_55);\n count = (count & 0x33_33_33_33) + ((count >>> 2) & 0x33_33_33_33);\n return (Math.imul((count + (count >>> 4)) & 0x0F_0F_0F_0F, 0x01_01_01_01) >>> 24) & 0xFF;\n}\n\nfunction toCandidate(\n fingerprint: string,\n startTokenIndex: number,\n endTokenIndex: number,\n first: TokenRange,\n last: TokenRange\n): DuplicateCandidate {\n return {\n fingerprint,\n tokenCount: endTokenIndex - startTokenIndex,\n startTokenIndex,\n endTokenIndex,\n startIndex: first.startIndex,\n endIndex: last.endIndex,\n startLine: first.startLine,\n endLine: last.endLine,\n };\n}\n\n/** Caches of hashText/hashText2 over '$0', '$1', ... so anonymized identifiers hash without allocating. */\nconst anonymizedIndexHashes: number[] = [];\nconst anonymizedIndexHashes2: number[] = [];\n\nfunction anonymizedIndexHash(index: number): number {\n let hash = anonymizedIndexHashes[index];\n if (hash === undefined) {\n hash = hashText(`$${index}`);\n anonymizedIndexHashes[index] = hash;\n }\n return hash;\n}\n\nfunction anonymizedIndexHash2(index: number): number {\n let hash = anonymizedIndexHashes2[index];\n if (hash === undefined) {\n hash = hashText2(`$${index}`);\n anonymizedIndexHashes2[index] = hash;\n }\n return hash;\n}\n\n/**\n * Content key of a token range: two independent 32-bit hashes over the normalized token sequence\n * (identifiers anonymized consistently by first-occurrence order) plus the token count. Regions\n * with equal keys are treated as equal content; a collision would need both 32-bit hashes and the\n * length to coincide, which is negligible for a metrics report. The format and arithmetic match\n * fingerprint_key in native/src/duplication.rs exactly, so window candidates fingerprinted here\n * group together with the per-file candidates the addon catalogues.\n */\nfunction fingerprintKey(\n tokens: Token[],\n literalCountPrefix: Int32Array,\n startTokenIndex: number,\n endTokenIndex: number\n): string {\n const literalCount = (literalCountPrefix[endTokenIndex] ?? 0) - (literalCountPrefix[startTokenIndex] ?? 0);\n const literalDense = isLiteralDense(literalCount, endTokenIndex - startTokenIndex);\n const [primary, secondary] = fingerprintHashPair(tokens, startTokenIndex, endTokenIndex, literalDense);\n return `${primary}:${secondary}:${endTokenIndex - startTokenIndex}`;\n}\n\n/**\n * A single 32-bit summary of a range for the coarse rolling-hash phase. Deliberately\n * density-agnostic: density is a property of the final candidate REGION, and folding literal\n * values into per-statement hashes would make a dense statement inside a logic-heavy window\n * (`const weights = [1, 2, 3];`) block the window from ever being enumerated. The coarse phase\n * over-approximates on shape alone; the exact region fingerprint still applies the density rule.\n */\nfunction fingerprintHash(tokens: Token[], startTokenIndex: number, endTokenIndex: number): number {\n const [primary, secondary] = fingerprintHashPair(tokens, startTokenIndex, endTokenIndex, false);\n // XOR already coerces to int32, matching the native side's i32 arithmetic.\n return primary ^ Math.imul(secondary, 31);\n}\n\nfunction fingerprintHashPair(\n tokens: Token[],\n startTokenIndex: number,\n endTokenIndex: number,\n foldLiteralValues: boolean\n): [number, number] {\n const indexByIdentifier = new Map<string, number>();\n let primary = 5381;\n let secondary = 52_711;\n for (let index = startTokenIndex; index < endTokenIndex; index += 1) {\n const token = tokens[index];\n if (!token) {\n continue;\n }\n // Each accumulator consumes its own independent per-token hash: sharing one would collapse\n // the key to 32 effective bits (a single djb2 collision would equate whole regions).\n let part: number;\n let part2: number;\n if (token.kind === 'id') {\n let identifierIndex = indexByIdentifier.get(token.text);\n if (identifierIndex === undefined) {\n identifierIndex = indexByIdentifier.size;\n indexByIdentifier.set(token.text, identifierIndex);\n }\n part = anonymizedIndexHash(identifierIndex);\n part2 = anonymizedIndexHash2(identifierIndex);\n } else {\n part = token.textHash;\n part2 = token.textHash2;\n }\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- `| 0` wraps the sum to int32 (Math.trunc does not), which must match the native side's wrapping i32 arithmetic.\n primary = (Math.imul(primary, 31) + part) | 0;\n secondary = Math.imul(secondary, 37) ^ part2;\n if (foldLiteralValues && token.literalHash !== undefined && token.literalHash2 !== undefined) {\n // oxlint-disable-next-line unicorn/prefer-math-trunc -- `| 0` wraps the sum to int32 (Math.trunc does not), which must match the native side's wrapping i32 arithmetic.\n primary = (Math.imul(primary, 31) + token.literalHash) | 0;\n secondary = Math.imul(secondary, 37) ^ token.literalHash2;\n }\n }\n return [primary, secondary];\n}\n\n/** djb2-style hash; XOR keeps the value in signed 32-bit range, which is fine for a grouping key. */\nfunction hashText(text: string): number {\n let hash = 5381;\n for (let index = 0; index < text.length; index += 1) {\n // oxlint-disable-next-line unicorn/prefer-code-point -- djb2 hashes UTF-16 code units; codePointAt would hash surrogate pairs twice (full code point, then the lone low surrogate).\n hash = Math.imul(hash, 33) ^ text.charCodeAt(index);\n }\n return hash;\n}\n\n/** FNV-1a over UTF-16 code units: independent of hashText so the two accumulators never share input. */\nfunction hashText2(text: string): number {\n let hash = -2_128_831_035; // 2166136261 as int32 (the FNV-1a offset basis)\n for (let index = 0; index < text.length; index += 1) {\n // oxlint-disable-next-line unicorn/prefer-code-point -- hashes UTF-16 code units like hashText.\n hash = Math.imul(hash ^ text.charCodeAt(index), 16_777_619);\n }\n return hash;\n}\n\nfunction combineHashes(hash: number, value: number): number {\n return Math.imul(hash, 31) + value;\n}\n\n/**\n * Merges duplicate groups separated by a small token gap into one gapped (Type-3) clone group: a\n * copy edited in one spot splits into two exact groups whose occurrences sit side by side in the\n * same order. Occurrences are paired greedily in source order; a merge happens when the pairing\n * fully consumes at least one group with at least two pairs. Equal-cardinality groups whose\n * occurrences all pair merge into one group as before. When cardinalities differ (a fragment also\n * occurs standalone: prefix ×3, suffix ×2), the fully-paired group is subsumed into the merged\n * gapped group while the other group is RETAINED with ALL its occurrences: dropping the leftover\n * would lose duplicated-line coverage, and reporting it alone would make a single-occurrence group\n * (contradicting duplicateBlockGroupCount's \"appears more than once\" meaning). Line coverage\n * unions ranges, so the overlap between the retained exact group and the merged group is harmless.\n * Merging repeats to a fixpoint so a clone edited in several spots still reassembles; it\n * terminates because a full merge shrinks the group count and a partial merge keeps it while\n * strictly growing the bounded total span of merged occurrences. Gap tokens are not matched\n * content: line coverage and sizes count only the matched segments. Generic so cross-file merging\n * can thread file identity through occurrences.\n */\nexport function mergeAdjacentGroups<T extends CountedOccurrence>(groups: T[][], maxGapTokens: number): T[][] {\n if (maxGapTokens <= 0 || groups.length < 2) {\n return groups;\n }\n // Deterministic processing order (mirrored by the native side): by first occurrence position.\n groups.sort(compareGroups);\n for (let restart = true; restart;) {\n restart = false;\n for (let leftIndex = 0; leftIndex < groups.length && !restart; leftIndex += 1) {\n for (let rightIndex = leftIndex + 1; rightIndex < groups.length; rightIndex += 1) {\n const left = groups[leftIndex];\n const right = groups[rightIndex];\n if (!left || !right) {\n continue;\n }\n const forward = mergeGroups(left, right, maxGapTokens);\n const result = forward ?? mergeGroups(right, left, maxGapTokens);\n if (!result) {\n continue;\n }\n const leftConsumed = forward ? result.firstConsumed : result.secondConsumed;\n const rightConsumed = forward ? result.secondConsumed : result.firstConsumed;\n if (leftConsumed && rightConsumed) {\n groups[leftIndex] = result.merged;\n groups.splice(rightIndex, 1);\n } else if (rightConsumed) {\n groups[rightIndex] = result.merged;\n } else {\n groups[leftIndex] = result.merged;\n }\n // A partial merge retains the not-fully-consumed group with ALL its occurrences (line\n // coverage must not shrink, and a reported group must keep >= 2 occurrences), so its\n // paired occurrences now also live inside the merged group's occurrences: mark them so\n // duplicateBlockCount counts each token span once.\n for (const occurrence of result.pairedRetained) {\n occurrence.sharedWithMergedGroup = true;\n }\n groups.sort(compareGroups);\n restart = true;\n break;\n }\n }\n }\n return groups;\n}\n\nfunction compareGroups(left: CountedOccurrence[], right: CountedOccurrence[]): number {\n const leftFirst = left[0];\n const rightFirst = right[0];\n return (\n (leftFirst?.startTokenIndex ?? 0) - (rightFirst?.startTokenIndex ?? 0) ||\n (leftFirst?.endTokenIndex ?? 0) - (rightFirst?.endTokenIndex ?? 0)\n );\n}\n\ninterface MergeResult<T> {\n merged: T[];\n /** Whether every occurrence of the respective input group was paired into the merge. */\n firstConsumed: boolean;\n secondConsumed: boolean;\n /** The retained (not fully consumed) group's occurrences that were paired into the merge. */\n pairedRetained: T[];\n}\n\n/**\n * Pairs `second` occurrences with gap-preceding `first` occurrences, greedily in source order:\n * each trailing occurrence takes the earliest unused leading occurrence within the gap, and a\n * pair's leading must start at or after the previous pair's trailing end so merged spans never\n * overlap. A merge needs at least two pairs (a merged group must still mean \"appears more than\n * once\") and must fully consume at least one group; for equal cardinalities this reduces to the\n * strict all-pairs merge, so pre-partial-merge behavior is unchanged there.\n */\nfunction mergeGroups<T extends CountedOccurrence>(\n first: T[],\n second: T[],\n maxGapTokens: number\n): MergeResult<T> | undefined {\n // Occurrences a previous partial merge already paired into a merged group must not pair again:\n // their spans already live inside that merged group, so re-pairing them would assemble a second,\n // competing merged group instead of letting the existing merged group extend (and would count\n // the same span twice). Consumption is still judged against the FULL group, so a group holding\n // shared occurrences is never subsumed away.\n const leadings = first.filter((occurrence) => !occurrence.sharedWithMergedGroup);\n const trailings = second.filter((occurrence) => !occurrence.sharedWithMergedGroup);\n const pairs: [T, T][] = [];\n let leadingIndex = 0;\n let previousTrailingEnd = -1;\n for (const trailing of trailings) {\n // Leadings ending too far before this trailing can never pair a later (even farther) one.\n while (leadingIndex < leadings.length) {\n const leading = leadings[leadingIndex];\n if (leading && leading.endTokenIndex + maxGapTokens < trailing.startTokenIndex) {\n leadingIndex += 1;\n } else {\n break;\n }\n }\n const leading = leadings[leadingIndex];\n if (\n leading &&\n leading.endTokenIndex <= trailing.startTokenIndex &&\n leading.startTokenIndex >= previousTrailingEnd\n ) {\n pairs.push([leading, trailing]);\n previousTrailingEnd = trailing.endTokenIndex;\n leadingIndex += 1;\n }\n }\n const firstConsumed = pairs.length === first.length;\n const secondConsumed = pairs.length === second.length;\n if (pairs.length < 2 || (!firstConsumed && !secondConsumed)) {\n return undefined;\n }\n const merged = pairs.map(([leading, trailing]) => ({\n ...leading,\n // A merged occurrence is a fresh span combination; it never inherits shared-span marks.\n sharedWithMergedGroup: undefined,\n segments: [...leading.segments, ...trailing.segments],\n tokenCount: leading.tokenCount + trailing.tokenCount,\n endTokenIndex: trailing.endTokenIndex,\n endIndex: trailing.endIndex,\n endLine: trailing.endLine,\n }));\n const pairedRetained =\n firstConsumed === secondConsumed ? [] : pairs.map(([leading, trailing]) => (firstConsumed ? trailing : leading));\n return { merged, firstConsumed, secondConsumed, pairedRetained };\n}\n\n/**\n * Redundant copies one group adds to duplicateBlockCount. Each redundant occurrence contributes\n * one count per matched fragment, so merging a gapped clone's fragments into one group does not\n * halve duplicateBlockCount: an edited two-fragment pair still counts 2, exactly as its unmerged\n * fragments did. Occurrence shapes can differ within one group (a\n * gap-merged exact pair plus an appended whole-block near-miss copy), so every occurrence's\n * fragments are summed and one representative — the largest — is deducted, keeping the count\n * independent of source order. Occurrences a partial gapped merge also paired into a merged group\n * are skipped: their spans are already counted there, and such a retained group deducts no\n * representative of its own — the merged group's representative already stands for the shared\n * content — so no token span contributes to the count twice.\n */\nexport function countRedundantFragments(group: CountedOccurrence[]): number {\n let fragmentCount = 0;\n let maxFragmentCount = 0;\n let hasSharedOccurrence = false;\n for (const occurrence of group) {\n if (occurrence.sharedWithMergedGroup) {\n hasSharedOccurrence = true;\n continue;\n }\n fragmentCount += occurrence.segments.length;\n maxFragmentCount = Math.max(maxFragmentCount, occurrence.segments.length);\n }\n return hasSharedOccurrence ? fragmentCount : fragmentCount - maxFragmentCount;\n}\n\n/** Adds the 1-based code lines the segment's matched tokens cover; shared with cross-file coverage. */\nexport function collectSegmentLines(\n segment: { startTokenIndex: number; endTokenIndex: number },\n tokens: Token[],\n codeLineNumbers: Set<number> | undefined,\n duplicatedLines: Set<number>\n): void {\n for (let index = segment.startTokenIndex; index < segment.endTokenIndex; index += 1) {\n const token = tokens[index];\n for (let row = token?.startRow ?? 0; row <= (token?.endRow ?? -1); row += 1) {\n if (!codeLineNumbers || codeLineNumbers.has(row + 1)) {\n duplicatedLines.add(row + 1);\n }\n }\n }\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./crossFileDuplication.cjs"),t=require("./languages.cjs"),n=require("./nativeMetrics.cjs"),r=require("./metrics.cjs"),i=require("./regressionGate.cjs");exports.TreeMeasurer=r.TreeMeasurer,exports.collectCrossFileDuplicationFileData=r.collectCrossFileDuplicationFileData,exports.collectDuplicationCandidates=r.collectDuplicationCandidates,exports.collectFunctionTokenSequences=r.collectFunctionTokenSequences,exports.defaultGateOptions=i.defaultGateOptions,exports.defaultLanguages=t.defaultLanguages,exports.defaultMeasurer=r.defaultMeasurer,exports.evaluateRegressionGate=i.evaluateRegressionGate,exports.isNativeBackendAvailable=n.isNativeBackendAvailable,exports.measureCode=r.measureCode,exports.measureCrossFileDuplication=e.measureCrossFileDuplication,exports.supportedLanguages=t.supportedLanguages;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./crossFileDuplication.cjs"),t=require("./languages.cjs"),n=require("./metrics.cjs"),r=require("./regressionGate.cjs");exports.TreeMeasurer=n.TreeMeasurer,exports.collectCrossFileDuplicationFileData=n.collectCrossFileDuplicationFileData,exports.collectDuplicationCandidates=n.collectDuplicationCandidates,exports.collectFunctionTokenSequences=n.collectFunctionTokenSequences,exports.defaultGateOptions=r.defaultGateOptions,exports.defaultLanguages=t.defaultLanguages,exports.defaultMeasurer=n.defaultMeasurer,exports.evaluateRegressionGate=r.evaluateRegressionGate,exports.measureCode=n.measureCode,exports.measureCrossFileDuplication=e.measureCrossFileDuplication,exports.supportedLanguages=t.supportedLanguages;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,6 @@ export type { CrossFileDuplicateBlockGroup, CrossFileDuplicateOccurrence, CrossF
3
3
  export type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';
4
4
  export { defaultLanguages, supportedLanguages } from './languages.js';
5
5
  export { TreeMeasurer, collectCrossFileDuplicationFileData, collectDuplicationCandidates, collectFunctionTokenSequences, defaultMeasurer, measureCode, } from './metrics.js';
6
- export { isNativeBackendAvailable } from './nativeMetrics.js';
7
6
  export { defaultGateOptions, evaluateRegressionGate } from './regressionGate.js';
8
7
  export type { GateFileInput, GateOptions, GateResult, GateTolerances, GateViolation, NewFunctionThresholds, } from './regressionGate.js';
9
8
  export type { CodeMetrics, DuplicationMetrics, DuplicationOptions, FunctionMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, LineMetrics, MeasureOptions, SupportedLanguage, } from './types.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{defaultLanguages as t,supportedLanguages as n}from"./languages.js";import{isNativeBackendAvailable as r}from"./nativeMetrics.js";import{TreeMeasurer as i,collectCrossFileDuplicationFileData as a,collectDuplicationCandidates as o,collectFunctionTokenSequences as s,defaultMeasurer as c,measureCode as l}from"./metrics.js";import{defaultGateOptions as u,evaluateRegressionGate as d}from"./regressionGate.js";export{i as TreeMeasurer,a as collectCrossFileDuplicationFileData,o as collectDuplicationCandidates,s as collectFunctionTokenSequences,u as defaultGateOptions,t as defaultLanguages,c as defaultMeasurer,d as evaluateRegressionGate,r as isNativeBackendAvailable,l as measureCode,e as measureCrossFileDuplication,n as supportedLanguages};
1
+ import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{defaultLanguages as t,supportedLanguages as n}from"./languages.js";import{TreeMeasurer as r,collectCrossFileDuplicationFileData as i,collectDuplicationCandidates as a,collectFunctionTokenSequences as o,defaultMeasurer as s,measureCode as c}from"./metrics.js";import{defaultGateOptions as l,evaluateRegressionGate as u}from"./regressionGate.js";export{r as TreeMeasurer,i as collectCrossFileDuplicationFileData,a as collectDuplicationCandidates,o as collectFunctionTokenSequences,l as defaultGateOptions,t as defaultLanguages,s as defaultMeasurer,u as evaluateRegressionGate,c as measureCode,e as measureCrossFileDuplication,n as supportedLanguages};
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./_virtual/_rolldown/runtime.cjs");let t=require("tree-sitter-c");t=e.__toESM(t,1);let n=require("tree-sitter-cpp");n=e.__toESM(n,1);let r=require("tree-sitter-go");r=e.__toESM(r,1);let i=require("tree-sitter-java");i=e.__toESM(i,1);let a=require("tree-sitter-javascript");a=e.__toESM(a,1);let o=require("tree-sitter-python");o=e.__toESM(o,1);let s=require("tree-sitter-ruby");s=e.__toESM(s,1);let c=require("tree-sitter-rust");c=e.__toESM(c,1);let l=require("tree-sitter-typescript");l=e.__toESM(l,1);const u=[`function`,`function_declaration`,`function_definition`,`function_expression`,`function_item`,`function_signature_item`,`function_declarator`,`func_literal`,`method_declaration`,`method_definition`,`method_spec`,`arrow_function`,`generator_function`,`generator_function_declaration`,`lambda`,`lambda_expression`,`closure_expression`],d=[`if_statement`,`elif_clause`,`else_if_clause`,`for_statement`,`for_in_statement`,`while_statement`,`do_statement`,`catch_clause`,`except_clause`,`case_clause`,`switch_case`,`match_arm`,`conditional_expression`,`ternary_expression`,`if_expression`,`while_expression`,`for_expression`,`loop_expression`],f=[...d,`expression_case`,`type_case`,`communication_case`],p=[...d,`switch_default`],m=[...f,`default_case`],h=[...u,`constructor_declaration`,`compact_constructor_declaration`],g=[...d,`enhanced_for_statement`,`switch_block_statement_group`,`switch_rule`],_=[`method`,`singleton_method`,`lambda`,`block`,`do_block`],v=[`if`,`elsif`,`unless`,`while`,`until`,`for`,`when`,`in_clause`,`rescue`,`conditional`,`if_modifier`,`unless_modifier`,`while_modifier`,`until_modifier`,`rescue_modifier`],y=[`function_definition`,`lambda_expression`],b=[...d,`case_statement`],x=[...b,`for_range_loop`],S=`import_statement.export_statement.lexical_declaration.variable_declaration.function_declaration.function_signature.generator_function_declaration.class_declaration.abstract_class_declaration.module.method_definition.abstract_method_signature.class_static_block.field_definition.public_field_definition.type_alias_declaration.interface_declaration.enum_declaration.expression_statement.if_statement.else_clause.switch_statement.switch_case.switch_default.for_statement.for_in_statement.while_statement.do_statement.catch_clause.finally_clause.labeled_statement.return_statement.break_statement.continue_statement.throw_statement.debugger_statement.with_statement`.split(`.`),C=`import_statement.import_from_statement.future_import_statement.print_statement.exec_statement.assert_statement.expression_statement.return_statement.delete_statement.raise_statement.pass_statement.break_statement.continue_statement.global_statement.nonlocal_statement.if_statement.elif_clause.else_clause.for_statement.while_statement.except_clause.except_group_clause.finally_clause.with_statement.match_statement.case_clause.function_definition.class_definition.type_alias_statement`.split(`.`),w=`package_clause.import_spec.type_spec.type_alias.const_spec.var_spec.function_declaration.method_declaration.short_var_declaration.expression_statement.send_statement.inc_statement.dec_statement.assignment_statement.if_statement.for_statement.expression_switch_statement.type_switch_statement.select_statement.expression_case.type_case.communication_case.default_case.return_statement.break_statement.continue_statement.goto_statement.fallthrough_statement.defer_statement.go_statement.labeled_statement`.split(`.`),T=[`use_declaration`,`extern_crate_declaration`,`foreign_mod_item`,`mod_item`,`const_item`,`static_item`,`struct_item`,`enum_item`,`union_item`,`trait_item`,`impl_item`,`function_item`,`function_signature_item`,`type_item`,`associated_type`,`macro_definition`,`field_declaration`,`let_declaration`,`expression_statement`,`else_clause`,`match_arm`],E=`package_declaration.import_declaration.module_declaration.requires_module_directive.exports_module_directive.opens_module_directive.uses_module_directive.provides_module_directive.class_declaration.interface_declaration.enum_declaration.annotation_type_declaration.annotation_type_element_declaration.record_declaration.field_declaration.constant_declaration.method_declaration.constructor_declaration.compact_constructor_declaration.static_initializer.explicit_constructor_invocation.local_variable_declaration.expression_statement.if_statement.while_statement.do_statement.for_statement.enhanced_for_statement.switch_statement.switch_expression.switch_label.break_statement.continue_statement.return_statement.throw_statement.assert_statement.synchronized_statement.labeled_statement.yield_statement.resource.catch_clause.finally_clause`.split(`.`),D=[`elsif`,`else`,`when`,`in_clause`,`rescue`,`ensure`],O=[`program`,`body_statement`,`then`,`else`,`do`,`block_body`,`begin`,`ensure`],k=[`preproc_include`,`preproc_def`,`preproc_function_def`,`declaration`,`type_definition`,`field_declaration`,`function_definition`,`struct_specifier`,`enum_specifier`,`union_specifier`,`expression_statement`,`if_statement`,`else_clause`,`switch_statement`,`case_statement`,`for_statement`,`while_statement`,`do_statement`,`return_statement`,`break_statement`,`continue_statement`,`goto_statement`,`labeled_statement`],A=[...k,`class_specifier`,`namespace_definition`,`using_declaration`,`alias_declaration`,`namespace_alias_definition`,`concept_definition`,`static_assert_declaration`,`for_range_loop`,`catch_clause`,`throw_statement`,`co_return_statement`,`co_yield_statement`];function j(e){return N(e,`default`)?e.default:e}function M(e){return l.default[e]}function N(e,t){return typeof e!=`object`||!e||!(t in e)?!1:!!e[t]}const P=[{name:`javascript`,aliases:[`js`,`mjs`,`cjs`],parserLanguage:j(a.default)},{name:`jsx`,parserLanguage:j(a.default)},{name:`typescript`,aliases:[`ts`],parserLanguage:M(`typescript`)},{name:`tsx`,parserLanguage:M(`tsx`)},{name:`python`,aliases:[`py`],parserLanguage:j(o.default),ncssNodeTypes:C},{name:`go`,parserLanguage:j(r.default),decisionNodeTypes:f,nestingNodeTypes:m,ncssNodeTypes:w},{name:`rust`,aliases:[`rs`],parserLanguage:j(c.default),ncssNodeTypes:T,ncssContainerNodeTypes:[`block`,`else_clause`]},{name:`java`,parserLanguage:j(i.default),functionNodeTypes:h,decisionNodeTypes:g,nestingNodeTypes:g,ncssNodeTypes:E},{name:`ruby`,aliases:[`rb`],parserLanguage:j(s.default),functionNodeTypes:_,decisionNodeTypes:v,nestingNodeTypes:v,ncssNodeTypes:D,ncssContainerNodeTypes:O},{name:`c`,parserLanguage:j(t.default),functionNodeTypes:y,decisionNodeTypes:b,nestingNodeTypes:b,ncssNodeTypes:k},{name:`cpp`,aliases:[`c++`,`cxx`],parserLanguage:j(n.default),functionNodeTypes:y,decisionNodeTypes:x,nestingNodeTypes:x,ncssNodeTypes:A}].map(e=>({functionNodeTypes:u,decisionNodeTypes:d,nestingNodeTypes:p,ncssNodeTypes:S,...e}));function F(e=P){let t=new Map;for(let n of e){t.set(n.name,n);for(let e of n.aliases??[])t.set(e,n)}return t}const I=P.map(e=>e.name);exports.createLanguageRegistry=F,exports.defaultLanguages=P,exports.supportedLanguages=I;
1
+ "use strict";const e=[{name:`javascript`,aliases:[`js`,`mjs`,`cjs`]},{name:`jsx`},{name:`typescript`,aliases:[`ts`]},{name:`tsx`},{name:`python`,aliases:[`py`]},{name:`go`},{name:`rust`,aliases:[`rs`]},{name:`java`},{name:`ruby`,aliases:[`rb`]},{name:`c`},{name:`cpp`,aliases:[`c++`,`cxx`]}];function t(t=e){let n=new Map;for(let e of t){n.set(e.name,e);for(let t of e.aliases??[])n.set(t,e)}return n}const n=e.map(e=>e.name);exports.createLanguageRegistry=t,exports.defaultLanguages=e,exports.supportedLanguages=n;
2
2
  //# sourceMappingURL=languages.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"languages.cjs","names":["grammars","JavaScript","Python","Go","Rust","Java","Ruby","C","Cpp"],"sources":["../src/languages.ts"],"sourcesContent":["import C from 'tree-sitter-c';\nimport Cpp from 'tree-sitter-cpp';\nimport Go from 'tree-sitter-go';\nimport Java from 'tree-sitter-java';\nimport JavaScript from 'tree-sitter-javascript';\nimport Python from 'tree-sitter-python';\nimport Ruby from 'tree-sitter-ruby';\nimport Rust from 'tree-sitter-rust';\nimport TypeScript from 'tree-sitter-typescript';\nimport type { LanguageDefinition, LanguageName, ParserLanguage } from './types.js';\n\ntype GrammarModule = unknown;\n\nconst commonFunctionNodes = [\n 'function',\n 'function_declaration',\n 'function_definition',\n 'function_expression',\n 'function_item',\n 'function_signature_item',\n 'function_declarator',\n 'func_literal',\n 'method_declaration',\n 'method_definition',\n 'method_spec',\n 'arrow_function',\n 'generator_function',\n 'generator_function_declaration',\n 'lambda',\n 'lambda_expression',\n 'closure_expression',\n] as const;\n\nconst commonDecisionNodes = [\n 'if_statement',\n 'elif_clause',\n 'else_if_clause',\n 'for_statement',\n 'for_in_statement',\n 'while_statement',\n 'do_statement',\n 'catch_clause',\n 'except_clause',\n 'case_clause',\n 'switch_case',\n 'match_arm',\n 'conditional_expression',\n 'ternary_expression',\n 'if_expression',\n 'while_expression',\n 'for_expression',\n 'loop_expression',\n] as const;\n\n// Go `switch`/`select` branches are `*_case` nodes, not the `case_clause`/`switch_case` of other grammars.\nconst goDecisionNodes = [...commonDecisionNodes, 'expression_case', 'type_case', 'communication_case'] as const;\n\n// Default switch branches add no decision, but their contents are nested inside the switch like\n// any other arm, so they appear in the nesting sets only.\nconst commonNestingNodes = [...commonDecisionNodes, 'switch_default'] as const;\nconst goNestingNodes = [...goDecisionNodes, 'default_case'] as const;\n\nconst javaFunctionNodes = [\n ...commonFunctionNodes,\n 'constructor_declaration',\n 'compact_constructor_declaration',\n] as const;\nconst javaDecisionNodes = [\n ...commonDecisionNodes,\n 'enhanced_for_statement',\n 'switch_block_statement_group',\n 'switch_rule',\n] as const;\n\n// Ruby node types are keyword-like (`if`, `while`, ...), so they must stay Ruby-specific: the same\n// strings appear as anonymous keyword tokens in other grammars and would be double-counted there.\n// `block`/`do_block` are Ruby's closures (`items.map { ... }`), the analog of JS callbacks.\nconst rubyFunctionNodes = ['method', 'singleton_method', 'lambda', 'block', 'do_block'] as const;\nconst rubyDecisionNodes = [\n 'if',\n 'elsif',\n 'unless',\n 'while',\n 'until',\n 'for',\n 'when',\n 'in_clause',\n 'rescue',\n 'conditional',\n 'if_modifier',\n 'unless_modifier',\n 'while_modifier',\n 'until_modifier',\n 'rescue_modifier',\n] as const;\n\n// `function_declarator` must stay out: it is nested inside every `function_definition` (which\n// would double-count) and also appears in body-less prototypes.\nconst cFunctionNodes = ['function_definition', 'lambda_expression'] as const;\nconst cDecisionNodes = [...commonDecisionNodes, 'case_statement'] as const;\nconst cppDecisionNodes = [...cDecisionNodes, 'for_range_loop'] as const;\n\n// NCSS node sets: every listed type counts as one non-commenting source statement. The Java set is\n// calibrated against PMD's NcssCount rule (`try` counts 0; `else`, `case`/`default` labels,\n// `catch`, `finally`, and try-with-resources resources count 1 each); the other languages follow\n// the same conventions with their grammar's node types.\nconst jsNcssNodes = [\n 'import_statement',\n 'export_statement',\n 'lexical_declaration',\n 'variable_declaration',\n 'function_declaration',\n 'function_signature',\n 'generator_function_declaration',\n 'class_declaration',\n 'abstract_class_declaration',\n 'module',\n 'method_definition',\n 'abstract_method_signature',\n 'class_static_block',\n 'field_definition',\n 'public_field_definition',\n 'type_alias_declaration',\n 'interface_declaration',\n 'enum_declaration',\n 'expression_statement',\n 'if_statement',\n 'else_clause',\n 'switch_statement',\n 'switch_case',\n 'switch_default',\n 'for_statement',\n 'for_in_statement',\n 'while_statement',\n 'do_statement',\n 'catch_clause',\n 'finally_clause',\n 'labeled_statement',\n 'return_statement',\n 'break_statement',\n 'continue_statement',\n 'throw_statement',\n 'debugger_statement',\n 'with_statement',\n] as const;\n\nconst pythonNcssNodes = [\n 'import_statement',\n 'import_from_statement',\n 'future_import_statement',\n 'print_statement',\n 'exec_statement',\n 'assert_statement',\n 'expression_statement',\n 'return_statement',\n 'delete_statement',\n 'raise_statement',\n 'pass_statement',\n 'break_statement',\n 'continue_statement',\n 'global_statement',\n 'nonlocal_statement',\n 'if_statement',\n 'elif_clause',\n 'else_clause',\n 'for_statement',\n 'while_statement',\n 'except_clause',\n 'except_group_clause',\n 'finally_clause',\n 'with_statement',\n 'match_statement',\n 'case_clause',\n 'function_definition',\n 'class_definition',\n 'type_alias_statement',\n] as const;\n\nconst goNcssNodes = [\n 'package_clause',\n 'import_spec',\n 'type_spec',\n 'type_alias',\n 'const_spec',\n 'var_spec',\n 'function_declaration',\n 'method_declaration',\n // Struct fields and interface members count contextually (see ncss.ts): only inside a named\n // `type T struct/interface { ... }`, not in inline anonymous types, which are part of one\n // declaration.\n 'short_var_declaration',\n 'expression_statement',\n 'send_statement',\n 'inc_statement',\n 'dec_statement',\n 'assignment_statement',\n 'if_statement',\n 'for_statement',\n 'expression_switch_statement',\n 'type_switch_statement',\n 'select_statement',\n 'expression_case',\n 'type_case',\n 'communication_case',\n 'default_case',\n 'return_statement',\n 'break_statement',\n 'continue_statement',\n 'goto_statement',\n 'fallthrough_statement',\n 'defer_statement',\n 'go_statement',\n 'labeled_statement',\n] as const;\n\nconst rustNcssNodes = [\n 'use_declaration',\n 'extern_crate_declaration',\n 'foreign_mod_item',\n 'mod_item',\n 'const_item',\n 'static_item',\n 'struct_item',\n 'enum_item',\n 'union_item',\n 'trait_item',\n 'impl_item',\n 'function_item',\n 'function_signature_item',\n 'type_item',\n 'associated_type',\n 'macro_definition',\n 'field_declaration',\n 'let_declaration',\n 'expression_statement',\n 'else_clause',\n 'match_arm',\n] as const;\n\nconst javaNcssNodes = [\n 'package_declaration',\n 'import_declaration',\n 'module_declaration',\n 'requires_module_directive',\n 'exports_module_directive',\n 'opens_module_directive',\n 'uses_module_directive',\n 'provides_module_directive',\n 'class_declaration',\n 'interface_declaration',\n 'enum_declaration',\n 'annotation_type_declaration',\n 'annotation_type_element_declaration',\n 'record_declaration',\n 'field_declaration',\n 'constant_declaration',\n 'method_declaration',\n 'constructor_declaration',\n 'compact_constructor_declaration',\n 'static_initializer',\n 'explicit_constructor_invocation',\n 'local_variable_declaration',\n 'expression_statement',\n 'if_statement',\n 'while_statement',\n 'do_statement',\n 'for_statement',\n 'enhanced_for_statement',\n 'switch_statement',\n 'switch_expression',\n 'switch_label',\n 'break_statement',\n 'continue_statement',\n 'return_statement',\n 'throw_statement',\n 'assert_statement',\n 'synchronized_statement',\n 'labeled_statement',\n 'yield_statement',\n 'resource',\n 'catch_clause',\n 'finally_clause',\n] as const;\n\n// Ruby statements have no wrapper node types, so bodies are counted positionally (see\n// ncssContainerNodeTypes); only clause nodes hanging off non-container parents need listing.\nconst rubyNcssNodes = ['elsif', 'else', 'when', 'in_clause', 'rescue', 'ensure'] as const;\nconst rubyNcssContainers = [\n 'program',\n 'body_statement',\n 'then',\n 'else',\n 'do',\n 'block_body',\n 'begin',\n 'ensure',\n] as const;\n\nconst cNcssNodes = [\n 'preproc_include',\n 'preproc_def',\n 'preproc_function_def',\n 'declaration',\n 'type_definition',\n 'field_declaration',\n 'function_definition',\n // Counted only when they carry a body (see bodylessNcssSpecifierTypes in ncss.ts).\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'expression_statement',\n 'if_statement',\n 'else_clause',\n 'switch_statement',\n 'case_statement',\n 'for_statement',\n 'while_statement',\n 'do_statement',\n 'return_statement',\n 'break_statement',\n 'continue_statement',\n 'goto_statement',\n 'labeled_statement',\n] as const;\nconst cppNcssNodes = [\n ...cNcssNodes,\n 'class_specifier',\n 'namespace_definition',\n 'using_declaration',\n 'alias_declaration',\n 'namespace_alias_definition',\n 'concept_definition',\n 'static_assert_declaration',\n 'for_range_loop',\n 'catch_clause',\n 'throw_statement',\n 'co_return_statement',\n 'co_yield_statement',\n] as const;\n\nfunction normalizeGrammar(module: GrammarModule): ParserLanguage {\n if (isGrammarWrapper(module, 'default')) {\n return module.default;\n }\n\n return module;\n}\n\nfunction getTypeScriptGrammar(name: 'typescript' | 'tsx'): ParserLanguage {\n const grammars = TypeScript as unknown as Record<string, GrammarModule>;\n return grammars[name];\n}\n\nfunction isGrammarWrapper(value: GrammarModule, key: 'default'): value is Record<typeof key, ParserLanguage> {\n if (typeof value !== 'object' || value === null || !(key in value)) {\n return false;\n }\n\n return Boolean((value as Record<string, ParserLanguage>)[key]);\n}\n\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n {\n name: 'javascript',\n aliases: ['js', 'mjs', 'cjs'],\n parserLanguage: normalizeGrammar(JavaScript as unknown as GrammarModule),\n },\n {\n name: 'jsx',\n parserLanguage: normalizeGrammar(JavaScript as unknown as GrammarModule),\n },\n {\n name: 'typescript',\n aliases: ['ts'],\n parserLanguage: getTypeScriptGrammar('typescript'),\n },\n {\n name: 'tsx',\n parserLanguage: getTypeScriptGrammar('tsx'),\n },\n {\n name: 'python',\n aliases: ['py'],\n parserLanguage: normalizeGrammar(Python as unknown as GrammarModule),\n ncssNodeTypes: pythonNcssNodes,\n },\n {\n name: 'go',\n parserLanguage: normalizeGrammar(Go as unknown as GrammarModule),\n decisionNodeTypes: goDecisionNodes,\n nestingNodeTypes: goNestingNodes,\n ncssNodeTypes: goNcssNodes,\n },\n {\n name: 'rust',\n aliases: ['rs'],\n parserLanguage: normalizeGrammar(Rust as unknown as GrammarModule),\n ncssNodeTypes: rustNcssNodes,\n // `else_clause` is a container so `else if` chains count the nested if_expression; a plain\n // `else { ... }` is unaffected because its only child is a `block`, itself a container.\n ncssContainerNodeTypes: ['block', 'else_clause'],\n },\n {\n name: 'java',\n parserLanguage: normalizeGrammar(Java as unknown as GrammarModule),\n functionNodeTypes: javaFunctionNodes,\n decisionNodeTypes: javaDecisionNodes,\n nestingNodeTypes: javaDecisionNodes,\n ncssNodeTypes: javaNcssNodes,\n },\n {\n name: 'ruby',\n aliases: ['rb'],\n parserLanguage: normalizeGrammar(Ruby as unknown as GrammarModule),\n functionNodeTypes: rubyFunctionNodes,\n decisionNodeTypes: rubyDecisionNodes,\n nestingNodeTypes: rubyDecisionNodes,\n ncssNodeTypes: rubyNcssNodes,\n ncssContainerNodeTypes: rubyNcssContainers,\n },\n {\n name: 'c',\n parserLanguage: normalizeGrammar(C as unknown as GrammarModule),\n functionNodeTypes: cFunctionNodes,\n decisionNodeTypes: cDecisionNodes,\n nestingNodeTypes: cDecisionNodes,\n ncssNodeTypes: cNcssNodes,\n },\n {\n name: 'cpp',\n aliases: ['c++', 'cxx'],\n parserLanguage: normalizeGrammar(Cpp as unknown as GrammarModule),\n functionNodeTypes: cFunctionNodes,\n decisionNodeTypes: cppDecisionNodes,\n nestingNodeTypes: cppDecisionNodes,\n ncssNodeTypes: cppNcssNodes,\n },\n].map((language) => ({\n functionNodeTypes: commonFunctionNodes,\n decisionNodeTypes: commonDecisionNodes,\n nestingNodeTypes: commonNestingNodes,\n ncssNodeTypes: jsNcssNodes,\n ...language,\n}));\n\nexport function createLanguageRegistry(\n languages: readonly LanguageDefinition[] = defaultLanguages\n): Map<LanguageName, LanguageDefinition> {\n const registry = new Map<LanguageName, LanguageDefinition>();\n\n for (const language of languages) {\n registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n registry.set(alias, language);\n }\n }\n\n return registry;\n}\n\nexport const supportedLanguages = defaultLanguages.map((language) => language.name);\n"],"mappings":"ohBAaA,MAAM,EAAsB,CAC1B,WACA,uBACA,sBACA,sBACA,gBACA,0BACA,sBACA,eACA,qBACA,oBACA,cACA,iBACA,qBACA,iCACA,SACA,oBACA,oBACF,EAEM,EAAsB,CAC1B,eACA,cACA,iBACA,gBACA,mBACA,kBACA,eACA,eACA,gBACA,cACA,cACA,YACA,yBACA,qBACA,gBACA,mBACA,iBACA,iBACF,EAGM,EAAkB,CAAC,GAAG,EAAqB,kBAAmB,YAAa,oBAAoB,EAI/F,EAAqB,CAAC,GAAG,EAAqB,gBAAgB,EAC9D,EAAiB,CAAC,GAAG,EAAiB,cAAc,EAEpD,EAAoB,CACxB,GAAG,EACH,0BACA,iCACF,EACM,EAAoB,CACxB,GAAG,EACH,yBACA,+BACA,aACF,EAKM,EAAoB,CAAC,SAAU,mBAAoB,SAAU,QAAS,UAAU,EAChF,EAAoB,CACxB,KACA,QACA,SACA,QACA,QACA,MACA,OACA,YACA,SACA,cACA,cACA,kBACA,iBACA,iBACA,iBACF,EAIM,EAAiB,CAAC,sBAAuB,mBAAmB,EAC5D,EAAiB,CAAC,GAAG,EAAqB,gBAAgB,EAC1D,EAAmB,CAAC,GAAG,EAAgB,gBAAgB,EAMvD,EAAc,iqBAsCpB,EAEM,EAAkB,gfA8BxB,EAEM,EAAc,kgBAmCpB,EAEM,EAAgB,CACpB,kBACA,2BACA,mBACA,WACA,aACA,cACA,cACA,YACA,aACA,aACA,YACA,gBACA,0BACA,YACA,kBACA,mBACA,oBACA,kBACA,uBACA,cACA,WACF,EAEM,EAAgB,k1BA2CtB,EAIM,EAAgB,CAAC,QAAS,OAAQ,OAAQ,YAAa,SAAU,QAAQ,EACzE,EAAqB,CACzB,UACA,iBACA,OACA,OACA,KACA,aACA,QACA,QACF,EAEM,EAAa,CACjB,kBACA,cACA,uBACA,cACA,kBACA,oBACA,sBAEA,mBACA,iBACA,kBACA,uBACA,eACA,cACA,mBACA,iBACA,gBACA,kBACA,eACA,mBACA,kBACA,qBACA,iBACA,mBACF,EACM,EAAe,CACnB,GAAG,EACH,kBACA,uBACA,oBACA,oBACA,6BACA,qBACA,4BACA,iBACA,eACA,kBACA,sBACA,oBACF,EAEA,SAAS,EAAiB,EAAuC,CAK/D,OAJI,EAAiB,EAAQ,SAAS,EAC7B,EAAO,QAGT,CACT,CAEA,SAAS,EAAqB,EAA4C,CAExE,OAAOA,EAAAA,QAAS,EAClB,CAEA,SAAS,EAAiB,EAAsB,EAA6D,CAK3G,OAJI,OAAO,GAAU,WAAY,GAAkB,EAAE,KAAO,GACnD,GAGF,EAAS,EAAyC,EAC3D,CAEA,MAAa,EAAkD,CAC7D,CACE,KAAM,aACN,QAAS,CAAC,KAAM,MAAO,KAAK,EAC5B,eAAgB,EAAiBC,EAAAA,OAAsC,CACzE,EACA,CACE,KAAM,MACN,eAAgB,EAAiBA,EAAAA,OAAsC,CACzE,EACA,CACE,KAAM,aACN,QAAS,CAAC,IAAI,EACd,eAAgB,EAAqB,YAAY,CACnD,EACA,CACE,KAAM,MACN,eAAgB,EAAqB,KAAK,CAC5C,EACA,CACE,KAAM,SACN,QAAS,CAAC,IAAI,EACd,eAAgB,EAAiBC,EAAAA,OAAkC,EACnE,cAAe,CACjB,EACA,CACE,KAAM,KACN,eAAgB,EAAiBC,EAAAA,OAA8B,EAC/D,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,CACjB,EACA,CACE,KAAM,OACN,QAAS,CAAC,IAAI,EACd,eAAgB,EAAiBC,EAAAA,OAAgC,EACjE,cAAe,EAGf,uBAAwB,CAAC,QAAS,aAAa,CACjD,EACA,CACE,KAAM,OACN,eAAgB,EAAiBC,EAAAA,OAAgC,EACjE,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,CACjB,EACA,CACE,KAAM,OACN,QAAS,CAAC,IAAI,EACd,eAAgB,EAAiBC,EAAAA,OAAgC,EACjE,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,EACf,uBAAwB,CAC1B,EACA,CACE,KAAM,IACN,eAAgB,EAAiBC,EAAAA,OAA6B,EAC9D,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,CACjB,EACA,CACE,KAAM,MACN,QAAS,CAAC,MAAO,KAAK,EACtB,eAAgB,EAAiBC,EAAAA,OAA+B,EAChE,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,CACjB,CACF,CAAC,CAAC,IAAK,IAAc,CACnB,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,EACf,GAAG,CACL,EAAE,EAEF,SAAgB,EACd,EAA2C,EACJ,CACvC,IAAM,EAAW,IAAI,IAErB,IAAK,IAAM,KAAY,EAAW,CAChC,EAAS,IAAI,EAAS,KAAM,CAAQ,EACpC,IAAK,IAAM,KAAS,EAAS,SAAW,CAAC,EACvC,EAAS,IAAI,EAAO,CAAQ,CAEhC,CAEA,OAAO,CACT,CAEA,MAAa,EAAqB,EAAiB,IAAK,GAAa,EAAS,IAAI"}
1
+ {"version":3,"file":"languages.cjs","names":[],"sources":["../src/languages.ts"],"sourcesContent":["import type { LanguageDefinition, LanguageName } from './types.js';\n\n/**\n * The built-in languages. Grammars and per-language node-type configuration live in the Rust\n * addon (native/src/languages.rs); this list only names the languages and their aliases so the\n * CLI and API can resolve and enumerate them without crossing the N-API boundary.\n */\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n { name: 'javascript', aliases: ['js', 'mjs', 'cjs'] },\n { name: 'jsx' },\n { name: 'typescript', aliases: ['ts'] },\n { name: 'tsx' },\n { name: 'python', aliases: ['py'] },\n { name: 'go' },\n { name: 'rust', aliases: ['rs'] },\n { name: 'java' },\n { name: 'ruby', aliases: ['rb'] },\n { name: 'c' },\n { name: 'cpp', aliases: ['c++', 'cxx'] },\n];\n\nexport function createLanguageRegistry(\n languages: readonly LanguageDefinition[] = defaultLanguages\n): Map<LanguageName, LanguageDefinition> {\n const registry = new Map<LanguageName, LanguageDefinition>();\n\n for (const language of languages) {\n registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n registry.set(alias, language);\n }\n }\n\n return registry;\n}\n\nexport const supportedLanguages = defaultLanguages.map((language) => language.name);\n"],"mappings":"aAOA,MAAa,EAAkD,CAC7D,CAAE,KAAM,aAAc,QAAS,CAAC,KAAM,MAAO,KAAK,CAAE,EACpD,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,aAAc,QAAS,CAAC,IAAI,CAAE,EACtC,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,SAAU,QAAS,CAAC,IAAI,CAAE,EAClC,CAAE,KAAM,IAAK,EACb,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,MAAO,EACf,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,GAAI,EACZ,CAAE,KAAM,MAAO,QAAS,CAAC,MAAO,KAAK,CAAE,CACzC,EAEA,SAAgB,EACd,EAA2C,EACJ,CACvC,IAAM,EAAW,IAAI,IAErB,IAAK,IAAM,KAAY,EAAW,CAChC,EAAS,IAAI,EAAS,KAAM,CAAQ,EACpC,IAAK,IAAM,KAAS,EAAS,SAAW,CAAC,EACvC,EAAS,IAAI,EAAO,CAAQ,CAEhC,CAEA,OAAO,CACT,CAEA,MAAa,EAAqB,EAAiB,IAAK,GAAa,EAAS,IAAI"}
@@ -1,4 +1,9 @@
1
1
  import type { LanguageDefinition, LanguageName } from './types.js';
2
+ /**
3
+ * The built-in languages. Grammars and per-language node-type configuration live in the Rust
4
+ * addon (native/src/languages.rs); this list only names the languages and their aliases so the
5
+ * CLI and API can resolve and enumerate them without crossing the N-API boundary.
6
+ */
2
7
  export declare const defaultLanguages: readonly LanguageDefinition[];
3
8
  export declare function createLanguageRegistry(languages?: readonly LanguageDefinition[]): Map<LanguageName, LanguageDefinition>;
4
9
  export declare const supportedLanguages: LanguageName[];
package/dist/languages.js CHANGED
@@ -1,2 +1,2 @@
1
- import e from"tree-sitter-c";import t from"tree-sitter-cpp";import n from"tree-sitter-go";import r from"tree-sitter-java";import i from"tree-sitter-javascript";import a from"tree-sitter-python";import o from"tree-sitter-ruby";import s from"tree-sitter-rust";import c from"tree-sitter-typescript";const l=[`function`,`function_declaration`,`function_definition`,`function_expression`,`function_item`,`function_signature_item`,`function_declarator`,`func_literal`,`method_declaration`,`method_definition`,`method_spec`,`arrow_function`,`generator_function`,`generator_function_declaration`,`lambda`,`lambda_expression`,`closure_expression`],u=[`if_statement`,`elif_clause`,`else_if_clause`,`for_statement`,`for_in_statement`,`while_statement`,`do_statement`,`catch_clause`,`except_clause`,`case_clause`,`switch_case`,`match_arm`,`conditional_expression`,`ternary_expression`,`if_expression`,`while_expression`,`for_expression`,`loop_expression`],d=[...u,`expression_case`,`type_case`,`communication_case`],f=[...u,`switch_default`],p=[...d,`default_case`],m=[...l,`constructor_declaration`,`compact_constructor_declaration`],h=[...u,`enhanced_for_statement`,`switch_block_statement_group`,`switch_rule`],g=[`method`,`singleton_method`,`lambda`,`block`,`do_block`],_=[`if`,`elsif`,`unless`,`while`,`until`,`for`,`when`,`in_clause`,`rescue`,`conditional`,`if_modifier`,`unless_modifier`,`while_modifier`,`until_modifier`,`rescue_modifier`],v=[`function_definition`,`lambda_expression`],y=[...u,`case_statement`],b=[...y,`for_range_loop`],x=`import_statement.export_statement.lexical_declaration.variable_declaration.function_declaration.function_signature.generator_function_declaration.class_declaration.abstract_class_declaration.module.method_definition.abstract_method_signature.class_static_block.field_definition.public_field_definition.type_alias_declaration.interface_declaration.enum_declaration.expression_statement.if_statement.else_clause.switch_statement.switch_case.switch_default.for_statement.for_in_statement.while_statement.do_statement.catch_clause.finally_clause.labeled_statement.return_statement.break_statement.continue_statement.throw_statement.debugger_statement.with_statement`.split(`.`),S=`import_statement.import_from_statement.future_import_statement.print_statement.exec_statement.assert_statement.expression_statement.return_statement.delete_statement.raise_statement.pass_statement.break_statement.continue_statement.global_statement.nonlocal_statement.if_statement.elif_clause.else_clause.for_statement.while_statement.except_clause.except_group_clause.finally_clause.with_statement.match_statement.case_clause.function_definition.class_definition.type_alias_statement`.split(`.`),C=`package_clause.import_spec.type_spec.type_alias.const_spec.var_spec.function_declaration.method_declaration.short_var_declaration.expression_statement.send_statement.inc_statement.dec_statement.assignment_statement.if_statement.for_statement.expression_switch_statement.type_switch_statement.select_statement.expression_case.type_case.communication_case.default_case.return_statement.break_statement.continue_statement.goto_statement.fallthrough_statement.defer_statement.go_statement.labeled_statement`.split(`.`),w=[`use_declaration`,`extern_crate_declaration`,`foreign_mod_item`,`mod_item`,`const_item`,`static_item`,`struct_item`,`enum_item`,`union_item`,`trait_item`,`impl_item`,`function_item`,`function_signature_item`,`type_item`,`associated_type`,`macro_definition`,`field_declaration`,`let_declaration`,`expression_statement`,`else_clause`,`match_arm`],T=`package_declaration.import_declaration.module_declaration.requires_module_directive.exports_module_directive.opens_module_directive.uses_module_directive.provides_module_directive.class_declaration.interface_declaration.enum_declaration.annotation_type_declaration.annotation_type_element_declaration.record_declaration.field_declaration.constant_declaration.method_declaration.constructor_declaration.compact_constructor_declaration.static_initializer.explicit_constructor_invocation.local_variable_declaration.expression_statement.if_statement.while_statement.do_statement.for_statement.enhanced_for_statement.switch_statement.switch_expression.switch_label.break_statement.continue_statement.return_statement.throw_statement.assert_statement.synchronized_statement.labeled_statement.yield_statement.resource.catch_clause.finally_clause`.split(`.`),E=[`elsif`,`else`,`when`,`in_clause`,`rescue`,`ensure`],D=[`program`,`body_statement`,`then`,`else`,`do`,`block_body`,`begin`,`ensure`],O=[`preproc_include`,`preproc_def`,`preproc_function_def`,`declaration`,`type_definition`,`field_declaration`,`function_definition`,`struct_specifier`,`enum_specifier`,`union_specifier`,`expression_statement`,`if_statement`,`else_clause`,`switch_statement`,`case_statement`,`for_statement`,`while_statement`,`do_statement`,`return_statement`,`break_statement`,`continue_statement`,`goto_statement`,`labeled_statement`],k=[...O,`class_specifier`,`namespace_definition`,`using_declaration`,`alias_declaration`,`namespace_alias_definition`,`concept_definition`,`static_assert_declaration`,`for_range_loop`,`catch_clause`,`throw_statement`,`co_return_statement`,`co_yield_statement`];function A(e){return M(e,`default`)?e.default:e}function j(e){return c[e]}function M(e,t){return typeof e!=`object`||!e||!(t in e)?!1:!!e[t]}const N=[{name:`javascript`,aliases:[`js`,`mjs`,`cjs`],parserLanguage:A(i)},{name:`jsx`,parserLanguage:A(i)},{name:`typescript`,aliases:[`ts`],parserLanguage:j(`typescript`)},{name:`tsx`,parserLanguage:j(`tsx`)},{name:`python`,aliases:[`py`],parserLanguage:A(a),ncssNodeTypes:S},{name:`go`,parserLanguage:A(n),decisionNodeTypes:d,nestingNodeTypes:p,ncssNodeTypes:C},{name:`rust`,aliases:[`rs`],parserLanguage:A(s),ncssNodeTypes:w,ncssContainerNodeTypes:[`block`,`else_clause`]},{name:`java`,parserLanguage:A(r),functionNodeTypes:m,decisionNodeTypes:h,nestingNodeTypes:h,ncssNodeTypes:T},{name:`ruby`,aliases:[`rb`],parserLanguage:A(o),functionNodeTypes:g,decisionNodeTypes:_,nestingNodeTypes:_,ncssNodeTypes:E,ncssContainerNodeTypes:D},{name:`c`,parserLanguage:A(e),functionNodeTypes:v,decisionNodeTypes:y,nestingNodeTypes:y,ncssNodeTypes:O},{name:`cpp`,aliases:[`c++`,`cxx`],parserLanguage:A(t),functionNodeTypes:v,decisionNodeTypes:b,nestingNodeTypes:b,ncssNodeTypes:k}].map(e=>({functionNodeTypes:l,decisionNodeTypes:u,nestingNodeTypes:f,ncssNodeTypes:x,...e}));function P(e=N){let t=new Map;for(let n of e){t.set(n.name,n);for(let e of n.aliases??[])t.set(e,n)}return t}const F=N.map(e=>e.name);export{P as createLanguageRegistry,N as defaultLanguages,F as supportedLanguages};
1
+ const e=[{name:`javascript`,aliases:[`js`,`mjs`,`cjs`]},{name:`jsx`},{name:`typescript`,aliases:[`ts`]},{name:`tsx`},{name:`python`,aliases:[`py`]},{name:`go`},{name:`rust`,aliases:[`rs`]},{name:`java`},{name:`ruby`,aliases:[`rb`]},{name:`c`},{name:`cpp`,aliases:[`c++`,`cxx`]}];function t(t=e){let n=new Map;for(let e of t){n.set(e.name,e);for(let t of e.aliases??[])n.set(t,e)}return n}const n=e.map(e=>e.name);export{t as createLanguageRegistry,e as defaultLanguages,n as supportedLanguages};
2
2
  //# sourceMappingURL=languages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"languages.js","names":["grammars"],"sources":["../src/languages.ts"],"sourcesContent":["import C from 'tree-sitter-c';\nimport Cpp from 'tree-sitter-cpp';\nimport Go from 'tree-sitter-go';\nimport Java from 'tree-sitter-java';\nimport JavaScript from 'tree-sitter-javascript';\nimport Python from 'tree-sitter-python';\nimport Ruby from 'tree-sitter-ruby';\nimport Rust from 'tree-sitter-rust';\nimport TypeScript from 'tree-sitter-typescript';\nimport type { LanguageDefinition, LanguageName, ParserLanguage } from './types.js';\n\ntype GrammarModule = unknown;\n\nconst commonFunctionNodes = [\n 'function',\n 'function_declaration',\n 'function_definition',\n 'function_expression',\n 'function_item',\n 'function_signature_item',\n 'function_declarator',\n 'func_literal',\n 'method_declaration',\n 'method_definition',\n 'method_spec',\n 'arrow_function',\n 'generator_function',\n 'generator_function_declaration',\n 'lambda',\n 'lambda_expression',\n 'closure_expression',\n] as const;\n\nconst commonDecisionNodes = [\n 'if_statement',\n 'elif_clause',\n 'else_if_clause',\n 'for_statement',\n 'for_in_statement',\n 'while_statement',\n 'do_statement',\n 'catch_clause',\n 'except_clause',\n 'case_clause',\n 'switch_case',\n 'match_arm',\n 'conditional_expression',\n 'ternary_expression',\n 'if_expression',\n 'while_expression',\n 'for_expression',\n 'loop_expression',\n] as const;\n\n// Go `switch`/`select` branches are `*_case` nodes, not the `case_clause`/`switch_case` of other grammars.\nconst goDecisionNodes = [...commonDecisionNodes, 'expression_case', 'type_case', 'communication_case'] as const;\n\n// Default switch branches add no decision, but their contents are nested inside the switch like\n// any other arm, so they appear in the nesting sets only.\nconst commonNestingNodes = [...commonDecisionNodes, 'switch_default'] as const;\nconst goNestingNodes = [...goDecisionNodes, 'default_case'] as const;\n\nconst javaFunctionNodes = [\n ...commonFunctionNodes,\n 'constructor_declaration',\n 'compact_constructor_declaration',\n] as const;\nconst javaDecisionNodes = [\n ...commonDecisionNodes,\n 'enhanced_for_statement',\n 'switch_block_statement_group',\n 'switch_rule',\n] as const;\n\n// Ruby node types are keyword-like (`if`, `while`, ...), so they must stay Ruby-specific: the same\n// strings appear as anonymous keyword tokens in other grammars and would be double-counted there.\n// `block`/`do_block` are Ruby's closures (`items.map { ... }`), the analog of JS callbacks.\nconst rubyFunctionNodes = ['method', 'singleton_method', 'lambda', 'block', 'do_block'] as const;\nconst rubyDecisionNodes = [\n 'if',\n 'elsif',\n 'unless',\n 'while',\n 'until',\n 'for',\n 'when',\n 'in_clause',\n 'rescue',\n 'conditional',\n 'if_modifier',\n 'unless_modifier',\n 'while_modifier',\n 'until_modifier',\n 'rescue_modifier',\n] as const;\n\n// `function_declarator` must stay out: it is nested inside every `function_definition` (which\n// would double-count) and also appears in body-less prototypes.\nconst cFunctionNodes = ['function_definition', 'lambda_expression'] as const;\nconst cDecisionNodes = [...commonDecisionNodes, 'case_statement'] as const;\nconst cppDecisionNodes = [...cDecisionNodes, 'for_range_loop'] as const;\n\n// NCSS node sets: every listed type counts as one non-commenting source statement. The Java set is\n// calibrated against PMD's NcssCount rule (`try` counts 0; `else`, `case`/`default` labels,\n// `catch`, `finally`, and try-with-resources resources count 1 each); the other languages follow\n// the same conventions with their grammar's node types.\nconst jsNcssNodes = [\n 'import_statement',\n 'export_statement',\n 'lexical_declaration',\n 'variable_declaration',\n 'function_declaration',\n 'function_signature',\n 'generator_function_declaration',\n 'class_declaration',\n 'abstract_class_declaration',\n 'module',\n 'method_definition',\n 'abstract_method_signature',\n 'class_static_block',\n 'field_definition',\n 'public_field_definition',\n 'type_alias_declaration',\n 'interface_declaration',\n 'enum_declaration',\n 'expression_statement',\n 'if_statement',\n 'else_clause',\n 'switch_statement',\n 'switch_case',\n 'switch_default',\n 'for_statement',\n 'for_in_statement',\n 'while_statement',\n 'do_statement',\n 'catch_clause',\n 'finally_clause',\n 'labeled_statement',\n 'return_statement',\n 'break_statement',\n 'continue_statement',\n 'throw_statement',\n 'debugger_statement',\n 'with_statement',\n] as const;\n\nconst pythonNcssNodes = [\n 'import_statement',\n 'import_from_statement',\n 'future_import_statement',\n 'print_statement',\n 'exec_statement',\n 'assert_statement',\n 'expression_statement',\n 'return_statement',\n 'delete_statement',\n 'raise_statement',\n 'pass_statement',\n 'break_statement',\n 'continue_statement',\n 'global_statement',\n 'nonlocal_statement',\n 'if_statement',\n 'elif_clause',\n 'else_clause',\n 'for_statement',\n 'while_statement',\n 'except_clause',\n 'except_group_clause',\n 'finally_clause',\n 'with_statement',\n 'match_statement',\n 'case_clause',\n 'function_definition',\n 'class_definition',\n 'type_alias_statement',\n] as const;\n\nconst goNcssNodes = [\n 'package_clause',\n 'import_spec',\n 'type_spec',\n 'type_alias',\n 'const_spec',\n 'var_spec',\n 'function_declaration',\n 'method_declaration',\n // Struct fields and interface members count contextually (see ncss.ts): only inside a named\n // `type T struct/interface { ... }`, not in inline anonymous types, which are part of one\n // declaration.\n 'short_var_declaration',\n 'expression_statement',\n 'send_statement',\n 'inc_statement',\n 'dec_statement',\n 'assignment_statement',\n 'if_statement',\n 'for_statement',\n 'expression_switch_statement',\n 'type_switch_statement',\n 'select_statement',\n 'expression_case',\n 'type_case',\n 'communication_case',\n 'default_case',\n 'return_statement',\n 'break_statement',\n 'continue_statement',\n 'goto_statement',\n 'fallthrough_statement',\n 'defer_statement',\n 'go_statement',\n 'labeled_statement',\n] as const;\n\nconst rustNcssNodes = [\n 'use_declaration',\n 'extern_crate_declaration',\n 'foreign_mod_item',\n 'mod_item',\n 'const_item',\n 'static_item',\n 'struct_item',\n 'enum_item',\n 'union_item',\n 'trait_item',\n 'impl_item',\n 'function_item',\n 'function_signature_item',\n 'type_item',\n 'associated_type',\n 'macro_definition',\n 'field_declaration',\n 'let_declaration',\n 'expression_statement',\n 'else_clause',\n 'match_arm',\n] as const;\n\nconst javaNcssNodes = [\n 'package_declaration',\n 'import_declaration',\n 'module_declaration',\n 'requires_module_directive',\n 'exports_module_directive',\n 'opens_module_directive',\n 'uses_module_directive',\n 'provides_module_directive',\n 'class_declaration',\n 'interface_declaration',\n 'enum_declaration',\n 'annotation_type_declaration',\n 'annotation_type_element_declaration',\n 'record_declaration',\n 'field_declaration',\n 'constant_declaration',\n 'method_declaration',\n 'constructor_declaration',\n 'compact_constructor_declaration',\n 'static_initializer',\n 'explicit_constructor_invocation',\n 'local_variable_declaration',\n 'expression_statement',\n 'if_statement',\n 'while_statement',\n 'do_statement',\n 'for_statement',\n 'enhanced_for_statement',\n 'switch_statement',\n 'switch_expression',\n 'switch_label',\n 'break_statement',\n 'continue_statement',\n 'return_statement',\n 'throw_statement',\n 'assert_statement',\n 'synchronized_statement',\n 'labeled_statement',\n 'yield_statement',\n 'resource',\n 'catch_clause',\n 'finally_clause',\n] as const;\n\n// Ruby statements have no wrapper node types, so bodies are counted positionally (see\n// ncssContainerNodeTypes); only clause nodes hanging off non-container parents need listing.\nconst rubyNcssNodes = ['elsif', 'else', 'when', 'in_clause', 'rescue', 'ensure'] as const;\nconst rubyNcssContainers = [\n 'program',\n 'body_statement',\n 'then',\n 'else',\n 'do',\n 'block_body',\n 'begin',\n 'ensure',\n] as const;\n\nconst cNcssNodes = [\n 'preproc_include',\n 'preproc_def',\n 'preproc_function_def',\n 'declaration',\n 'type_definition',\n 'field_declaration',\n 'function_definition',\n // Counted only when they carry a body (see bodylessNcssSpecifierTypes in ncss.ts).\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'expression_statement',\n 'if_statement',\n 'else_clause',\n 'switch_statement',\n 'case_statement',\n 'for_statement',\n 'while_statement',\n 'do_statement',\n 'return_statement',\n 'break_statement',\n 'continue_statement',\n 'goto_statement',\n 'labeled_statement',\n] as const;\nconst cppNcssNodes = [\n ...cNcssNodes,\n 'class_specifier',\n 'namespace_definition',\n 'using_declaration',\n 'alias_declaration',\n 'namespace_alias_definition',\n 'concept_definition',\n 'static_assert_declaration',\n 'for_range_loop',\n 'catch_clause',\n 'throw_statement',\n 'co_return_statement',\n 'co_yield_statement',\n] as const;\n\nfunction normalizeGrammar(module: GrammarModule): ParserLanguage {\n if (isGrammarWrapper(module, 'default')) {\n return module.default;\n }\n\n return module;\n}\n\nfunction getTypeScriptGrammar(name: 'typescript' | 'tsx'): ParserLanguage {\n const grammars = TypeScript as unknown as Record<string, GrammarModule>;\n return grammars[name];\n}\n\nfunction isGrammarWrapper(value: GrammarModule, key: 'default'): value is Record<typeof key, ParserLanguage> {\n if (typeof value !== 'object' || value === null || !(key in value)) {\n return false;\n }\n\n return Boolean((value as Record<string, ParserLanguage>)[key]);\n}\n\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n {\n name: 'javascript',\n aliases: ['js', 'mjs', 'cjs'],\n parserLanguage: normalizeGrammar(JavaScript as unknown as GrammarModule),\n },\n {\n name: 'jsx',\n parserLanguage: normalizeGrammar(JavaScript as unknown as GrammarModule),\n },\n {\n name: 'typescript',\n aliases: ['ts'],\n parserLanguage: getTypeScriptGrammar('typescript'),\n },\n {\n name: 'tsx',\n parserLanguage: getTypeScriptGrammar('tsx'),\n },\n {\n name: 'python',\n aliases: ['py'],\n parserLanguage: normalizeGrammar(Python as unknown as GrammarModule),\n ncssNodeTypes: pythonNcssNodes,\n },\n {\n name: 'go',\n parserLanguage: normalizeGrammar(Go as unknown as GrammarModule),\n decisionNodeTypes: goDecisionNodes,\n nestingNodeTypes: goNestingNodes,\n ncssNodeTypes: goNcssNodes,\n },\n {\n name: 'rust',\n aliases: ['rs'],\n parserLanguage: normalizeGrammar(Rust as unknown as GrammarModule),\n ncssNodeTypes: rustNcssNodes,\n // `else_clause` is a container so `else if` chains count the nested if_expression; a plain\n // `else { ... }` is unaffected because its only child is a `block`, itself a container.\n ncssContainerNodeTypes: ['block', 'else_clause'],\n },\n {\n name: 'java',\n parserLanguage: normalizeGrammar(Java as unknown as GrammarModule),\n functionNodeTypes: javaFunctionNodes,\n decisionNodeTypes: javaDecisionNodes,\n nestingNodeTypes: javaDecisionNodes,\n ncssNodeTypes: javaNcssNodes,\n },\n {\n name: 'ruby',\n aliases: ['rb'],\n parserLanguage: normalizeGrammar(Ruby as unknown as GrammarModule),\n functionNodeTypes: rubyFunctionNodes,\n decisionNodeTypes: rubyDecisionNodes,\n nestingNodeTypes: rubyDecisionNodes,\n ncssNodeTypes: rubyNcssNodes,\n ncssContainerNodeTypes: rubyNcssContainers,\n },\n {\n name: 'c',\n parserLanguage: normalizeGrammar(C as unknown as GrammarModule),\n functionNodeTypes: cFunctionNodes,\n decisionNodeTypes: cDecisionNodes,\n nestingNodeTypes: cDecisionNodes,\n ncssNodeTypes: cNcssNodes,\n },\n {\n name: 'cpp',\n aliases: ['c++', 'cxx'],\n parserLanguage: normalizeGrammar(Cpp as unknown as GrammarModule),\n functionNodeTypes: cFunctionNodes,\n decisionNodeTypes: cppDecisionNodes,\n nestingNodeTypes: cppDecisionNodes,\n ncssNodeTypes: cppNcssNodes,\n },\n].map((language) => ({\n functionNodeTypes: commonFunctionNodes,\n decisionNodeTypes: commonDecisionNodes,\n nestingNodeTypes: commonNestingNodes,\n ncssNodeTypes: jsNcssNodes,\n ...language,\n}));\n\nexport function createLanguageRegistry(\n languages: readonly LanguageDefinition[] = defaultLanguages\n): Map<LanguageName, LanguageDefinition> {\n const registry = new Map<LanguageName, LanguageDefinition>();\n\n for (const language of languages) {\n registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n registry.set(alias, language);\n }\n }\n\n return registry;\n}\n\nexport const supportedLanguages = defaultLanguages.map((language) => language.name);\n"],"mappings":"wSAaA,MAAM,EAAsB,CAC1B,WACA,uBACA,sBACA,sBACA,gBACA,0BACA,sBACA,eACA,qBACA,oBACA,cACA,iBACA,qBACA,iCACA,SACA,oBACA,oBACF,EAEM,EAAsB,CAC1B,eACA,cACA,iBACA,gBACA,mBACA,kBACA,eACA,eACA,gBACA,cACA,cACA,YACA,yBACA,qBACA,gBACA,mBACA,iBACA,iBACF,EAGM,EAAkB,CAAC,GAAG,EAAqB,kBAAmB,YAAa,oBAAoB,EAI/F,EAAqB,CAAC,GAAG,EAAqB,gBAAgB,EAC9D,EAAiB,CAAC,GAAG,EAAiB,cAAc,EAEpD,EAAoB,CACxB,GAAG,EACH,0BACA,iCACF,EACM,EAAoB,CACxB,GAAG,EACH,yBACA,+BACA,aACF,EAKM,EAAoB,CAAC,SAAU,mBAAoB,SAAU,QAAS,UAAU,EAChF,EAAoB,CACxB,KACA,QACA,SACA,QACA,QACA,MACA,OACA,YACA,SACA,cACA,cACA,kBACA,iBACA,iBACA,iBACF,EAIM,EAAiB,CAAC,sBAAuB,mBAAmB,EAC5D,EAAiB,CAAC,GAAG,EAAqB,gBAAgB,EAC1D,EAAmB,CAAC,GAAG,EAAgB,gBAAgB,EAMvD,EAAc,iqBAsCpB,EAEM,EAAkB,gfA8BxB,EAEM,EAAc,kgBAmCpB,EAEM,EAAgB,CACpB,kBACA,2BACA,mBACA,WACA,aACA,cACA,cACA,YACA,aACA,aACA,YACA,gBACA,0BACA,YACA,kBACA,mBACA,oBACA,kBACA,uBACA,cACA,WACF,EAEM,EAAgB,k1BA2CtB,EAIM,EAAgB,CAAC,QAAS,OAAQ,OAAQ,YAAa,SAAU,QAAQ,EACzE,EAAqB,CACzB,UACA,iBACA,OACA,OACA,KACA,aACA,QACA,QACF,EAEM,EAAa,CACjB,kBACA,cACA,uBACA,cACA,kBACA,oBACA,sBAEA,mBACA,iBACA,kBACA,uBACA,eACA,cACA,mBACA,iBACA,gBACA,kBACA,eACA,mBACA,kBACA,qBACA,iBACA,mBACF,EACM,EAAe,CACnB,GAAG,EACH,kBACA,uBACA,oBACA,oBACA,6BACA,qBACA,4BACA,iBACA,eACA,kBACA,sBACA,oBACF,EAEA,SAAS,EAAiB,EAAuC,CAK/D,OAJI,EAAiB,EAAQ,SAAS,EAC7B,EAAO,QAGT,CACT,CAEA,SAAS,EAAqB,EAA4C,CAExE,OAAOA,EAAS,EAClB,CAEA,SAAS,EAAiB,EAAsB,EAA6D,CAK3G,OAJI,OAAO,GAAU,WAAY,GAAkB,EAAE,KAAO,GACnD,GAGF,EAAS,EAAyC,EAC3D,CAEA,MAAa,EAAkD,CAC7D,CACE,KAAM,aACN,QAAS,CAAC,KAAM,MAAO,KAAK,EAC5B,eAAgB,EAAiB,CAAsC,CACzE,EACA,CACE,KAAM,MACN,eAAgB,EAAiB,CAAsC,CACzE,EACA,CACE,KAAM,aACN,QAAS,CAAC,IAAI,EACd,eAAgB,EAAqB,YAAY,CACnD,EACA,CACE,KAAM,MACN,eAAgB,EAAqB,KAAK,CAC5C,EACA,CACE,KAAM,SACN,QAAS,CAAC,IAAI,EACd,eAAgB,EAAiB,CAAkC,EACnE,cAAe,CACjB,EACA,CACE,KAAM,KACN,eAAgB,EAAiB,CAA8B,EAC/D,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,CACjB,EACA,CACE,KAAM,OACN,QAAS,CAAC,IAAI,EACd,eAAgB,EAAiB,CAAgC,EACjE,cAAe,EAGf,uBAAwB,CAAC,QAAS,aAAa,CACjD,EACA,CACE,KAAM,OACN,eAAgB,EAAiB,CAAgC,EACjE,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,CACjB,EACA,CACE,KAAM,OACN,QAAS,CAAC,IAAI,EACd,eAAgB,EAAiB,CAAgC,EACjE,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,EACf,uBAAwB,CAC1B,EACA,CACE,KAAM,IACN,eAAgB,EAAiB,CAA6B,EAC9D,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,CACjB,EACA,CACE,KAAM,MACN,QAAS,CAAC,MAAO,KAAK,EACtB,eAAgB,EAAiB,CAA+B,EAChE,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,CACjB,CACF,CAAC,CAAC,IAAK,IAAc,CACnB,kBAAmB,EACnB,kBAAmB,EACnB,iBAAkB,EAClB,cAAe,EACf,GAAG,CACL,EAAE,EAEF,SAAgB,EACd,EAA2C,EACJ,CACvC,IAAM,EAAW,IAAI,IAErB,IAAK,IAAM,KAAY,EAAW,CAChC,EAAS,IAAI,EAAS,KAAM,CAAQ,EACpC,IAAK,IAAM,KAAS,EAAS,SAAW,CAAC,EACvC,EAAS,IAAI,EAAO,CAAQ,CAEhC,CAEA,OAAO,CACT,CAEA,MAAa,EAAqB,EAAiB,IAAK,GAAa,EAAS,IAAI"}
1
+ {"version":3,"file":"languages.js","names":[],"sources":["../src/languages.ts"],"sourcesContent":["import type { LanguageDefinition, LanguageName } from './types.js';\n\n/**\n * The built-in languages. Grammars and per-language node-type configuration live in the Rust\n * addon (native/src/languages.rs); this list only names the languages and their aliases so the\n * CLI and API can resolve and enumerate them without crossing the N-API boundary.\n */\nexport const defaultLanguages: readonly LanguageDefinition[] = [\n { name: 'javascript', aliases: ['js', 'mjs', 'cjs'] },\n { name: 'jsx' },\n { name: 'typescript', aliases: ['ts'] },\n { name: 'tsx' },\n { name: 'python', aliases: ['py'] },\n { name: 'go' },\n { name: 'rust', aliases: ['rs'] },\n { name: 'java' },\n { name: 'ruby', aliases: ['rb'] },\n { name: 'c' },\n { name: 'cpp', aliases: ['c++', 'cxx'] },\n];\n\nexport function createLanguageRegistry(\n languages: readonly LanguageDefinition[] = defaultLanguages\n): Map<LanguageName, LanguageDefinition> {\n const registry = new Map<LanguageName, LanguageDefinition>();\n\n for (const language of languages) {\n registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n registry.set(alias, language);\n }\n }\n\n return registry;\n}\n\nexport const supportedLanguages = defaultLanguages.map((language) => language.name);\n"],"mappings":"AAOA,MAAa,EAAkD,CAC7D,CAAE,KAAM,aAAc,QAAS,CAAC,KAAM,MAAO,KAAK,CAAE,EACpD,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,aAAc,QAAS,CAAC,IAAI,CAAE,EACtC,CAAE,KAAM,KAAM,EACd,CAAE,KAAM,SAAU,QAAS,CAAC,IAAI,CAAE,EAClC,CAAE,KAAM,IAAK,EACb,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,MAAO,EACf,CAAE,KAAM,OAAQ,QAAS,CAAC,IAAI,CAAE,EAChC,CAAE,KAAM,GAAI,EACZ,CAAE,KAAM,MAAO,QAAS,CAAC,MAAO,KAAK,CAAE,CACzC,EAEA,SAAgB,EACd,EAA2C,EACJ,CACvC,IAAM,EAAW,IAAI,IAErB,IAAK,IAAM,KAAY,EAAW,CAChC,EAAS,IAAI,EAAS,KAAM,CAAQ,EACpC,IAAK,IAAM,KAAS,EAAS,SAAW,CAAC,EACvC,EAAS,IAAI,EAAO,CAAQ,CAEhC,CAEA,OAAO,CACT,CAEA,MAAa,EAAqB,EAAiB,IAAK,GAAa,EAAS,IAAI"}
package/dist/metrics.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./duplication.cjs"),n=require("./languages.cjs"),r=require("./depDegree.cjs"),i=require("./ncss.cjs"),a=require("./nativeMetrics.cjs");let o=require("tree-sitter");o=e.__toESM(o,1);const s=new Set([`&&`,`||`,`and`,`or`]),c=new Set(`+,-,*,/,%,**,=,+=,-=,*=,/=,%=,==,!=,===,!==,<,<=,>,>=,!,~,&,|,^,++,--,<<,>>,>>>,=>,**=,<<=,>>=,>>>=,&=,|=,^=,&&=,||=,??=,??,?.,?,//,//=,@,@=,:=,<-,<=>,=~,..,...,..=,&&,||,!~,&^,&^=,&.,.,->,::,->*,.*,sizeof,alignof,defined?,as,bitand,bitor,xor,compl,and_eq,or_eq,xor_eq,not_eq,and,or,not,in,is,instanceof,typeof,new,delete,return,throw,raise,yield,await,co_await,co_yield,co_return,break,continue`.split(`,`)),l=new Set(`identifier.property_identifier.field_identifier.type_identifier.constant.instance_variable.class_variable.global_variable.simple_symbol.self.this.super.primitive_type.boolean_type.void_type.auto.number.integer.float.integer_literal.float_literal.int_literal.rune_literal.imaginary_literal.number_literal.decimal_integer_literal.hex_integer_literal.octal_integer_literal.binary_integer_literal.decimal_floating_point_literal.hex_floating_point_literal.string.string_literal.raw_string_literal.string_fragment.multiline_string_fragment.string_content.raw_string_content.template_string.character_literal.char_literal.character.true.false.null.null_literal.undefined.nil.none`.split(`.`)),u=new Set([`interpreted_string_literal`,`regex`,`user_defined_literal`,`integral_type`,`floating_point_type`,`sized_type_specifier`,`placeholder_type_specifier`]);var d=class{registry=n.createLanguageRegistry();registerLanguage(e){ee(e),i.invalidateNcssSetsCache(e),this.registry.set(e.name,e);for(let t of e.aliases??[])this.registry.set(t,e)}getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,n){let r=this.registry.get(n.language);if(!r)throw Error(`Unsupported language: ${n.language}`);let o=p(n)?a.measureWithNativeBackend(e,r,n.includeSyntaxTree??!1):void 0;if(o)return y(o,n.includeSyntaxTree??!1);let s=f(e,r),c=b(s,H(s,new Set(r.functionNodeTypes)).filter(e=>!E(e)&&T(e)),r,e),l=ne(s,r),{lines:u,codeLineNumbers:d}=U(e,s);return{language:r.name,bytes:Buffer.byteLength(e),lines:u,functions:c,cognitiveComplexity:l.cognitiveComplexity,maxCognitiveComplexity:ve(c),nestingDepth:l.nestingDepth,ncssCount:i.countNcss(s,r),duplication:t.measureDuplication(s,d,n.duplication),halstead:W(s,e),syntaxTree:n.includeSyntaxTree?s.toString():void 0}}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectFunctionTokenSequences(e,t){let{language:n,root:r}=this.parse(e,t);return H(r,new Set(n.functionNodeTypes)).filter(e=>!E(e)&&T(e)).map(e=>le(e))}collectCrossFileDuplicationFileData(e,n){let{root:r}=this.parse(e,n);return{...t.collectCrossFileDuplicateCandidates(r,n.duplication),codeLineNumbers:U(e,r).codeLineNumbers}}parse(e,t){let n=this.registry.get(t.language);if(!n)throw Error(`Unsupported language: ${t.language}`);return{language:n,root:f(e,n)}}};function f(e,t){let n=new o.default;return n.setLanguage(t.parserLanguage),n.parse(e,void 0,{bufferSize:e.length+1}).rootNode}function p(e){let n=e.duplication;return(n?.minTokens??t.defaultDuplicationOptions.minTokens)===t.defaultDuplicationOptions.minTokens&&(n?.maxGapTokens??t.defaultDuplicationOptions.maxGapTokens)===t.defaultDuplicationOptions.maxGapTokens&&(n?.minSimilarityPercent??t.defaultDuplicationOptions.minSimilarityPercent)===t.defaultDuplicationOptions.minSimilarityPercent}const m=new d;function h(e,t){return m.measure(e,t)}function g(e,t){return m.collectDuplicationCandidates(e,t)}function _(e,t){return m.collectFunctionTokenSequences(e,t)}function v(e,t){return m.collectCrossFileDuplicationFileData(e,t)}function y(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:G(e.halsteadCounts),depDegree:e.depDegree})),cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:G(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function b(e,t,n,i){let a=te(e,n),{functionNodes:o}=M(n);return t.map(e=>{let t=a.get(e.id);if(!t)throw Error(`missing body metrics for function node at line ${e.startPosition.row+1}`);return{name:ue(e),nodeType:e.type,startLine:e.startPosition.row+1,startColumn:e.startPosition.column,endLine:e.endPosition.row+1,cognitiveComplexity:t.cognitiveComplexity,nestingDepth:t.nestingDepth,ncss:t.ncss,parameterCount:x(e),halstead:W(e,i),depDegree:r.measureDepDegree(e,e=>D(e,o))}})}function x(e){if(e.childForFieldName(`parameter`))return 1;let t=C(e);if(!t)return 0;if(t.type===`identifier`)return 1;let n=new Set(Z(t,`locals`).map(e=>e.id)),r=0;for(let e of t.namedChildren)e.type===`comment`||e.type===`self_parameter`||e.type===`receiver_parameter`||e.type===`block_parameter`||e.type===`positional_separator`||e.type===`keyword_separator`||n.has(e.id)||S(e)||(r+=e.type===`parameter_declaration`?Math.max(1,Z(e,`name`).length):1);let i=t.children.filter(e=>!e.isNamed&&e.text===`...`).length;return r+i}function S(e){return e.type===`parameter_declaration`&&e.childForFieldName(`declarator`)===null&&e.childForFieldName(`type`)?.text===`void`}function C(e){let t=e.childForFieldName(`parameters`);if(t)return t;if(e.type===`compact_constructor_declaration`)return e.parent?.parent?.childForFieldName(`parameters`)??void 0;let n=e.childForFieldName(`declarator`);for(;n;){let e=n.childForFieldName(`parameters`);if(e)return e;n=J(n)}return e.namedChildren.find(e=>e.type===`formal_parameters`||e.type===`parameter_list`)}const w=new Set([`function_definition`,`constructor_declaration`,`compact_constructor_declaration`,`function_signature_item`]);function T(e){return!w.has(e.type)||e.childForFieldName(`body`)!==null||e.namedChildren.some(e=>e.type===`try_statement`)}function E(e){return(e.type===`block`||e.type===`do_block`)&&e.parent?.type===`lambda`}function D(e,t){return t.has(e.type)&&!E(e)}const O=new Set([`switch_statement`,`switch_expression`,`expression_switch_statement`,`type_switch_statement`,`select_statement`,`match_expression`,`match_statement`,`case`,`case_match`]),k=new Set([`case_clause`,`switch_case`,`switch_block_statement_group`,`switch_rule`,`case_statement`,`expression_case`,`type_case`,`communication_case`,`match_arm`,`when`,`in_clause`]),A=new Set([`if_statement`,`if_expression`,`if`,`unless`]),j=new WeakMap;function ee(e){j.delete(e)}function M(e){let t=j.get(e);return t||(t={functionNodes:new Set(e.functionNodeTypes),decisionNodes:new Set(e.decisionNodeTypes),nestingNodes:new Set(e.nestingNodeTypes)},j.set(e,t)),t}function N(e,t){return{cognitiveComplexity:0,nestingSensitiveCount:0,nestingDepth:0,ncss:0,hasOwnNcssContribution:!1,entryCognitiveNesting:e,entryStructuralNesting:t}}function te(e,t){let{functionNodes:n,decisionNodes:r,nestingNodes:a}=M(t),{countable:o,containers:s}=i.getNcssSets(t),c=new Map,l=[N(0,0)];function u(e,t,d,f,p,m){let h=e.type===`class_body`&&f;h&&(p=!0,d+=1);let g=D(e,n);g&&(f&&!m&&(d+=1),f=!0);let _=l.at(-1),v=t+d-_.entryCognitiveNesting,y=!p&&!g,b=e.isNamed&&r.has(e.type)&&!B(e),x=e.isNamed&&k.has(e.type),S=e.isNamed&&(a.has(e.type)||e.type===`else`&&(e.parent?.type===`case`||e.parent?.type===`case_match`)),C=b&&z(e);b&&!x&&(C?_.cognitiveComplexity+=1:(_.cognitiveComplexity+=1+v,_.nestingSensitiveCount+=1)),e.isNamed&&O.has(e.type)&&(_.cognitiveComplexity+=1+v,_.nestingSensitiveCount+=1),_.cognitiveComplexity+=P(e),F(e)&&(_.cognitiveComplexity+=1),V(e)&&I(e)&&(_.cognitiveComplexity+=1),R(e)&&(_.cognitiveComplexity+=1);let w=S&&!C?t+1:t;y&&(_.nestingDepth=Math.max(_.nestingDepth,w-_.entryStructuralNesting)),g&&l.push(N(w+d,w));let T=i.ncssContribution(e,o,s),E=l.at(-1);E.ncss+=T,g&&T>0&&(E.hasOwnNcssContribution=!0);for(let t of e.children)u(t,w,d,f,!g&&p,h);if(g){let t=l.pop();c.set(e.id,{cognitiveComplexity:t.cognitiveComplexity,nestingDepth:t.nestingDepth,ncss:t.ncss+ +!t.hasOwnNcssContribution});let n=l.at(-1);n.cognitiveComplexity+=t.cognitiveComplexity+t.nestingSensitiveCount*(t.entryCognitiveNesting-n.entryCognitiveNesting),n.nestingSensitiveCount+=t.nestingSensitiveCount,n.ncss+=t.ncss}}return u(e,0,0,!1,!1,!1),c}function ne(e,t){let n=0,r=0,{functionNodes:i,decisionNodes:a,nestingNodes:o}=M(t);function s(e,t,c,l,u){let d=e.type===`class_body`&&l;d&&(c+=1),D(e,i)&&(l&&!u&&(c+=1),l=!0);let f=t+c,p=e.isNamed&&a.has(e.type)&&!B(e),m=e.isNamed&&k.has(e.type),h=e.isNamed&&(o.has(e.type)||e.type===`else`&&(e.parent?.type===`case`||e.parent?.type===`case_match`)),g=p&&z(e);p&&!m&&(n+=g?1:1+f),e.isNamed&&O.has(e.type)&&(n+=1+f),n+=P(e),F(e)&&(n+=1),V(e)&&I(e)&&(n+=1),R(e)&&(n+=1);let _=h&&!g?t+1:t;r=Math.max(r,_);for(let t of e.children)s(t,_,c,l,d)}for(let t of e.children)s(t,0,0,!1,!1);return{cognitiveComplexity:n,nestingDepth:r}}function P(e){if(!e.isNamed)return 0;if(e.type===`else`)return e.parent?.type===`case`||e.parent?.type===`case_match`?0:1;if(e.type===`else_clause`)return+!e.namedChildren.some(e=>A.has(e.type));if(e.type!==`if_statement`&&e.type!==`if_expression`)return 0;let t=0;for(let n=0;n<e.childCount;n+=1){let r=e.child(n);r&&e.fieldNameForChild(n)===`alternative`&&r.type!==`else_clause`&&r.type!==`elif_clause`&&!A.has(r.type)&&(t+=1)}return t}function F(e){return e.isNamed?e.type===`goto_statement`?!0:e.type===`break_expression`||e.type===`continue_expression`?e.namedChildren.some(e=>e.type===`label`||e.type===`loop_label`):(e.type===`break_statement`||e.type===`continue_statement`)&&e.namedChildren.some(e=>!i.commentNodeTypes.has(e.type)):!1}const re=new Set([`parenthesized_expression`,`parenthesized_statements`]);function I(e){let t=e.parent;if(!t)return!0;let n=t.parent;for(;n&&re.has(n.type);)n=n.parent;return!n||n.type!==t.type||L(ie(n))!==L(e.text)}function L(e){return e===`and`?`&&`:e===`or`?`||`:e}function ie(e){let t=e.childForFieldName(`operator`);return t?t.text:e.children.find(e=>!e.isNamed&&s.has(e.text))?.text}function R(e){return e.isNamed?e.type===`guard`||e.type===`if_guard`||e.type===`unless_guard`||e.type===`if_clause`||e.type===`match_pattern`&&e.children.some(e=>!e.isNamed&&e.type===`if`):!1}function z(e){if(e.type===`elsif`||e.type===`elif_clause`)return!0;if(e.type!==`if_statement`&&e.type!==`if_expression`&&e.type!==`if`)return!1;let t=e.parent;return t?t.type===`else_clause`||t.childForFieldName(`alternative`)?.id===e.id:!1}function B(e){if(e.type===`case_statement`)return e.childForFieldName(`value`)===null;if(e.type===`switch_block_statement_group`||e.type===`switch_rule`){let t=e.namedChildren.find(e=>e.type===`switch_label`);return t!==void 0&&t.namedChildCount===0}if(e.type===`case_clause`||e.type===`match_arm`){let t=e.namedChildren.find(e=>e.type===`case_pattern`||e.type===`match_pattern`);if(!t)return!1;if(t.child(0)?.type===`_`&&(t.childCount===1||t.child(1)?.type===`if`))return!0;let n=t.namedChildCount===1?t.namedChild(0):void 0;return e.type===`case_clause`&&n?.type===`dotted_name`&&n.namedChildCount===1&&n.namedChild(0)?.type===`identifier`}return e.type===`in_clause`&&e.namedChild(0)?.type===`identifier`}const ae=new Set([`binary_expression`,`binary`,`boolean_operator`]);function V(e){if(e.isNamed||!s.has(e.text))return!1;let t=e.parent;return t!==null&&ae.has(t.type)}function H(e,t){let n=[];function r(e){t.has(e.type)&&n.push(e);for(let t of e.namedChildren)r(t)}return r(e),n}function U(e,t){let n=e.length===0?[]:e.split(/\r\n|\n|\r/),r=new Map;for(let e of oe(t)){let t=r.get(e.line)??[];t.push(e),r.set(e.line,t)}let i=0,a=0,o=new Set;for(let[e,t]of n.entries()){if(t.trim()===``){i+=1;continue}se(t,r.get(e)??[])?a+=1:o.add(e+1)}return{lines:{total:n.length,code:o.size,comment:a,blank:i},codeLineNumbers:o}}function oe(e){let t=[];function n(e){if(e.type===`comment`||e.type===`line_comment`||e.type===`block_comment`)for(let n=e.startPosition.row;n<=e.endPosition.row;n+=1)t.push({line:n,startColumn:n===e.startPosition.row?e.startPosition.column:0,endColumn:n===e.endPosition.row?e.endPosition.column:1/0});for(let t of e.namedChildren)n(t)}return n(e),t}function se(e,t){if(t.length===0)return!1;for(let n=0;n<e.length;n+=1)if(!/\s/u.test(e[n]??` `)&&!t.some(e=>e.startColumn<=n&&n<e.endColumn))return!1;return!0}function W(e,t){let n=new Map,r=new Map;function i(e){if(e.type!==`comment`&&e.type!==`line_comment`&&e.type!==`block_comment`){if(u.has(e.type)){Q(r,t.slice(e.startIndex,e.endIndex));return}if(e.childCount===0){let i=t.slice(e.startIndex,e.endIndex);l.has(e.type)?Q(r,i):(c.has(i)||c.has(e.type))&&_e(e,i)&&Q(n,i||e.type);return}for(let t of e.children)i(t)}}return i(e),G({distinctOperators:n.size,distinctOperands:r.size,totalOperators:$(n.values()),totalOperands:$(r.values())})}function G(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,effort:(n===0?0:t/2*(i/n))*s}}const ce=new Set([`identifier`,`property_identifier`,`field_identifier`,`type_identifier`,`constant`,`instance_variable`,`class_variable`,`global_variable`]);function le(e){let t=[];return K(e,t,new Map),Int32Array.from(t)}function K(e,n,r){if(e.type!==`comment`&&e.type!==`line_comment`&&e.type!==`block_comment`){if(u.has(e.type)){n.push(t.hashText(e.type));return}if(e.childCount>0){for(let t of e.children)K(t,n,r);return}if(ce.has(e.type)){let i=r.get(e.text);i===void 0&&(i=r.size,r.set(e.text,i)),n.push(t.hashText(`id${i}`));return}n.push(t.hashText(l.has(e.type)?e.type:e.text))}}function ue(e){let t=me(e);if(t)return t;let n=e.childForFieldName(`name`);if(n)return n.text;let r=de(e);if(r)return r;let i=e.parent;if(i){if(e.type===`closure_expression`&&i.type===`let_declaration`){let e=i.childForFieldName(`pattern`);return e?.type===`identifier`?e.text:void 0}return e.type===`lambda_expression`&&i.type===`init_declarator`?q(i.childForFieldName(`declarator`)):e.type===`func_literal`&&i.type===`expression_list`?pe(e,i):e.type===`lambda`&&i.type===`assignment`?Y(i):(e.type===`block`||e.type===`do_block`)&&fe(i)?i.parent?.type===`assignment`?Y(i.parent):void 0:i.childForFieldName(`name`)?.text}}function de(e){return q(e.childForFieldName(`declarator`))}function q(e){let t=e;for(;t;)switch(t.type){case`identifier`:case`field_identifier`:case`type_identifier`:case`destructor_name`:case`operator_name`:return t.text;case`operator_cast`:return`operator ${t.childForFieldName(`type`)?.text??``}`.trimEnd();case`template_function`:case`qualified_identifier`:t=t.childForFieldName(`name`);break;default:t=J(t)}}function J(e){let t=e.childForFieldName(`declarator`);if(t)return t;if(e.type===`reference_declarator`||e.type===`parenthesized_declarator`)return e.namedChild(0)??void 0}function Y(e){let t=e.childForFieldName(`left`);return t?.type===`identifier`||t?.type===`constant`?t.text:void 0}function fe(e){if(e.type!==`call`||e.childForFieldName(`receiver`))return!1;let t=e.childForFieldName(`method`);return t?.type===`identifier`&&(t.text===`lambda`||t.text===`proc`)}function pe(e,t){let n=t.parent,r=t.namedChildren.filter(e=>e.type!==`comment`).findIndex(t=>t.id===e.id);if(!(!n||r===-1)){if(n.type===`short_var_declaration`){let e=n.childForFieldName(`left`)?.namedChildren.filter(e=>e.type!==`comment`);return X(e?.[r])}if(n.type===`var_spec`){let e=Z(n,`name`)[r];return X(e)}}}function X(e){return e?.type===`identifier`&&e.text!==`_`?e.text:void 0}function me(e){let t=e;for(;t;){let e=t.parent,n=e?.parent;if(e?.type!==`arguments`||n?.type!==`call_expression`||!he(n))return;let r=n.parent;if(r?.type===`variable_declarator`)return r.childForFieldName(`name`)?.text;t=n}}function he(e){let t=e.childForFieldName(`function`)??e.namedChild(0);return t?.text===`memo`||t?.text===`React.memo`||t?.text===`forwardRef`||t?.text===`React.forwardRef`}const ge=new Set([`ternary_expression`,`conditional_expression`,`conditional`,`try_expression`,`conditional_type`]);function _e(e,t){if(t===`@`){let t=e.parent?.type;return t===`binary_operator`||t===`augmented_assignment`}if(t!==`?`)return!0;let n=e.parent?.type;return n!==void 0&&ge.has(n)}function Z(e,t){let n=[];for(let r=0;r<e.childCount;r+=1){let i=e.child(r);i&&e.fieldNameForChild(r)===t&&n.push(i)}return n}function Q(e,t){e.set(t,(e.get(t)??0)+1)}function ve(e){return e.length===0?0:Math.max(...e.map(e=>e.cognitiveComplexity))}function $(e){let t=0;for(let n of e)t+=n;return t}exports.TreeMeasurer=d,exports.collectCrossFileDuplicationFileData=v,exports.collectDuplicationCandidates=g,exports.collectFunctionTokenSequences=_,exports.defaultMeasurer=m,exports.measureCode=h;
1
+ "use strict";const e=require("./languages.cjs"),t=require("./nativeMetrics.cjs");var n=class{registry=e.createLanguageRegistry();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,n){let i=this.resolveLanguage(n.language),a=n.includeSyntaxTree??!1;return r(t.measureCodeNative(e,i.name,a,n.duplication),a)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language),i=t.collectCrossFileDataNative(e,r.name,n.duplication?.minTokens);return{candidates:i.candidates,tokens:i.tokens,containerStatements:i.containerStatements,codeLineNumbers:new Set(i.codeLineNumbers)}}collectFunctionTokenSequences(e,n){let r=this.resolveLanguage(n.language);return t.collectFunctionTokenSequencesNative(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function r(e,t){return{language:e.language,bytes:e.bytes,lines:e.lines,functions:e.functions.map(e=>({name:e.name,nodeType:e.nodeType,startLine:e.startLine,startColumn:e.startColumn,endLine:e.endLine,cognitiveComplexity:e.cognitiveComplexity,nestingDepth:e.nestingDepth,ncss:e.ncss,parameterCount:e.parameterCount,halstead:i(e.halsteadCounts),depDegree:e.depDegree})),cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:i(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function i(e){let{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i}=e,a=t+n,o=r+i,s=a===0?0:o*Math.log2(a);return{distinctOperators:t,distinctOperands:n,totalOperators:r,totalOperands:i,vocabulary:a,length:o,volume:s,effort:(n===0?0:t/2*(i/n))*s}}const a=new n;function o(e,t){return a.measure(e,t)}function s(e,t){return a.collectDuplicationCandidates(e,t)}function c(e,t){return a.collectFunctionTokenSequences(e,t)}function l(e,t){return a.collectCrossFileDuplicationFileData(e,t)}exports.TreeMeasurer=n,exports.collectCrossFileDuplicationFileData=l,exports.collectDuplicationCandidates=s,exports.collectFunctionTokenSequences=c,exports.defaultMeasurer=a,exports.measureCode=o;
2
2
  //# sourceMappingURL=metrics.cjs.map