code-gauge 3.0.0 → 4.0.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 (79) hide show
  1. package/README.md +83 -21
  2. package/dist/cli.cjs +3 -3
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.js +3 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/cliConfig.cjs +1 -1
  7. package/dist/cliConfig.cjs.map +1 -1
  8. package/dist/cliConfig.d.ts +11 -0
  9. package/dist/cliConfig.js +1 -1
  10. package/dist/cliConfig.js.map +1 -1
  11. package/dist/crossFileDuplication.cjs +1 -1
  12. package/dist/crossFileDuplication.cjs.map +1 -1
  13. package/dist/crossFileDuplication.js +1 -1
  14. package/dist/crossFileDuplication.js.map +1 -1
  15. package/dist/diffCommand.cjs +5 -0
  16. package/dist/diffCommand.cjs.map +1 -0
  17. package/dist/diffCommand.d.ts +17 -0
  18. package/dist/diffCommand.js +5 -0
  19. package/dist/diffCommand.js.map +1 -0
  20. package/dist/duplication.cjs +1 -1
  21. package/dist/duplication.cjs.map +1 -1
  22. package/dist/duplication.d.ts +20 -24
  23. package/dist/duplication.js +1 -1
  24. package/dist/duplication.js.map +1 -1
  25. package/dist/git.cjs +2 -0
  26. package/dist/git.cjs.map +1 -0
  27. package/dist/git.d.ts +27 -0
  28. package/dist/git.js +2 -0
  29. package/dist/git.js.map +1 -0
  30. package/dist/index.cjs +1 -1
  31. package/dist/index.d.ts +3 -2
  32. package/dist/index.js +1 -1
  33. package/dist/languages.cjs +1 -1
  34. package/dist/languages.cjs.map +1 -1
  35. package/dist/languages.d.ts +5 -0
  36. package/dist/languages.js +1 -1
  37. package/dist/languages.js.map +1 -1
  38. package/dist/metrics.cjs +1 -1
  39. package/dist/metrics.cjs.map +1 -1
  40. package/dist/metrics.d.ts +18 -5
  41. package/dist/metrics.js +1 -1
  42. package/dist/metrics.js.map +1 -1
  43. package/dist/nativeMetrics.cjs +3 -1
  44. package/dist/nativeMetrics.cjs.map +1 -1
  45. package/dist/nativeMetrics.d.ts +29 -10
  46. package/dist/nativeMetrics.js +3 -1
  47. package/dist/nativeMetrics.js.map +1 -1
  48. package/dist/regressionGate.cjs +2 -0
  49. package/dist/regressionGate.cjs.map +1 -0
  50. package/dist/regressionGate.d.ts +106 -0
  51. package/dist/regressionGate.js +2 -0
  52. package/dist/regressionGate.js.map +1 -0
  53. package/dist/scan.cjs +2 -0
  54. package/dist/scan.cjs.map +1 -0
  55. package/dist/scan.d.ts +55 -0
  56. package/dist/scan.js +2 -0
  57. package/dist/scan.js.map +1 -0
  58. package/dist/types.d.ts +18 -13
  59. package/native/Cargo.lock +523 -0
  60. package/native/Cargo.toml +45 -0
  61. package/native/build.rs +3 -0
  62. package/native/src/complexity.rs +627 -0
  63. package/native/src/dep_degree.rs +253 -0
  64. package/native/src/duplication.rs +2007 -0
  65. package/native/src/functions.rs +345 -0
  66. package/native/src/languages.rs +647 -0
  67. package/native/src/lib.rs +101 -0
  68. package/native/src/measure.rs +590 -0
  69. package/native/src/ncss.rs +263 -0
  70. package/native/src/types.rs +135 -0
  71. package/native/src/util.rs +139 -0
  72. package/package.json +16 -19
  73. package/scripts/buildNative.mjs +25 -0
  74. package/scripts/installNative.mjs +96 -0
  75. package/dist/ncss.cjs +0 -2
  76. package/dist/ncss.cjs.map +0 -1
  77. package/dist/ncss.d.ts +0 -17
  78. package/dist/ncss.js +0 -2
  79. package/dist/ncss.js.map +0 -1
@@ -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("./ncss.cjs"),i=require("./nativeMetrics.cjs");let a=require("tree-sitter");a=e.__toESM(a,1);const o=new Set([`&&`,`||`,`and`,`or`]),s=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(`,`)),c=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(`.`)),l=new Set([`interpreted_string_literal`,`regex`,`user_defined_literal`,`integral_type`,`floating_point_type`,`sized_type_specifier`,`placeholder_type_specifier`]);var u=class{registry=n.createLanguageRegistry();registerLanguage(e){ee(e),r.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 a=this.registry.get(n.language);if(!a)throw Error(`Unsupported language: ${n.language}`);let o=f(n)?i.measureWithNativeBackend(e,a,n.includeSyntaxTree??!1):void 0;if(o)return _(o,n.includeSyntaxTree??!1);let s=d(e,a),c=v(s,ae(s,new Set(a.functionNodeTypes)).filter(e=>!w(e)&&C(e)),a),l=te(s,a),{lines:u,codeLineNumbers:p}=V(e,s);return{language:a.name,bytes:Buffer.byteLength(e),lines:u,functions:c,cognitiveComplexity:l.cognitiveComplexity,maxCognitiveComplexity:pe(c),nestingDepth:l.nestingDepth,ncssCount:r.countNcss(s,a),duplication:t.measureDuplication(s,p,n.duplication),halstead:U(s,e),syntaxTree:n.includeSyntaxTree?s.toString():void 0}}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.registry.get(n.language);if(!r)throw Error(`Unsupported language: ${n.language}`);let i=d(e,r);return{...t.collectCrossFileDuplicateCandidates(i,n.duplication),codeLineNumbers:V(e,i).codeLineNumbers}}};function d(e,t){let n=new a.default;return n.setLanguage(t.parserLanguage),n.parse(e,void 0,{bufferSize:e.length+1}).rootNode}function f(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 p=new u;function m(e,t){return p.measure(e,t)}function h(e,t){return p.collectDuplicationCandidates(e,t)}function g(e,t){return p.collectCrossFileDuplicationFileData(e,t)}function _(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})),cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:W(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function v(e,t,n){let r=M(e,n);return t.map(e=>{let t=r.get(e.id);if(!t)throw Error(`missing body metrics for function node at line ${e.startPosition.row+1}`);return{name:G(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:y(e)}})}function y(e){if(e.childForFieldName(`parameter`))return 1;let t=x(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)||b(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 b(e){return e.type===`parameter_declaration`&&e.childForFieldName(`declarator`)===null&&e.childForFieldName(`type`)?.text===`void`}function x(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 S=new Set([`function_definition`,`constructor_declaration`,`compact_constructor_declaration`,`function_signature_item`]);function C(e){return!S.has(e.type)||e.childForFieldName(`body`)!==null||e.namedChildren.some(e=>e.type===`try_statement`)}function w(e){return(e.type===`block`||e.type===`do_block`)&&e.parent?.type===`lambda`}function T(e,t){return t.has(e.type)&&!w(e)}const E=new Set([`switch_statement`,`switch_expression`,`expression_switch_statement`,`type_switch_statement`,`select_statement`,`match_expression`,`match_statement`,`case`,`case_match`]),D=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`]),O=new Set([`if_statement`,`if_expression`,`if`,`unless`]),k=new WeakMap;function ee(e){k.delete(e)}function A(e){let t=k.get(e);return t||(t={functionNodes:new Set(e.functionNodeTypes),decisionNodes:new Set(e.decisionNodeTypes),nestingNodes:new Set(e.nestingNodeTypes)},k.set(e,t)),t}function j(e,t){return{cognitiveComplexity:0,nestingSensitiveCount:0,nestingDepth:0,ncss:0,hasOwnNcssContribution:!1,entryCognitiveNesting:e,entryStructuralNesting:t}}function M(e,t){let{functionNodes:n,decisionNodes:i,nestingNodes:a}=A(t),{countable:o,containers:s}=r.getNcssSets(t),c=new Map,l=[j(0,0)];function u(e,t,d,f,p,m){let h=e.type===`class_body`&&f;h&&(p=!0,d+=1);let g=T(e,n);g&&(f&&!m&&(d+=1),f=!0);let _=l.at(-1),v=t+d-_.entryCognitiveNesting,y=!p&&!g,b=e.isNamed&&i.has(e.type)&&!z(e),x=e.isNamed&&D.has(e.type),S=e.isNamed&&(a.has(e.type)||e.type===`else`&&(e.parent?.type===`case`||e.parent?.type===`case_match`)),C=b&&R(e);b&&!x&&(C?_.cognitiveComplexity+=1:(_.cognitiveComplexity+=1+v,_.nestingSensitiveCount+=1)),e.isNamed&&E.has(e.type)&&(_.cognitiveComplexity+=1+v,_.nestingSensitiveCount+=1),_.cognitiveComplexity+=N(e),P(e)&&(_.cognitiveComplexity+=1),B(e)&&F(e)&&(_.cognitiveComplexity+=1),L(e)&&(_.cognitiveComplexity+=1);let w=S&&!C?t+1:t;y&&(_.nestingDepth=Math.max(_.nestingDepth,w-_.entryStructuralNesting)),g&&l.push(j(w+d,w));let O=r.ncssContribution(e,o,s),k=l.at(-1);k.ncss+=O,g&&O>0&&(k.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 te(e,t){let n=0,r=0,{functionNodes:i,decisionNodes:a,nestingNodes:o}=A(t);function s(e,t,c,l,u){let d=e.type===`class_body`&&l;d&&(c+=1),T(e,i)&&(l&&!u&&(c+=1),l=!0);let f=t+c,p=e.isNamed&&a.has(e.type)&&!z(e),m=e.isNamed&&D.has(e.type),h=e.isNamed&&(o.has(e.type)||e.type===`else`&&(e.parent?.type===`case`||e.parent?.type===`case_match`)),g=p&&R(e);p&&!m&&(n+=g?1:1+f),e.isNamed&&E.has(e.type)&&(n+=1+f),n+=N(e),P(e)&&(n+=1),B(e)&&F(e)&&(n+=1),L(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 N(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=>O.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`&&!O.has(r.type)&&(t+=1)}return t}function P(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=>!r.commentNodeTypes.has(e.type)):!1}const ne=new Set([`parenthesized_expression`,`parenthesized_statements`]);function F(e){let t=e.parent;if(!t)return!0;let n=t.parent;for(;n&&ne.has(n.type);)n=n.parent;return!n||n.type!==t.type||I(re(n))!==I(e.text)}function I(e){return e===`and`?`&&`:e===`or`?`||`:e}function re(e){let t=e.childForFieldName(`operator`);return t?t.text:e.children.find(e=>!e.isNamed&&o.has(e.text))?.text}function L(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 R(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 z(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 ie=new Set([`binary_expression`,`binary`,`boolean_operator`]);function B(e){if(e.isNamed||!o.has(e.text))return!1;let t=e.parent;return t!==null&&ie.has(t.type)}function ae(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 V(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}H(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 H(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 U(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(l.has(e.type)){Q(r,t.slice(e.startIndex,e.endIndex));return}if(e.childCount===0){let i=t.slice(e.startIndex,e.endIndex);c.has(e.type)?Q(r,i):(s.has(i)||s.has(e.type))&&fe(e,i)&&Q(n,i||e.type);return}for(let t of e.children)i(t)}}return i(e),W({distinctOperators:n.size,distinctOperands:r.size,totalOperators:$(n.values()),totalOperands:$(r.values())})}function W(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}}function G(e){let t=le(e);if(t)return t;let n=e.childForFieldName(`name`);if(n)return n.text;let r=K(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`?ce(e,i):e.type===`lambda`&&i.type===`assignment`?Y(i):(e.type===`block`||e.type===`do_block`)&&se(i)?i.parent?.type===`assignment`?Y(i.parent):void 0:i.childForFieldName(`name`)?.text}}function K(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 se(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 ce(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 le(e){let t=e;for(;t;){let e=t.parent,n=e?.parent;if(e?.type!==`arguments`||n?.type!==`call_expression`||!ue(n))return;let r=n.parent;if(r?.type===`variable_declarator`)return r.childForFieldName(`name`)?.text;t=n}}function ue(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 de=new Set([`ternary_expression`,`conditional_expression`,`conditional`,`try_expression`,`conditional_type`]);function fe(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&&de.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 pe(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=u,exports.collectCrossFileDuplicationFileData=g,exports.collectDuplicationCandidates=h,exports.defaultMeasurer=p,exports.measureCode=m;
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
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.cjs","names":["createLanguageRegistry","measureWithNativeBackend","countNcss","measureDuplication","collectCrossFileDuplicateCandidates","Parser","defaultDuplicationOptions","getNcssSets","ncssContribution","commentNodeTypes"],"sources":["../src/metrics.ts"],"sourcesContent":["import Parser from 'tree-sitter';\nimport {\n collectCrossFileDuplicateCandidates,\n defaultDuplicationOptions,\n measureDuplication,\n type CrossFileDuplicateCandidate,\n type CrossFileDuplicationFileData,\n} from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport { commentNodeTypes, countNcss, getNcssSets, invalidateNcssSetsCache, ncssContribution } from './ncss.js';\nimport { measureWithNativeBackend, type NativeHalsteadCounts, type NativeMetricsPayload } from './nativeMetrics.js';\nimport type {\n CodeMetrics,\n FunctionMetrics,\n HalsteadMetrics,\n LanguageDefinition,\n LanguageName,\n MeasureOptions,\n} from './types.js';\n\nconst booleanOperators = new Set(['&&', '||', 'and', 'or']);\nconst operatorTexts = new Set([\n '+',\n '-',\n '*',\n '/',\n '%',\n '**',\n '=',\n '+=',\n '-=',\n '*=',\n '/=',\n '%=',\n '==',\n '!=',\n '===',\n '!==',\n '<',\n '<=',\n '>',\n '>=',\n '!',\n '~',\n '&',\n '|',\n '^',\n '++',\n '--',\n '<<',\n '>>',\n '>>>',\n '=>',\n '**=',\n '<<=',\n '>>=',\n '>>>=',\n '&=',\n '|=',\n '^=',\n '&&=',\n '||=',\n '??=',\n '??',\n '?.',\n '?',\n '//',\n '//=',\n '@',\n '@=',\n ':=',\n '<-',\n '<=>',\n '=~',\n '..',\n '...',\n '..=',\n '&&',\n '||',\n '!~',\n '&^',\n '&^=',\n '&.',\n // Member access/qualification are classical Halstead operators (floats and range/spread tokens\n // are distinct leaves, so `.` cannot collide with them). `->` also captures Python/Rust\n // return-type arrows, consistent with the counted `=>`.\n '.',\n '->',\n '::',\n '->*',\n '.*',\n 'sizeof',\n 'alignof',\n 'defined?',\n 'as',\n // C++ alternative operator tokens parse as anonymous leaves like their symbolic forms.\n 'bitand',\n 'bitor',\n 'xor',\n 'compl',\n 'and_eq',\n 'or_eq',\n 'xor_eq',\n 'not_eq',\n 'and',\n 'or',\n 'not',\n 'in',\n 'is',\n 'instanceof',\n 'typeof',\n 'new',\n 'delete',\n 'return',\n 'throw',\n 'raise',\n 'yield',\n 'await',\n 'co_await',\n 'co_yield',\n 'co_return',\n 'break',\n 'continue',\n]);\n\nconst operandNodeTypes = new Set([\n 'identifier',\n 'property_identifier',\n 'field_identifier',\n 'type_identifier',\n 'constant',\n 'instance_variable',\n 'class_variable',\n 'global_variable',\n 'simple_symbol',\n 'self',\n 'this',\n 'super',\n // C/C++/Rust built-in types are leaves of their own node type, unlike Go's `type_identifier`.\n 'primitive_type',\n 'boolean_type',\n 'void_type',\n 'auto',\n 'number',\n 'integer',\n 'float',\n 'integer_literal',\n 'float_literal',\n 'int_literal',\n 'rune_literal',\n 'imaginary_literal',\n 'number_literal',\n 'decimal_integer_literal',\n 'hex_integer_literal',\n 'octal_integer_literal',\n 'binary_integer_literal',\n 'decimal_floating_point_literal',\n 'hex_floating_point_literal',\n 'string',\n 'string_literal',\n // Go raw strings are leaves with no content child, unlike Rust/C++ `raw_string_literal`s.\n 'raw_string_literal',\n 'string_fragment',\n 'multiline_string_fragment',\n 'string_content',\n 'raw_string_content',\n 'template_string',\n 'character_literal',\n 'char_literal',\n 'character',\n 'true',\n 'false',\n 'null',\n 'null_literal',\n 'undefined',\n 'nil',\n 'none',\n]);\n\n/**\n * Non-leaf literals counted as one Halstead operand without descending: Go interpreted strings\n * have no content leaf at all, and regex literals would otherwise count their `/` delimiters as\n * division operators. Interpolated regex contents are deliberately swallowed by the atom.\n */\n// C++ user-defined literals (`42_km`) are atomic too, keeping their suffix in the operand identity,\n// and multi-token built-in types (Java `int`, C `unsigned long`) wrap anonymous keyword leaves so\n// they count as one operand.\nconst atomicOperandNodeTypes = new Set([\n 'interpreted_string_literal',\n 'regex',\n 'user_defined_literal',\n 'integral_type',\n 'floating_point_type',\n 'sized_type_specifier',\n 'placeholder_type_specifier',\n]);\n\ninterface ComplexityResult {\n cognitiveComplexity: number;\n nestingDepth: number;\n}\n\ninterface CommentSpan {\n line: number;\n startColumn: number;\n endColumn: number;\n}\n\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n registerLanguage(language: LanguageDefinition): void {\n // Re-registering may carry mutated node-type arrays; drop derived caches so they rebuild.\n invalidateComplexityNodeSetsCache(language);\n invalidateNcssSetsCache(language);\n this.registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n this.registry.set(alias, language);\n }\n }\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.registry.get(options.language);\n if (!language) {\n throw new Error(`Unsupported language: ${options.language}`);\n }\n\n // The native backend implements only the default duplication settings, so custom settings\n // measure through the TypeScript backend instead of silently ignoring them.\n const nativePayload = usesDefaultDuplicationOptions(options)\n ? measureWithNativeBackend(code, language, options.includeSyntaxTree ?? false)\n : undefined;\n if (nativePayload) {\n return assembleNativeMetrics(nativePayload, options.includeSyntaxTree ?? false);\n }\n\n const root = parseRoot(code, language);\n const functions = collectNodes(root, new Set(language.functionNodeTypes)).filter(\n (node) => !isLambdaBodyBlock(node) && isImplementedFunction(node)\n );\n const functionMetrics = collectFunctionMetrics(root, functions, language);\n const globalComplexity = measureComplexity(root, language);\n const { lines, codeLineNumbers } = classifyLines(code, root);\n\n return {\n language: language.name,\n bytes: Buffer.byteLength(code),\n lines,\n functions: functionMetrics,\n cognitiveComplexity: globalComplexity.cognitiveComplexity,\n maxCognitiveComplexity: maxCognitiveComplexity(functionMetrics),\n nestingDepth: globalComplexity.nestingDepth,\n ncssCount: countNcss(root, language),\n duplication: measureDuplication(root, codeLineNumbers, options.duplication),\n halstead: measureHalstead(root, code),\n syntaxTree: options.includeSyntaxTree ? root.toString() : undefined,\n };\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication. Always measured by the\n * TypeScript backend.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.registry.get(options.language);\n if (!language) {\n throw new Error(`Unsupported language: ${options.language}`);\n }\n const root = parseRoot(code, language);\n return {\n ...collectCrossFileDuplicateCandidates(root, options.duplication),\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: classifyLines(code, root).codeLineNumbers,\n };\n }\n}\n\nfunction parseRoot(code: string, language: LanguageDefinition): Parser.SyntaxNode {\n const parser = new Parser();\n parser.setLanguage(language.parserLanguage);\n return parser.parse(code, undefined, { bufferSize: code.length + 1 }).rootNode;\n}\n\nfunction usesDefaultDuplicationOptions(options: MeasureOptions): boolean {\n const duplication = options.duplication;\n return (\n (duplication?.minTokens ?? defaultDuplicationOptions.minTokens) === defaultDuplicationOptions.minTokens &&\n (duplication?.maxGapTokens ?? defaultDuplicationOptions.maxGapTokens) === defaultDuplicationOptions.maxGapTokens &&\n (duplication?.minSimilarityPercent ?? defaultDuplicationOptions.minSimilarityPercent) ===\n defaultDuplicationOptions.minSimilarityPercent\n );\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has exactly the shape the TypeScript backend\n * produces, including explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n })),\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction collectFunctionMetrics(\n root: Parser.SyntaxNode,\n functions: Parser.SyntaxNode[],\n language: LanguageDefinition\n): FunctionMetrics[] {\n const bodyMetricsByNodeId = measureFunctionBodyMetrics(root, language);\n return functions.map((node) => {\n const bodyMetrics = bodyMetricsByNodeId.get(node.id);\n if (!bodyMetrics) {\n throw new Error(`missing body metrics for function node at line ${node.startPosition.row + 1}`);\n }\n return {\n name: findFunctionName(node),\n nodeType: node.type,\n startLine: node.startPosition.row + 1,\n startColumn: node.startPosition.column,\n endLine: node.endPosition.row + 1,\n cognitiveComplexity: bodyMetrics.cognitiveComplexity,\n nestingDepth: bodyMetrics.nestingDepth,\n ncss: bodyMetrics.ncss,\n parameterCount: countParameters(node),\n };\n });\n}\n\n/** Counts declared parameters of a function/method, ignoring punctuation and comments. */\nfunction countParameters(node: Parser.SyntaxNode): number {\n // An unparenthesized arrow-function parameter (`x => x + 1`) is a bare `parameter` field.\n if (node.childForFieldName('parameter')) {\n return 1;\n }\n\n const parametersNode = findParametersNode(node);\n if (!parametersNode) {\n return 0;\n }\n\n // A Java bare lambda parameter (`x -> x + 1`) puts a lone identifier in the `parameters` field.\n if (parametersNode.type === 'identifier') {\n return 1;\n }\n\n // Ruby block-locals after `;` (`{ |x; memo| ... }`) occupy `locals` fields and receive no arguments.\n const blockLocalIds = new Set(findChildrenByFieldName(parametersNode, 'locals').map((child) => child.id));\n // Rust's `self` and Java's explicit receiver (`void f(X this)`) are not declared parameters, and\n // C/C++ `f(void)` declares none. A Ruby block parameter (`&blk`) binds the block, which call\n // sites pass outside the argument list, so it counts toward no arity.\n let count = 0;\n for (const child of parametersNode.namedChildren) {\n if (\n child.type === 'comment' ||\n child.type === 'self_parameter' ||\n child.type === 'receiver_parameter' ||\n child.type === 'block_parameter' ||\n // Python's PEP 570/3102 markers (`/`, `*`) separate parameter kinds but bind nothing.\n child.type === 'positional_separator' ||\n child.type === 'keyword_separator' ||\n blockLocalIds.has(child.id) ||\n isVoidParameter(child)\n ) {\n continue;\n }\n // Go declares several names per declaration (`a, b int`); each name is a parameter.\n count += child.type === 'parameter_declaration' ? Math.max(1, findChildrenByFieldName(child, 'name').length) : 1;\n }\n // C++ C-style varargs (`int f(int a, ...)`) leave `...` as an anonymous token, unlike C's named\n // `variadic_parameter`.\n const anonymousVariadicCount = parametersNode.children.filter(\n (child) => !child.isNamed && child.text === '...'\n ).length;\n return count + anonymousVariadicCount;\n}\n\n/** C/C++ `int f(void)` has a `parameter_declaration` whose type is a bare `void` with no declarator. */\nfunction isVoidParameter(node: Parser.SyntaxNode): boolean {\n return (\n node.type === 'parameter_declaration' &&\n node.childForFieldName('declarator') === null &&\n node.childForFieldName('type')?.text === 'void'\n );\n}\n\nfunction findParametersNode(node: Parser.SyntaxNode): Parser.SyntaxNode | undefined {\n const direct = node.childForFieldName('parameters');\n if (direct) {\n return direct;\n }\n\n // A Java compact constructor implicitly takes the record's components, declared on the\n // `record_declaration` two levels up (via `class_body`).\n if (node.type === 'compact_constructor_declaration') {\n return node.parent?.parent?.childForFieldName('parameters') ?? undefined;\n }\n\n // C/C++ parameters hang off the (possibly pointer/reference-wrapped) declarator, not the\n // definition itself.\n let declarator: Parser.SyntaxNode | null | undefined = node.childForFieldName('declarator');\n while (declarator) {\n const parameters = declarator.childForFieldName('parameters');\n if (parameters) {\n return parameters;\n }\n declarator = nextDeclarator(declarator);\n }\n\n return node.namedChildren.find((child) => child.type === 'formal_parameters' || child.type === 'parameter_list');\n}\n\n/**\n * C++ `function_definition` also covers pure-virtual/`= default`/`= delete` members; those have no\n * `body` and are signatures, not implementations, matching how TypeScript method signatures are\n * excluded. Java `method_declaration` is NOT here: PMD reports abstract/interface methods as\n * methods (NCSS 1), so bodyless Java methods stay in the function list.\n */\nconst bodyRequiredFunctionTypes = new Set([\n 'function_definition',\n 'constructor_declaration',\n 'compact_constructor_declaration',\n // Rust trait method signatures (`fn required(&self);`) never carry a body.\n 'function_signature_item',\n]);\n\nfunction isImplementedFunction(node: Parser.SyntaxNode): boolean {\n if (!bodyRequiredFunctionTypes.has(node.type) || node.childForFieldName('body') !== null) {\n return true;\n }\n\n // C++ constructor/destructor function-try-blocks carry their `try_statement` outside the\n // `body` field; they are implementations, unlike `= 0`/`= default`/`= delete` members.\n return node.namedChildren.some((child) => child.type === 'try_statement');\n}\n\n/**\n * A Ruby stabby lambda (`->(x) { ... }`) wraps its body in a `block`/`do_block`, which is itself a\n * function node type; the wrapper is part of the lambda, not a separate function, so it must not\n * count as one or act as a nested-function boundary.\n */\nfunction isLambdaBodyBlock(node: Parser.SyntaxNode): boolean {\n return (node.type === 'block' || node.type === 'do_block') && node.parent?.type === 'lambda';\n}\n\nfunction isFunctionBoundary(node: Parser.SyntaxNode, functionNodeTypes: Set<string>): boolean {\n return functionNodeTypes.has(node.type) && !isLambdaBodyBlock(node);\n}\n\n// Sonar cognitive complexity charges a switch/match once as a whole, not per case label. Only\n// named nodes are consulted, so anonymous keyword tokens never match.\nconst switchLikeNodeTypes = new Set([\n 'switch_statement',\n 'switch_expression',\n 'expression_switch_statement',\n 'type_switch_statement',\n 'select_statement',\n 'match_expression',\n 'match_statement',\n 'case',\n 'case_match',\n]);\n\n// Per-case decision nodes add no cognitive point because the switch itself carries the cost.\nconst caseClauseNodeTypes = new Set([\n 'case_clause',\n 'switch_case',\n 'switch_block_statement_group',\n 'switch_rule',\n 'case_statement',\n 'expression_case',\n 'type_case',\n 'communication_case',\n 'match_arm',\n 'when',\n 'in_clause',\n]);\n\nconst ifLikeNodeTypes = new Set(['if_statement', 'if_expression', 'if', 'unless']);\n\ninterface ComplexityNodeSets {\n functionNodes: Set<string>;\n decisionNodes: Set<string>;\n nestingNodes: Set<string>;\n}\n\n// Cached per language: measureComplexity runs once per function plus once per file, so per-call\n// Set construction would add a measurable constant factor on large files.\nconst complexityNodeSetsCache = new WeakMap<LanguageDefinition, ComplexityNodeSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nfunction invalidateComplexityNodeSetsCache(language: LanguageDefinition): void {\n complexityNodeSetsCache.delete(language);\n}\n\nfunction getComplexityNodeSets(language: LanguageDefinition): ComplexityNodeSets {\n let sets = complexityNodeSetsCache.get(language);\n if (!sets) {\n sets = {\n functionNodes: new Set(language.functionNodeTypes),\n decisionNodes: new Set(language.decisionNodeTypes),\n nestingNodes: new Set(language.nestingNodeTypes),\n };\n complexityNodeSetsCache.set(language, sets);\n }\n return sets;\n}\n\ninterface FunctionBodyMetrics {\n cognitiveComplexity: number;\n nestingDepth: number;\n ncss: number;\n}\n\n/** Accumulator for one function body during measureFunctionBodyMetrics' post-order pass. */\ninterface FunctionBodyFrame {\n cognitiveComplexity: number;\n /** Count of `1 + nesting` cognitive increments, for re-basing on hoist into the parent frame. */\n nestingSensitiveCount: number;\n nestingDepth: number;\n ncss: number;\n hasOwnNcssContribution: boolean;\n /** Cognitive nesting (structural nesting + function/class bonuses) carried into this body. */\n entryCognitiveNesting: number;\n /** Structural nesting carried into this body (bonuses excluded), for nesting depth. */\n entryStructuralNesting: number;\n}\n\nfunction createFrame(entryCognitiveNesting: number, entryStructuralNesting: number): FunctionBodyFrame {\n return {\n cognitiveComplexity: 0,\n nestingSensitiveCount: 0,\n nestingDepth: 0,\n ncss: 0,\n hasOwnNcssContribution: false,\n entryCognitiveNesting,\n entryStructuralNesting,\n };\n}\n\n/**\n * Per-function complexity and NCSS for every function boundary, in one post-order pass so each\n * node is visited once instead of once per enclosing function (issue #35). A function's metrics\n * are its own-body contributions plus, per directly nested function, that function's\n * already-computed totals: NCSS hoists as-is; cognitive complexity re-bases the nested function's\n * nesting-sensitive increments (each worth `1 + nesting`) by the nesting offset at the embedding\n * site, while flat increments (else branches, boolean-operator sequences, chain continuations,\n * jumps, guards) hoist unchanged; nesting depth describes the own body only, so it does not hoist.\n */\nfunction measureFunctionBodyMetrics(\n root: Parser.SyntaxNode,\n language: LanguageDefinition\n): Map<number, FunctionBodyMetrics> {\n const { functionNodes, decisionNodes, nestingNodes } = getComplexityNodeSets(language);\n const { countable, containers } = getNcssSets(language);\n const results = new Map<number, FunctionBodyMetrics>();\n // frames[0] is a sentinel for top-level code; its accumulation is discarded.\n const frames: FunctionBodyFrame[] = [createFrame(0, 0)];\n\n function visit(\n current: Parser.SyntaxNode,\n currentNesting: number,\n functionNestingBonus: number,\n insideFunction: boolean,\n insideNestedRegion: boolean,\n insideChargedClassBody: boolean\n ): void {\n // A class body nested in a function (anonymous/local classes) raises the cognitive nesting\n // level once for everything inside it — PMD charges the class body, not the methods it holds\n // (verified: an anonymous-class instance initializer's `if` costs 1 + 1 nesting), so methods\n // directly inside a charged class body skip the function-boundary bonus.\n const isChargedClassBody = current.type === 'class_body' && insideFunction;\n if (isChargedClassBody) {\n insideNestedRegion = true;\n functionNestingBonus += 1;\n }\n const opensFrame = isFunctionBoundary(current, functionNodes);\n if (opensFrame) {\n if (insideFunction && !insideChargedClassBody) {\n functionNestingBonus += 1;\n }\n insideFunction = true;\n }\n // The node's own increments target the frame it is embedded in, not the one it opens; a\n // frame-opening or charged-class-body node contributes nothing to that frame's own body\n // (nesting), matching the per-function traversal this pass replaces.\n const frame = frames.at(-1) as FunctionBodyFrame;\n const relativeNesting = currentNesting + functionNestingBonus - frame.entryCognitiveNesting;\n const countsForOwnBody = !insideNestedRegion && !opensFrame;\n\n // Anonymous keyword tokens can share a type with named nodes (Ruby's `if` node contains an\n // `if` keyword token), so only named nodes count as decisions.\n const isDecision = current.isNamed && decisionNodes.has(current.type) && !isDefaultSwitchBranch(current);\n const isCaseClause = current.isNamed && caseClauseNodeTypes.has(current.type);\n // Ruby's `case ... else` arm is an `else` node; like every other language's default branch it\n // nests its contents inside the switch (it cannot go in the Ruby nesting set because\n // `if`/`begin` else branches would then double-nest under their already-nesting parent).\n const isNesting =\n current.isNamed &&\n (nestingNodes.has(current.type) ||\n (current.type === 'else' && (current.parent?.type === 'case' || current.parent?.type === 'case_match')));\n // `elsif`/`elif`/`else if` continue a flat chain: they add a decision without a nesting\n // surcharge, and their bodies stay at the chain's nesting level (Sonar cognitive-complexity\n // semantics); genuinely nested conditionals inside those bodies still deepen.\n const isContinuation = isDecision && isFlatChainContinuation(current);\n\n if (isDecision && !isCaseClause) {\n if (isContinuation) {\n frame.cognitiveComplexity += 1;\n } else {\n frame.cognitiveComplexity += 1 + relativeNesting;\n frame.nestingSensitiveCount += 1;\n }\n }\n if (current.isNamed && switchLikeNodeTypes.has(current.type)) {\n frame.cognitiveComplexity += 1 + relativeNesting;\n frame.nestingSensitiveCount += 1;\n }\n // A plain `else` branch adds one flat cognitive point; `else if` chains are charged on the\n // nested if instead.\n frame.cognitiveComplexity += countPlainElseBranches(current);\n // Sonar charges flow-breaking jumps: goto and labeled break/continue add one flat point.\n if (isFlowBreakingJump(current)) {\n frame.cognitiveComplexity += 1;\n }\n\n // A sequence of identical boolean operators reads as one condition, so only the operator\n // starting a sequence adds a cognitive point (Sonar spec).\n if (isBooleanOperator(current) && startsBooleanOperatorSequence(current)) {\n frame.cognitiveComplexity += 1;\n }\n\n // Pattern guards (Java `when`, Ruby `in y if ...`, Python `case n if ...`, Rust `n if ... =>`)\n // add one independent execution path without nesting.\n if (isPatternGuard(current)) {\n frame.cognitiveComplexity += 1;\n }\n\n const childNesting = isNesting && !isContinuation ? currentNesting + 1 : currentNesting;\n if (countsForOwnBody) {\n frame.nestingDepth = Math.max(frame.nestingDepth, childNesting - frame.entryStructuralNesting);\n }\n\n if (opensFrame) {\n frames.push(createFrame(childNesting + functionNestingBonus, childNesting));\n }\n // The node's NCSS contribution belongs to the innermost frame whose subtree holds it — the\n // frame the node opens, if any (per-function NCSS includes the declaration node itself).\n const ownNcss = ncssContribution(current, countable, containers);\n const ncssFrame = frames.at(-1) as FunctionBodyFrame;\n ncssFrame.ncss += ownNcss;\n if (opensFrame && ownNcss > 0) {\n ncssFrame.hasOwnNcssContribution = true;\n }\n\n for (const child of current.children) {\n visit(\n child,\n childNesting,\n functionNestingBonus,\n insideFunction,\n opensFrame ? false : insideNestedRegion,\n isChargedClassBody\n );\n }\n\n if (opensFrame) {\n const closed = frames.pop() as FunctionBodyFrame;\n results.set(current.id, {\n cognitiveComplexity: closed.cognitiveComplexity,\n nestingDepth: closed.nestingDepth,\n // A function node without a countable declaration of its own (arrow functions, lambdas,\n // blocks) still counts 1 for the declaration itself.\n ncss: closed.ncss + (closed.hasOwnNcssContribution ? 0 : 1),\n });\n const parent = frames.at(-1) as FunctionBodyFrame;\n parent.cognitiveComplexity +=\n closed.cognitiveComplexity +\n closed.nestingSensitiveCount * (closed.entryCognitiveNesting - parent.entryCognitiveNesting);\n parent.nestingSensitiveCount += closed.nestingSensitiveCount;\n parent.ncss += closed.ncss;\n }\n }\n\n visit(root, 0, 0, false, false, false);\n return results;\n}\n\n/**\n * File-level complexity over the whole tree. Cognitive complexity charges nested function/lambda\n * content one nesting level deeper per function boundary crossed (Sonar spec); nesting depth\n * counts every node once.\n */\nfunction measureComplexity(node: Parser.SyntaxNode, language: LanguageDefinition): ComplexityResult {\n let cognitiveComplexity = 0;\n let nestingDepth = 0;\n const { functionNodes, decisionNodes, nestingNodes } = getComplexityNodeSets(language);\n\n function visit(\n current: Parser.SyntaxNode,\n currentNesting: number,\n functionNestingBonus: number,\n insideFunction: boolean,\n insideChargedClassBody: boolean\n ): void {\n // A class body nested in a function (anonymous/local classes) raises the cognitive nesting\n // level once for everything inside it — PMD charges the class body, not the methods it holds,\n // so methods directly inside a charged class body skip the function-boundary bonus.\n const isChargedClassBody = current.type === 'class_body' && insideFunction;\n if (isChargedClassBody) {\n functionNestingBonus += 1;\n }\n if (isFunctionBoundary(current, functionNodes)) {\n if (insideFunction && !insideChargedClassBody) {\n functionNestingBonus += 1;\n }\n insideFunction = true;\n }\n const cognitiveNesting = currentNesting + functionNestingBonus;\n\n // Anonymous keyword tokens can share a type with named nodes (Ruby's `if` node contains an\n // `if` keyword token), so only named nodes count as decisions.\n const isDecision = current.isNamed && decisionNodes.has(current.type) && !isDefaultSwitchBranch(current);\n const isCaseClause = current.isNamed && caseClauseNodeTypes.has(current.type);\n // Ruby's `case ... else` arm is an `else` node; like every other language's default branch it\n // nests its contents inside the switch (it cannot go in the Ruby nesting set because\n // `if`/`begin` else branches would then double-nest under their already-nesting parent).\n const isNesting =\n current.isNamed &&\n (nestingNodes.has(current.type) ||\n (current.type === 'else' && (current.parent?.type === 'case' || current.parent?.type === 'case_match')));\n // `elsif`/`elif`/`else if` continue a flat chain: they add a decision without a nesting\n // surcharge, and their bodies stay at the chain's nesting level (Sonar cognitive-complexity\n // semantics); genuinely nested conditionals inside those bodies still deepen.\n const isContinuation = isDecision && isFlatChainContinuation(current);\n\n if (isDecision && !isCaseClause) {\n cognitiveComplexity += isContinuation ? 1 : 1 + cognitiveNesting;\n }\n if (current.isNamed && switchLikeNodeTypes.has(current.type)) {\n cognitiveComplexity += 1 + cognitiveNesting;\n }\n // A plain `else` branch adds one flat cognitive point; `else if` chains are charged on the\n // nested if instead.\n cognitiveComplexity += countPlainElseBranches(current);\n // Sonar charges flow-breaking jumps: goto and labeled break/continue add one flat point.\n if (isFlowBreakingJump(current)) {\n cognitiveComplexity += 1;\n }\n\n // A sequence of identical boolean operators reads as one condition, so only the operator\n // starting a sequence adds a cognitive point (Sonar spec).\n if (isBooleanOperator(current) && startsBooleanOperatorSequence(current)) {\n cognitiveComplexity += 1;\n }\n\n // Pattern guards (Java `when`, Ruby `in y if ...`, Python `case n if ...`, Rust `n if ... =>`)\n // add one independent execution path without nesting.\n if (isPatternGuard(current)) {\n cognitiveComplexity += 1;\n }\n\n const childNesting = isNesting && !isContinuation ? currentNesting + 1 : currentNesting;\n nestingDepth = Math.max(nestingDepth, childNesting);\n\n for (const child of current.children) {\n visit(child, childNesting, functionNestingBonus, insideFunction, isChargedClassBody);\n }\n }\n\n for (const child of node.children) {\n visit(child, 0, 0, false, false);\n }\n\n return { cognitiveComplexity, nestingDepth };\n}\n\n/**\n * Plain else branches attached to `current`: an `else_clause`/Ruby `else` whose branch is not an\n * `else if` continuation, or a bare Java/Go `alternative:` statement without a clause wrapper.\n */\nfunction countPlainElseBranches(current: Parser.SyntaxNode): number {\n if (!current.isNamed) {\n return 0;\n }\n if (current.type === 'else') {\n // A Ruby `case ... else` is the default arm of a switch, which already counts as a whole\n // (sonar-ruby models it as a match case, not an else branch); `if`/`unless`/`begin` else\n // branches count one point each.\n return current.parent?.type === 'case' || current.parent?.type === 'case_match' ? 0 : 1;\n }\n if (current.type === 'else_clause') {\n return current.namedChildren.some((child) => ifLikeNodeTypes.has(child.type)) ? 0 : 1;\n }\n if (current.type !== 'if_statement' && current.type !== 'if_expression') {\n return 0;\n }\n let count = 0;\n for (let index = 0; index < current.childCount; index += 1) {\n const child = current.child(index);\n if (\n child &&\n current.fieldNameForChild(index) === 'alternative' &&\n child.type !== 'else_clause' &&\n child.type !== 'elif_clause' &&\n !ifLikeNodeTypes.has(child.type)\n ) {\n count += 1;\n }\n }\n return count;\n}\n\n/** goto, and break/continue that jump to a label (their only named child is the label). */\nfunction isFlowBreakingJump(node: Parser.SyntaxNode): boolean {\n if (!node.isNamed) {\n return false;\n }\n if (node.type === 'goto_statement') {\n return true;\n }\n // Rust jumps are expressions; `break value` carries a named expression child, so only an\n // explicit `label` child marks a labeled jump.\n if (node.type === 'break_expression' || node.type === 'continue_expression') {\n return node.namedChildren.some((child) => child.type === 'label' || child.type === 'loop_label');\n }\n // Comments are named children too (`break /* done */;`), so only non-comment children mark a\n // label.\n return (\n (node.type === 'break_statement' || node.type === 'continue_statement') &&\n node.namedChildren.some((child) => !commentNodeTypes.has(child.type))\n );\n}\n\n// Wrappers that are transparent when locating the enclosing boolean operation: PMD/Sonar keep a\n// sequence continuous across parentheses (`a && (b && c)` costs one point).\nconst parenthesizedNodeTypes = new Set(['parenthesized_expression', 'parenthesized_statements']);\n\n/**\n * Whether this boolean operator token starts a new sequence, i.e. its binary node is the root of a\n * run of same-operator binaries (possibly through parentheses). Only the root operator counts one\n * cognitive point: `a && b && c` and `a && (b && c)` cost one, `a && b || c` costs two, matching\n * the Sonar specification and PMD 7.26.0.\n */\nfunction startsBooleanOperatorSequence(token: Parser.SyntaxNode): boolean {\n const binary = token.parent;\n if (!binary) {\n return true;\n }\n let ancestor = binary.parent;\n while (ancestor && parenthesizedNodeTypes.has(ancestor.type)) {\n ancestor = ancestor.parent;\n }\n if (!ancestor || ancestor.type !== binary.type) {\n return true;\n }\n return normalizeBooleanOperator(findBooleanOperatorText(ancestor)) !== normalizeBooleanOperator(token.text);\n}\n\n/** C++ `and`/`or` are alternative spellings of `&&`/`||`, so mixing them keeps one sequence. */\nfunction normalizeBooleanOperator(text: string | undefined): string | undefined {\n if (text === 'and') {\n return '&&';\n }\n if (text === 'or') {\n return '||';\n }\n return text;\n}\n\nfunction findBooleanOperatorText(binaryNode: Parser.SyntaxNode): string | undefined {\n const operator = binaryNode.childForFieldName('operator');\n if (operator) {\n return operator.text;\n }\n return binaryNode.children.find((child) => !child.isNamed && booleanOperators.has(child.text))?.text;\n}\n\n/** Java `guard`, Ruby `if_guard`, Python `if_clause`, and Rust guards inside `match_pattern`. */\nfunction isPatternGuard(node: Parser.SyntaxNode): boolean {\n if (!node.isNamed) {\n return false;\n }\n if (node.type === 'guard' || node.type === 'if_guard' || node.type === 'unless_guard' || node.type === 'if_clause') {\n return true;\n }\n return node.type === 'match_pattern' && node.children.some((child) => !child.isNamed && child.type === 'if');\n}\n\n/** Ruby `elsif`, Python `elif`, and `else if` (an if node in an else/alternative position). */\nfunction isFlatChainContinuation(node: Parser.SyntaxNode): boolean {\n if (node.type === 'elsif' || node.type === 'elif_clause') {\n return true;\n }\n if (node.type !== 'if_statement' && node.type !== 'if_expression' && node.type !== 'if') {\n return false;\n }\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // JS/C/C++/Rust wrap `else if` in an else clause; Java/Go put it directly in `alternative`.\n return parent.type === 'else_clause' || parent.childForFieldName('alternative')?.id === node.id;\n}\n\n/**\n * C/C++ `default:` shares the `case_statement` node type with `case` (only `case` has a `value`\n * field), and Java's default group/rule carries an expressionless `switch_label`; a default branch\n * adds no decision, though its contents still nest inside the switch like any other arm.\n */\nfunction isDefaultSwitchBranch(node: Parser.SyntaxNode): boolean {\n if (node.type === 'case_statement') {\n return node.childForFieldName('value') === null;\n }\n\n if (node.type === 'switch_block_statement_group' || node.type === 'switch_rule') {\n const label = node.namedChildren.find((child) => child.type === 'switch_label');\n return label !== undefined && label.namedChildCount === 0;\n }\n\n // Python `case _:` / `case y:` and Rust `_ =>` fallback arms are unconditional like `default`\n // (a bare Python name is always a capture); a guard is charged separately as a flat decision,\n // so the arm itself still adds nothing. Rust bare identifiers are NOT suppressed: they can name\n // constants or unit variants, which the grammar cannot distinguish from captures.\n if (node.type === 'case_clause' || node.type === 'match_arm') {\n const pattern = node.namedChildren.find((child) => child.type === 'case_pattern' || child.type === 'match_pattern');\n if (!pattern) {\n return false;\n }\n if (pattern.child(0)?.type === '_' && (pattern.childCount === 1 || pattern.child(1)?.type === 'if')) {\n return true;\n }\n const soleChild = pattern.namedChildCount === 1 ? pattern.namedChild(0) : undefined;\n return (\n node.type === 'case_clause' &&\n soleChild?.type === 'dotted_name' &&\n soleChild.namedChildCount === 1 &&\n soleChild.namedChild(0)?.type === 'identifier'\n );\n }\n\n // Ruby `in y` binds unconditionally (bare lowercase names are variable captures; constants and\n // literals are tests).\n if (node.type === 'in_clause') {\n return node.namedChild(0)?.type === 'identifier';\n }\n\n return false;\n}\n\n/** Parents under which `&&`/`||`/`and`/`or` tokens are actual boolean operators. */\nconst booleanOperatorParentTypes = new Set(['binary_expression', 'binary', 'boolean_operator']);\n\n/**\n * The parent guard is required because the same tokens appear in non-boolean syntax: C++ rvalue\n * references (`int&&`), ref-qualifiers, `operator&&`, and Rust's empty closure parameter list\n * (`|| 5`) must not count as decisions.\n */\nfunction isBooleanOperator(node: Parser.SyntaxNode): boolean {\n if (node.isNamed || !booleanOperators.has(node.text)) {\n return false;\n }\n\n const parent = node.parent;\n return parent !== null && booleanOperatorParentTypes.has(parent.type);\n}\n\nfunction collectNodes(root: Parser.SyntaxNode, nodeTypes: Set<string>): Parser.SyntaxNode[] {\n const nodes: Parser.SyntaxNode[] = [];\n\n function visit(node: Parser.SyntaxNode): void {\n if (nodeTypes.has(node.type)) {\n nodes.push(node);\n }\n\n for (const child of node.namedChildren) {\n visit(child);\n }\n }\n\n visit(root);\n return nodes;\n}\n\n/**\n * 1-based numbers of lines that are neither blank nor comment-only, matching measureLines'\n * classification so duplication line coverage and its code-line denominator agree.\n */\nfunction classifyLines(\n code: string,\n root: Parser.SyntaxNode\n): { lines: CodeMetrics['lines']; codeLineNumbers: Set<number> } {\n const sourceLines = code.length === 0 ? [] : code.split(/\\r\\n|\\n|\\r/);\n // Spans are bucketed by line so classification stays linear; scanning every span per line made\n // this pass quadratic on comment-heavy files.\n const commentSpansByLine = new Map<number, CommentSpan[]>();\n for (const span of collectCommentSpans(root)) {\n const spans = commentSpansByLine.get(span.line) ?? [];\n spans.push(span);\n commentSpansByLine.set(span.line, spans);\n }\n let blank = 0;\n let comment = 0;\n const codeLineNumbers = new Set<number>();\n\n for (const [index, line] of sourceLines.entries()) {\n if (line.trim() === '') {\n blank += 1;\n continue;\n }\n if (isCommentOnlyLine(line, commentSpansByLine.get(index) ?? [])) {\n comment += 1;\n } else {\n codeLineNumbers.add(index + 1);\n }\n }\n\n return {\n lines: {\n total: sourceLines.length,\n code: codeLineNumbers.size,\n comment,\n blank,\n },\n codeLineNumbers,\n };\n}\n\nfunction collectCommentSpans(root: Parser.SyntaxNode): CommentSpan[] {\n const spans: CommentSpan[] = [];\n\n function visit(node: Parser.SyntaxNode): void {\n if (node.type === 'comment' || node.type === 'line_comment' || node.type === 'block_comment') {\n for (let row = node.startPosition.row; row <= node.endPosition.row; row += 1) {\n spans.push({\n line: row,\n startColumn: row === node.startPosition.row ? node.startPosition.column : 0,\n endColumn: row === node.endPosition.row ? node.endPosition.column : Number.POSITIVE_INFINITY,\n });\n }\n }\n\n for (const child of node.namedChildren) {\n visit(child);\n }\n }\n\n visit(root);\n return spans;\n}\n\nfunction isCommentOnlyLine(line: string, relevantSpans: CommentSpan[]): boolean {\n if (relevantSpans.length === 0) {\n return false;\n }\n\n // A line may hold several comments (`/* one */ /* two */`), so every non-whitespace column must\n // be covered by the UNION of spans, not by a single span.\n for (let column = 0; column < line.length; column += 1) {\n if (/\\s/u.test(line[column] ?? ' ')) {\n continue;\n }\n if (!relevantSpans.some((span) => span.startColumn <= column && column < span.endColumn)) {\n return false;\n }\n }\n return true;\n}\n\nfunction measureHalstead(root: Parser.SyntaxNode, code: string): HalsteadMetrics {\n const operators = new Map<string, number>();\n const operands = new Map<string, number>();\n\n function visit(node: Parser.SyntaxNode): void {\n if (node.type === 'comment' || node.type === 'line_comment' || node.type === 'block_comment') {\n return;\n }\n\n if (atomicOperandNodeTypes.has(node.type)) {\n incrementCount(operands, code.slice(node.startIndex, node.endIndex));\n return;\n }\n\n // Operators are counted from leaf tokens only: keyword-named nodes (Ruby `return`, Python\n // `await`, ...) always contain a same-text anonymous keyword leaf, so counting the named node\n // as well would double-count.\n if (node.childCount === 0) {\n const text = code.slice(node.startIndex, node.endIndex);\n // Operands win over text matches so identifiers spelled like word operators (`cache.delete(...)`,\n // a Go parameter named `in`) stay operands; genuine keyword operators are anonymous leaves whose\n // types are never operand types. A blanket `isNamed` guard would break JS's named `optional_chain`.\n if (operandNodeTypes.has(node.type)) {\n incrementCount(operands, text);\n } else if ((operatorTexts.has(text) || operatorTexts.has(node.type)) && isCountableContextualToken(node, text)) {\n incrementCount(operators, text || node.type);\n }\n return;\n }\n\n for (const child of node.children) {\n visit(child);\n }\n }\n\n visit(root);\n\n return deriveHalsteadMetrics({\n distinctOperators: operators.size,\n distinctOperands: operands.size,\n totalOperators: sum(operators.values()),\n totalOperands: sum(operands.values()),\n });\n}\n\n/** Derives the full Halstead metrics from the four base counts (shared with the native backend). */\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nfunction findFunctionName(node: Parser.SyntaxNode): string | undefined {\n const wrappedName = findWrappedComponentName(node);\n if (wrappedName) {\n return wrappedName;\n }\n\n const nameNode = node.childForFieldName('name');\n if (nameNode) {\n return nameNode.text;\n }\n\n // C/C++ definitions name the function inside the (possibly pointer-wrapped) declarator chain.\n const declaratorName = findDeclaratorName(node);\n if (declaratorName) {\n return declaratorName;\n }\n\n const parent = node.parent;\n if (!parent) {\n return undefined;\n }\n\n // A Rust closure bound to a simple `let` identifier (`let add = |x| ...;`) takes that identifier\n // as its name, mirroring how JS arrow functions assigned to a variable are named, so calls to the\n // binding resolve as intra-file edges.\n if (node.type === 'closure_expression' && parent.type === 'let_declaration') {\n const patternNode = parent.childForFieldName('pattern');\n return patternNode?.type === 'identifier' ? patternNode.text : undefined;\n }\n\n // A C++ lambda assigned to a variable (`auto f = [](int x) { ... };`) takes the variable name,\n // like Rust `let` closures above, so calls to the binding resolve as intra-file edges.\n if (node.type === 'lambda_expression' && parent.type === 'init_declarator') {\n return unwrapDeclaratorName(parent.childForFieldName('declarator'));\n }\n\n // A Go func literal bound via `add := func...` or `var add = func...` takes the identifier at\n // the same list position; unpaired or non-identifier targets stay unnamed.\n if (node.type === 'func_literal' && parent.type === 'expression_list') {\n return findGoFuncLiteralName(node, parent);\n }\n\n // Ruby lambdas assigned to a name (`choose = ->(x) {...}` / `ADD = lambda { ... }`) take that\n // name; Ruby assignments use the `left` field, and `lambda { }` blocks hang off a `call`.\n if (node.type === 'lambda' && parent.type === 'assignment') {\n return findRubyAssignmentName(parent);\n }\n if ((node.type === 'block' || node.type === 'do_block') && isRubyLambdaCall(parent)) {\n return parent.parent?.type === 'assignment' ? findRubyAssignmentName(parent.parent) : undefined;\n }\n\n const parentName = parent.childForFieldName('name');\n return parentName?.text;\n}\n\nfunction findDeclaratorName(node: Parser.SyntaxNode): string | undefined {\n return unwrapDeclaratorName(node.childForFieldName('declarator'));\n}\n\n/**\n * Unwraps a C/C++ declarator chain to the declared name, handling parenthesized declarators\n * (function pointers), qualified names, destructors, and operator overloads explicitly; a\n * rightmost-identifier fallback would pick up parameter names from nested `function_declarator`s.\n */\nfunction unwrapDeclaratorName(declarator: Parser.SyntaxNode | null): string | undefined {\n let current: Parser.SyntaxNode | null | undefined = declarator;\n while (current) {\n switch (current.type) {\n case 'identifier':\n case 'field_identifier':\n case 'type_identifier':\n case 'destructor_name':\n case 'operator_name': {\n return current.text;\n }\n // A C++ conversion operator (`operator int()`) is its own declarator node whose text spans\n // the parameter list and qualifiers; only `operator <type>` is the name.\n case 'operator_cast': {\n return `operator ${current.childForFieldName('type')?.text ?? ''}`.trimEnd();\n }\n // Template specializations (`id<int>`) and qualified names both carry a `name` field.\n case 'template_function':\n case 'qualified_identifier': {\n current = current.childForFieldName('name');\n break;\n }\n default: {\n current = nextDeclarator(current);\n }\n }\n }\n return undefined;\n}\n\n/**\n * Steps into the inner declarator; `reference_declarator` and `parenthesized_declarator` do not\n * expose a `declarator` field in tree-sitter-cpp, so their sole named child is the inner node.\n */\nfunction nextDeclarator(node: Parser.SyntaxNode): Parser.SyntaxNode | undefined {\n const direct = node.childForFieldName('declarator');\n if (direct) {\n return direct;\n }\n if (node.type === 'reference_declarator' || node.type === 'parenthesized_declarator') {\n return node.namedChild(0) ?? undefined;\n }\n return undefined;\n}\n\nfunction findRubyAssignmentName(assignment: Parser.SyntaxNode): string | undefined {\n const leftNode = assignment.childForFieldName('left');\n return leftNode?.type === 'identifier' || leftNode?.type === 'constant' ? leftNode.text : undefined;\n}\n\nfunction isRubyLambdaCall(node: Parser.SyntaxNode): boolean {\n if (node.type !== 'call' || node.childForFieldName('receiver')) {\n return false;\n }\n const methodNode = node.childForFieldName('method');\n return methodNode?.type === 'identifier' && (methodNode.text === 'lambda' || methodNode.text === 'proc');\n}\n\nfunction findGoFuncLiteralName(node: Parser.SyntaxNode, expressionList: Parser.SyntaxNode): string | undefined {\n const holder = expressionList.parent;\n // Comments interleave with expressions in the list but have no matching binding target, so\n // positions are aligned over non-comment children on both sides.\n const values = expressionList.namedChildren.filter((child) => child.type !== 'comment');\n const valueIndex = values.findIndex((child) => child.id === node.id);\n if (!holder || valueIndex === -1) {\n return undefined;\n }\n\n if (holder.type === 'short_var_declaration') {\n const targets = holder.childForFieldName('left')?.namedChildren.filter((child) => child.type !== 'comment');\n return asGoBindingName(targets?.[valueIndex]);\n }\n\n if (holder.type === 'var_spec') {\n const target = findChildrenByFieldName(holder, 'name')[valueIndex];\n return asGoBindingName(target);\n }\n\n return undefined;\n}\n\n/** Go's blank identifier `_` discards the value and creates no callable binding. */\nfunction asGoBindingName(target: Parser.SyntaxNode | undefined): string | undefined {\n return target?.type === 'identifier' && target.text !== '_' ? target.text : undefined;\n}\n\nfunction findWrappedComponentName(node: Parser.SyntaxNode): string | undefined {\n let current: Parser.SyntaxNode | undefined = node;\n while (current) {\n const argumentsNode: Parser.SyntaxNode | null = current.parent;\n const callNode: Parser.SyntaxNode | null | undefined = argumentsNode?.parent;\n if (argumentsNode?.type !== 'arguments' || callNode?.type !== 'call_expression') {\n return undefined;\n }\n\n if (!isReactComponentWrapperCall(callNode)) {\n return undefined;\n }\n\n const declaratorNode = callNode.parent;\n if (declaratorNode?.type === 'variable_declarator') {\n return declaratorNode.childForFieldName('name')?.text;\n }\n\n current = callNode;\n }\n\n return undefined;\n}\n\nfunction isReactComponentWrapperCall(node: Parser.SyntaxNode): boolean {\n const calleeNode = node.childForFieldName('function') ?? node.namedChild(0);\n return (\n calleeNode?.text === 'memo' ||\n calleeNode?.text === 'React.memo' ||\n calleeNode?.text === 'forwardRef' ||\n calleeNode?.text === 'React.forwardRef'\n );\n}\n\n/** Ternary/conditional and Rust try parents make `?` an operator; TS optional markers do not. */\nconst questionOperatorParentTypes = new Set([\n 'ternary_expression',\n 'conditional_expression',\n 'conditional',\n 'try_expression',\n // TypeScript conditional types (`T extends U ? X : Y`) select like a ternary.\n 'conditional_type',\n]);\n\nfunction isCountableContextualToken(node: Parser.SyntaxNode, text: string): boolean {\n if (text === '@') {\n // Python matrix multiplication only; decorator/annotation `@` marks are not operators.\n const parentType = node.parent?.type;\n return parentType === 'binary_operator' || parentType === 'augmented_assignment';\n }\n if (text !== '?') {\n return true;\n }\n const parentType = node.parent?.type;\n return parentType !== undefined && questionOperatorParentTypes.has(parentType);\n}\n\nfunction findChildrenByFieldName(node: Parser.SyntaxNode, fieldName: string): Parser.SyntaxNode[] {\n const children: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === fieldName) {\n children.push(child);\n }\n }\n return children;\n}\n\nfunction incrementCount(map: Map<string, number>, value: string): void {\n map.set(value, (map.get(value) ?? 0) + 1);\n}\n\nfunction maxCognitiveComplexity(functions: FunctionMetrics[]): number {\n return functions.length === 0 ? 0 : Math.max(...functions.map((fn) => fn.cognitiveComplexity));\n}\n\nfunction sum(values: Iterable<number>): number {\n let total = 0;\n for (const value of values) {\n total += value;\n }\n return total;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.cjs","names":["createLanguageRegistry","measureCodeNative","collectCrossFileDataNative","collectFunctionTokenSequencesNative"],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n const payload = collectCrossFileDataNative(code, language.name, options.duplication?.minTokens);\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or\n * moved functions across two revisions by token-LCS similarity.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"iFAgBA,IAAa,EAAb,KAA0B,CACxB,SAA4BA,EAAAA,uBAAuB,EAEnD,uBAAwC,CACtC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,IAAK,GAAa,EAAS,IAAI,CAAC,CAAC,CAClF,CAEA,QAAQ,EAAc,EAAsC,CAC1D,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAoB,EAAQ,mBAAqB,GAEvD,OAAO,EADSC,EAAAA,kBAAkB,EAAM,EAAS,KAAM,EAAmB,EAAQ,WAC/C,EAAG,CAAiB,CACzD,CAGA,6BAA6B,EAAc,EAAwD,CACjG,OAAO,KAAK,oCAAoC,EAAM,CAAO,CAAC,CAAC,UACjE,CAMA,oCAAoC,EAAc,EAAuD,CACvG,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAUC,EAAAA,2BAA2B,EAAM,EAAS,KAAM,EAAQ,aAAa,SAAS,EAC9F,MAAO,CACL,WAAY,EAAQ,WACpB,OAAQ,EAAQ,OAChB,oBAAqB,EAAQ,oBAE7B,gBAAiB,IAAI,IAAI,EAAQ,eAAe,CAClD,CACF,CAQA,8BAA8B,EAAc,EAAuC,CACjF,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EACtD,OAAOC,EAAAA,oCAAoC,EAAM,EAAS,IAAI,CAChE,CAEA,gBAAwB,EAAwC,CAC9D,IAAM,EAAW,KAAK,SAAS,IAAI,CAAI,EACvC,GAAI,CAAC,EACH,MAAU,MAAM,yBAAyB,GAAM,EAEjD,OAAO,CACT,CACF,EAOA,SAAS,EAAsB,EAA+B,EAAyC,CACrG,MAAO,CACL,SAAU,EAAQ,SAClB,MAAO,EAAQ,MACf,MAAO,EAAQ,MACf,UAAW,EAAQ,UAAU,IAAK,IAAQ,CACxC,KAAM,EAAG,KACT,SAAU,EAAG,SACb,UAAW,EAAG,UACd,YAAa,EAAG,YAChB,QAAS,EAAG,QACZ,oBAAqB,EAAG,oBACxB,aAAc,EAAG,aACjB,KAAM,EAAG,KACT,eAAgB,EAAG,eACnB,SAAU,EAAsB,EAAG,cAAc,EACjD,UAAW,EAAG,SAChB,EAAE,EACF,oBAAqB,EAAQ,oBAC7B,uBAAwB,EAAQ,uBAChC,aAAc,EAAQ,aACtB,UAAW,EAAQ,UACnB,YAAa,EAAQ,YACrB,SAAU,EAAsB,EAAQ,cAAc,EACtD,WAAY,EAAoB,EAAQ,WAAa,IAAA,EACvD,CACF,CAEA,SAAS,EAAsB,EAA+C,CAC5E,GAAM,CAAE,oBAAmB,mBAAkB,iBAAgB,iBAAkB,EACzE,EAAa,EAAoB,EACjC,EAAS,EAAiB,EAC1B,EAAS,IAAe,EAAI,EAAI,EAAS,KAAK,KAAK,CAAU,EAInE,MAAO,CACL,oBACA,mBACA,iBACA,gBACA,aACA,SACA,SACA,QAXiB,IAAqB,EAAI,EAAK,EAAoB,GAAM,EAAgB,IAC/D,CAW5B,CACF,CAEA,MAAa,EAAkB,IAAI,EAEnC,SAAgB,EAAY,EAAc,EAAsC,CAC9E,OAAO,EAAgB,QAAQ,EAAM,CAAO,CAC9C,CAGA,SAAgB,EAA6B,EAAc,EAAwD,CACjH,OAAO,EAAgB,6BAA6B,EAAM,CAAO,CACnE,CAGA,SAAgB,EAA8B,EAAc,EAAuC,CACjG,OAAO,EAAgB,8BAA8B,EAAM,CAAO,CACpE,CAGA,SAAgB,EACd,EACA,EAC8B,CAC9B,OAAO,EAAgB,oCAAoC,EAAM,CAAO,CAC1E"}
package/dist/metrics.d.ts CHANGED
@@ -1,22 +1,35 @@
1
- import { type CrossFileDuplicateCandidate, type CrossFileDuplicationFileData } from './duplication.js';
2
- import type { CodeMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';
1
+ import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';
2
+ import type { CodeMetrics, LanguageName, MeasureOptions } from './types.js';
3
+ /**
4
+ * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled
5
+ * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and
6
+ * derives the Halstead float metrics from the natively measured counts.
7
+ */
3
8
  export declare class TreeMeasurer {
4
9
  private readonly registry;
5
- registerLanguage(language: LanguageDefinition): void;
6
10
  getSupportedLanguages(): LanguageName[];
7
11
  measure(code: string, options: MeasureOptions): CodeMetrics;
8
12
  /** Collects one file's duplicate candidates for cross-file clone detection. */
9
13
  collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[];
10
14
  /**
11
15
  * Collects one file's duplicate candidates, normalized tokens, and statement structure for
12
- * cross-file clone detection with measureCrossFileDuplication. Always measured by the
13
- * TypeScript backend.
16
+ * cross-file clone detection with measureCrossFileDuplication.
14
17
  */
15
18
  collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData;
19
+ /**
20
+ * Normalized token hash sequences of every function, index-parallel to the functions array of
21
+ * measure(): identifiers are anonymized by first occurrence within the function, literals by
22
+ * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or
23
+ * moved functions across two revisions by token-LCS similarity.
24
+ */
25
+ collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[];
26
+ private resolveLanguage;
16
27
  }
17
28
  export declare const defaultMeasurer: TreeMeasurer;
18
29
  export declare function measureCode(code: string, options: MeasureOptions): CodeMetrics;
19
30
  /** Standalone helper mirroring measureCode for the default measurer. */
20
31
  export declare function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[];
21
32
  /** Standalone helper mirroring measureCode for the default measurer. */
33
+ export declare function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[];
34
+ /** Standalone helper mirroring measureCode for the default measurer. */
22
35
  export declare function collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData;
package/dist/metrics.js CHANGED
@@ -1,2 +1,2 @@
1
- import{collectCrossFileDuplicateCandidates as e,defaultDuplicationOptions as t,measureDuplication as n}from"./duplication.js";import{createLanguageRegistry as r}from"./languages.js";import{commentNodeTypes as i,countNcss as a,getNcssSets as o,invalidateNcssSetsCache as s,ncssContribution as c}from"./ncss.js";import{measureWithNativeBackend as l}from"./nativeMetrics.js";import u from"tree-sitter";const d=new Set([`&&`,`||`,`and`,`or`]),f=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(`,`)),p=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(`.`)),m=new Set([`interpreted_string_literal`,`regex`,`user_defined_literal`,`integral_type`,`floating_point_type`,`sized_type_specifier`,`placeholder_type_specifier`]);var h=class{registry=r();registerLanguage(e){ne(e),s(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,t){let r=this.registry.get(t.language);if(!r)throw Error(`Unsupported language: ${t.language}`);let i=_(t)?l(e,r,t.includeSyntaxTree??!1):void 0;if(i)return S(i,t.includeSyntaxTree??!1);let o=g(e,r),s=C(o,ce(o,new Set(r.functionNodeTypes)).filter(e=>!D(e)&&te(e)),r),c=ie(o,r),{lines:u,codeLineNumbers:d}=U(e,o);return{language:r.name,bytes:Buffer.byteLength(e),lines:u,functions:s,cognitiveComplexity:c.cognitiveComplexity,maxCognitiveComplexity:ve(s),nestingDepth:c.nestingDepth,ncssCount:a(o,r),duplication:n(o,d,t.duplication),halstead:ue(o,e),syntaxTree:t.includeSyntaxTree?o.toString():void 0}}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(t,n){let r=this.registry.get(n.language);if(!r)throw Error(`Unsupported language: ${n.language}`);let i=g(t,r);return{...e(i,n.duplication),codeLineNumbers:U(t,i).codeLineNumbers}}};function g(e,t){let n=new u;return n.setLanguage(t.parserLanguage),n.parse(e,void 0,{bufferSize:e.length+1}).rootNode}function _(e){let n=e.duplication;return(n?.minTokens??t.minTokens)===t.minTokens&&(n?.maxGapTokens??t.maxGapTokens)===t.maxGapTokens&&(n?.minSimilarityPercent??t.minSimilarityPercent)===t.minSimilarityPercent}const v=new h;function y(e,t){return v.measure(e,t)}function b(e,t){return v.collectDuplicationCandidates(e,t)}function x(e,t){return v.collectCrossFileDuplicationFileData(e,t)}function S(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})),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 C(e,t,n){let r=re(e,n);return t.map(e=>{let t=r.get(e.id);if(!t)throw Error(`missing body metrics for function node at line ${e.startPosition.row+1}`);return{name:de(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:w(e)}})}function w(e){if(e.childForFieldName(`parameter`))return 1;let t=E(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)||T(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 T(e){return e.type===`parameter_declaration`&&e.childForFieldName(`declarator`)===null&&e.childForFieldName(`type`)?.text===`void`}function E(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=q(n)}return e.namedChildren.find(e=>e.type===`formal_parameters`||e.type===`parameter_list`)}const ee=new Set([`function_definition`,`constructor_declaration`,`compact_constructor_declaration`,`function_signature_item`]);function te(e){return!ee.has(e.type)||e.childForFieldName(`body`)!==null||e.namedChildren.some(e=>e.type===`try_statement`)}function D(e){return(e.type===`block`||e.type===`do_block`)&&e.parent?.type===`lambda`}function O(e,t){return t.has(e.type)&&!D(e)}const k=new Set([`switch_statement`,`switch_expression`,`expression_switch_statement`,`type_switch_statement`,`select_statement`,`match_expression`,`match_statement`,`case`,`case_match`]),A=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`]),j=new Set([`if_statement`,`if_expression`,`if`,`unless`]),M=new WeakMap;function ne(e){M.delete(e)}function N(e){let t=M.get(e);return t||(t={functionNodes:new Set(e.functionNodeTypes),decisionNodes:new Set(e.decisionNodeTypes),nestingNodes:new Set(e.nestingNodeTypes)},M.set(e,t)),t}function P(e,t){return{cognitiveComplexity:0,nestingSensitiveCount:0,nestingDepth:0,ncss:0,hasOwnNcssContribution:!1,entryCognitiveNesting:e,entryStructuralNesting:t}}function re(e,t){let{functionNodes:n,decisionNodes:r,nestingNodes:i}=N(t),{countable:a,containers:s}=o(t),l=new Map,u=[P(0,0)];function d(e,t,o,f,p,m){let h=e.type===`class_body`&&f;h&&(p=!0,o+=1);let g=O(e,n);g&&(f&&!m&&(o+=1),f=!0);let _=u.at(-1),v=t+o-_.entryCognitiveNesting,y=!p&&!g,b=e.isNamed&&r.has(e.type)&&!V(e),x=e.isNamed&&A.has(e.type),S=e.isNamed&&(i.has(e.type)||e.type===`else`&&(e.parent?.type===`case`||e.parent?.type===`case_match`)),C=b&&B(e);b&&!x&&(C?_.cognitiveComplexity+=1:(_.cognitiveComplexity+=1+v,_.nestingSensitiveCount+=1)),e.isNamed&&k.has(e.type)&&(_.cognitiveComplexity+=1+v,_.nestingSensitiveCount+=1),_.cognitiveComplexity+=F(e),I(e)&&(_.cognitiveComplexity+=1),H(e)&&L(e)&&(_.cognitiveComplexity+=1),z(e)&&(_.cognitiveComplexity+=1);let w=S&&!C?t+1:t;y&&(_.nestingDepth=Math.max(_.nestingDepth,w-_.entryStructuralNesting)),g&&u.push(P(w+o,w));let T=c(e,a,s),E=u.at(-1);E.ncss+=T,g&&T>0&&(E.hasOwnNcssContribution=!0);for(let t of e.children)d(t,w,o,f,!g&&p,h);if(g){let t=u.pop();l.set(e.id,{cognitiveComplexity:t.cognitiveComplexity,nestingDepth:t.nestingDepth,ncss:t.ncss+ +!t.hasOwnNcssContribution});let n=u.at(-1);n.cognitiveComplexity+=t.cognitiveComplexity+t.nestingSensitiveCount*(t.entryCognitiveNesting-n.entryCognitiveNesting),n.nestingSensitiveCount+=t.nestingSensitiveCount,n.ncss+=t.ncss}}return d(e,0,0,!1,!1,!1),l}function ie(e,t){let n=0,r=0,{functionNodes:i,decisionNodes:a,nestingNodes:o}=N(t);function s(e,t,c,l,u){let d=e.type===`class_body`&&l;d&&(c+=1),O(e,i)&&(l&&!u&&(c+=1),l=!0);let f=t+c,p=e.isNamed&&a.has(e.type)&&!V(e),m=e.isNamed&&A.has(e.type),h=e.isNamed&&(o.has(e.type)||e.type===`else`&&(e.parent?.type===`case`||e.parent?.type===`case_match`)),g=p&&B(e);p&&!m&&(n+=g?1:1+f),e.isNamed&&k.has(e.type)&&(n+=1+f),n+=F(e),I(e)&&(n+=1),H(e)&&L(e)&&(n+=1),z(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 F(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=>j.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`&&!j.has(r.type)&&(t+=1)}return t}function I(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.has(e.type)):!1}const ae=new Set([`parenthesized_expression`,`parenthesized_statements`]);function L(e){let t=e.parent;if(!t)return!0;let n=t.parent;for(;n&&ae.has(n.type);)n=n.parent;return!n||n.type!==t.type||R(oe(n))!==R(e.text)}function R(e){return e===`and`?`&&`:e===`or`?`||`:e}function oe(e){let t=e.childForFieldName(`operator`);return t?t.text:e.children.find(e=>!e.isNamed&&d.has(e.text))?.text}function z(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 B(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 V(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 se=new Set([`binary_expression`,`binary`,`boolean_operator`]);function H(e){if(e.isNamed||!d.has(e.text))return!1;let t=e.parent;return t!==null&&se.has(t.type)}function ce(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 W(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}le(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 W(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 le(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 ue(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(m.has(e.type)){Q(r,t.slice(e.startIndex,e.endIndex));return}if(e.childCount===0){let i=t.slice(e.startIndex,e.endIndex);p.has(e.type)?Q(r,i):(f.has(i)||f.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}}function de(e){let t=me(e);if(t)return t;let n=e.childForFieldName(`name`);if(n)return n.text;let r=fe(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`?K(i.childForFieldName(`declarator`)):e.type===`func_literal`&&i.type===`expression_list`?Y(e,i):e.type===`lambda`&&i.type===`assignment`?J(i):(e.type===`block`||e.type===`do_block`)&&pe(i)?i.parent?.type===`assignment`?J(i.parent):void 0:i.childForFieldName(`name`)?.text}}function fe(e){return K(e.childForFieldName(`declarator`))}function K(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=q(t)}}function q(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 J(e){let t=e.childForFieldName(`left`);return t?.type===`identifier`||t?.type===`constant`?t.text:void 0}function pe(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 Y(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}export{h as TreeMeasurer,x as collectCrossFileDuplicationFileData,b as collectDuplicationCandidates,v as defaultMeasurer,y as measureCode};
1
+ import{createLanguageRegistry as e}from"./languages.js";import{collectCrossFileDataNative as t,collectFunctionTokenSequencesNative as n,measureCodeNative as r}from"./nativeMetrics.js";var i=class{registry=e();getSupportedLanguages(){return[...new Set([...this.registry.values()].map(e=>e.name))]}measure(e,t){let n=this.resolveLanguage(t.language),i=t.includeSyntaxTree??!1;return a(r(e,n.name,i,t.duplication),i)}collectDuplicationCandidates(e,t){return this.collectCrossFileDuplicationFileData(e,t).candidates}collectCrossFileDuplicationFileData(e,n){let r=this.resolveLanguage(n.language),i=t(e,r.name,n.duplication?.minTokens);return{candidates:i.candidates,tokens:i.tokens,containerStatements:i.containerStatements,codeLineNumbers:new Set(i.codeLineNumbers)}}collectFunctionTokenSequences(e,t){let r=this.resolveLanguage(t.language);return n(e,r.name)}resolveLanguage(e){let t=this.registry.get(e);if(!t)throw Error(`Unsupported language: ${e}`);return t}};function a(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:o(e.halsteadCounts),depDegree:e.depDegree})),cognitiveComplexity:e.cognitiveComplexity,maxCognitiveComplexity:e.maxCognitiveComplexity,nestingDepth:e.nestingDepth,ncssCount:e.ncssCount,duplication:e.duplication,halstead:o(e.halsteadCounts),syntaxTree:t?e.syntaxTree:void 0}}function o(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 s=new i;function c(e,t){return s.measure(e,t)}function l(e,t){return s.collectDuplicationCandidates(e,t)}function u(e,t){return s.collectFunctionTokenSequences(e,t)}function d(e,t){return s.collectCrossFileDuplicationFileData(e,t)}export{i as TreeMeasurer,d as collectCrossFileDuplicationFileData,l as collectDuplicationCandidates,u as collectFunctionTokenSequences,s as defaultMeasurer,c as measureCode};
2
2
  //# sourceMappingURL=metrics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["import Parser from 'tree-sitter';\nimport {\n collectCrossFileDuplicateCandidates,\n defaultDuplicationOptions,\n measureDuplication,\n type CrossFileDuplicateCandidate,\n type CrossFileDuplicationFileData,\n} from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport { commentNodeTypes, countNcss, getNcssSets, invalidateNcssSetsCache, ncssContribution } from './ncss.js';\nimport { measureWithNativeBackend, type NativeHalsteadCounts, type NativeMetricsPayload } from './nativeMetrics.js';\nimport type {\n CodeMetrics,\n FunctionMetrics,\n HalsteadMetrics,\n LanguageDefinition,\n LanguageName,\n MeasureOptions,\n} from './types.js';\n\nconst booleanOperators = new Set(['&&', '||', 'and', 'or']);\nconst operatorTexts = new Set([\n '+',\n '-',\n '*',\n '/',\n '%',\n '**',\n '=',\n '+=',\n '-=',\n '*=',\n '/=',\n '%=',\n '==',\n '!=',\n '===',\n '!==',\n '<',\n '<=',\n '>',\n '>=',\n '!',\n '~',\n '&',\n '|',\n '^',\n '++',\n '--',\n '<<',\n '>>',\n '>>>',\n '=>',\n '**=',\n '<<=',\n '>>=',\n '>>>=',\n '&=',\n '|=',\n '^=',\n '&&=',\n '||=',\n '??=',\n '??',\n '?.',\n '?',\n '//',\n '//=',\n '@',\n '@=',\n ':=',\n '<-',\n '<=>',\n '=~',\n '..',\n '...',\n '..=',\n '&&',\n '||',\n '!~',\n '&^',\n '&^=',\n '&.',\n // Member access/qualification are classical Halstead operators (floats and range/spread tokens\n // are distinct leaves, so `.` cannot collide with them). `->` also captures Python/Rust\n // return-type arrows, consistent with the counted `=>`.\n '.',\n '->',\n '::',\n '->*',\n '.*',\n 'sizeof',\n 'alignof',\n 'defined?',\n 'as',\n // C++ alternative operator tokens parse as anonymous leaves like their symbolic forms.\n 'bitand',\n 'bitor',\n 'xor',\n 'compl',\n 'and_eq',\n 'or_eq',\n 'xor_eq',\n 'not_eq',\n 'and',\n 'or',\n 'not',\n 'in',\n 'is',\n 'instanceof',\n 'typeof',\n 'new',\n 'delete',\n 'return',\n 'throw',\n 'raise',\n 'yield',\n 'await',\n 'co_await',\n 'co_yield',\n 'co_return',\n 'break',\n 'continue',\n]);\n\nconst operandNodeTypes = new Set([\n 'identifier',\n 'property_identifier',\n 'field_identifier',\n 'type_identifier',\n 'constant',\n 'instance_variable',\n 'class_variable',\n 'global_variable',\n 'simple_symbol',\n 'self',\n 'this',\n 'super',\n // C/C++/Rust built-in types are leaves of their own node type, unlike Go's `type_identifier`.\n 'primitive_type',\n 'boolean_type',\n 'void_type',\n 'auto',\n 'number',\n 'integer',\n 'float',\n 'integer_literal',\n 'float_literal',\n 'int_literal',\n 'rune_literal',\n 'imaginary_literal',\n 'number_literal',\n 'decimal_integer_literal',\n 'hex_integer_literal',\n 'octal_integer_literal',\n 'binary_integer_literal',\n 'decimal_floating_point_literal',\n 'hex_floating_point_literal',\n 'string',\n 'string_literal',\n // Go raw strings are leaves with no content child, unlike Rust/C++ `raw_string_literal`s.\n 'raw_string_literal',\n 'string_fragment',\n 'multiline_string_fragment',\n 'string_content',\n 'raw_string_content',\n 'template_string',\n 'character_literal',\n 'char_literal',\n 'character',\n 'true',\n 'false',\n 'null',\n 'null_literal',\n 'undefined',\n 'nil',\n 'none',\n]);\n\n/**\n * Non-leaf literals counted as one Halstead operand without descending: Go interpreted strings\n * have no content leaf at all, and regex literals would otherwise count their `/` delimiters as\n * division operators. Interpolated regex contents are deliberately swallowed by the atom.\n */\n// C++ user-defined literals (`42_km`) are atomic too, keeping their suffix in the operand identity,\n// and multi-token built-in types (Java `int`, C `unsigned long`) wrap anonymous keyword leaves so\n// they count as one operand.\nconst atomicOperandNodeTypes = new Set([\n 'interpreted_string_literal',\n 'regex',\n 'user_defined_literal',\n 'integral_type',\n 'floating_point_type',\n 'sized_type_specifier',\n 'placeholder_type_specifier',\n]);\n\ninterface ComplexityResult {\n cognitiveComplexity: number;\n nestingDepth: number;\n}\n\ninterface CommentSpan {\n line: number;\n startColumn: number;\n endColumn: number;\n}\n\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n registerLanguage(language: LanguageDefinition): void {\n // Re-registering may carry mutated node-type arrays; drop derived caches so they rebuild.\n invalidateComplexityNodeSetsCache(language);\n invalidateNcssSetsCache(language);\n this.registry.set(language.name, language);\n for (const alias of language.aliases ?? []) {\n this.registry.set(alias, language);\n }\n }\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.registry.get(options.language);\n if (!language) {\n throw new Error(`Unsupported language: ${options.language}`);\n }\n\n // The native backend implements only the default duplication settings, so custom settings\n // measure through the TypeScript backend instead of silently ignoring them.\n const nativePayload = usesDefaultDuplicationOptions(options)\n ? measureWithNativeBackend(code, language, options.includeSyntaxTree ?? false)\n : undefined;\n if (nativePayload) {\n return assembleNativeMetrics(nativePayload, options.includeSyntaxTree ?? false);\n }\n\n const root = parseRoot(code, language);\n const functions = collectNodes(root, new Set(language.functionNodeTypes)).filter(\n (node) => !isLambdaBodyBlock(node) && isImplementedFunction(node)\n );\n const functionMetrics = collectFunctionMetrics(root, functions, language);\n const globalComplexity = measureComplexity(root, language);\n const { lines, codeLineNumbers } = classifyLines(code, root);\n\n return {\n language: language.name,\n bytes: Buffer.byteLength(code),\n lines,\n functions: functionMetrics,\n cognitiveComplexity: globalComplexity.cognitiveComplexity,\n maxCognitiveComplexity: maxCognitiveComplexity(functionMetrics),\n nestingDepth: globalComplexity.nestingDepth,\n ncssCount: countNcss(root, language),\n duplication: measureDuplication(root, codeLineNumbers, options.duplication),\n halstead: measureHalstead(root, code),\n syntaxTree: options.includeSyntaxTree ? root.toString() : undefined,\n };\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication. Always measured by the\n * TypeScript backend.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.registry.get(options.language);\n if (!language) {\n throw new Error(`Unsupported language: ${options.language}`);\n }\n const root = parseRoot(code, language);\n return {\n ...collectCrossFileDuplicateCandidates(root, options.duplication),\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: classifyLines(code, root).codeLineNumbers,\n };\n }\n}\n\nfunction parseRoot(code: string, language: LanguageDefinition): Parser.SyntaxNode {\n const parser = new Parser();\n parser.setLanguage(language.parserLanguage);\n return parser.parse(code, undefined, { bufferSize: code.length + 1 }).rootNode;\n}\n\nfunction usesDefaultDuplicationOptions(options: MeasureOptions): boolean {\n const duplication = options.duplication;\n return (\n (duplication?.minTokens ?? defaultDuplicationOptions.minTokens) === defaultDuplicationOptions.minTokens &&\n (duplication?.maxGapTokens ?? defaultDuplicationOptions.maxGapTokens) === defaultDuplicationOptions.maxGapTokens &&\n (duplication?.minSimilarityPercent ?? defaultDuplicationOptions.minSimilarityPercent) ===\n defaultDuplicationOptions.minSimilarityPercent\n );\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has exactly the shape the TypeScript backend\n * produces, including explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n })),\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction collectFunctionMetrics(\n root: Parser.SyntaxNode,\n functions: Parser.SyntaxNode[],\n language: LanguageDefinition\n): FunctionMetrics[] {\n const bodyMetricsByNodeId = measureFunctionBodyMetrics(root, language);\n return functions.map((node) => {\n const bodyMetrics = bodyMetricsByNodeId.get(node.id);\n if (!bodyMetrics) {\n throw new Error(`missing body metrics for function node at line ${node.startPosition.row + 1}`);\n }\n return {\n name: findFunctionName(node),\n nodeType: node.type,\n startLine: node.startPosition.row + 1,\n startColumn: node.startPosition.column,\n endLine: node.endPosition.row + 1,\n cognitiveComplexity: bodyMetrics.cognitiveComplexity,\n nestingDepth: bodyMetrics.nestingDepth,\n ncss: bodyMetrics.ncss,\n parameterCount: countParameters(node),\n };\n });\n}\n\n/** Counts declared parameters of a function/method, ignoring punctuation and comments. */\nfunction countParameters(node: Parser.SyntaxNode): number {\n // An unparenthesized arrow-function parameter (`x => x + 1`) is a bare `parameter` field.\n if (node.childForFieldName('parameter')) {\n return 1;\n }\n\n const parametersNode = findParametersNode(node);\n if (!parametersNode) {\n return 0;\n }\n\n // A Java bare lambda parameter (`x -> x + 1`) puts a lone identifier in the `parameters` field.\n if (parametersNode.type === 'identifier') {\n return 1;\n }\n\n // Ruby block-locals after `;` (`{ |x; memo| ... }`) occupy `locals` fields and receive no arguments.\n const blockLocalIds = new Set(findChildrenByFieldName(parametersNode, 'locals').map((child) => child.id));\n // Rust's `self` and Java's explicit receiver (`void f(X this)`) are not declared parameters, and\n // C/C++ `f(void)` declares none. A Ruby block parameter (`&blk`) binds the block, which call\n // sites pass outside the argument list, so it counts toward no arity.\n let count = 0;\n for (const child of parametersNode.namedChildren) {\n if (\n child.type === 'comment' ||\n child.type === 'self_parameter' ||\n child.type === 'receiver_parameter' ||\n child.type === 'block_parameter' ||\n // Python's PEP 570/3102 markers (`/`, `*`) separate parameter kinds but bind nothing.\n child.type === 'positional_separator' ||\n child.type === 'keyword_separator' ||\n blockLocalIds.has(child.id) ||\n isVoidParameter(child)\n ) {\n continue;\n }\n // Go declares several names per declaration (`a, b int`); each name is a parameter.\n count += child.type === 'parameter_declaration' ? Math.max(1, findChildrenByFieldName(child, 'name').length) : 1;\n }\n // C++ C-style varargs (`int f(int a, ...)`) leave `...` as an anonymous token, unlike C's named\n // `variadic_parameter`.\n const anonymousVariadicCount = parametersNode.children.filter(\n (child) => !child.isNamed && child.text === '...'\n ).length;\n return count + anonymousVariadicCount;\n}\n\n/** C/C++ `int f(void)` has a `parameter_declaration` whose type is a bare `void` with no declarator. */\nfunction isVoidParameter(node: Parser.SyntaxNode): boolean {\n return (\n node.type === 'parameter_declaration' &&\n node.childForFieldName('declarator') === null &&\n node.childForFieldName('type')?.text === 'void'\n );\n}\n\nfunction findParametersNode(node: Parser.SyntaxNode): Parser.SyntaxNode | undefined {\n const direct = node.childForFieldName('parameters');\n if (direct) {\n return direct;\n }\n\n // A Java compact constructor implicitly takes the record's components, declared on the\n // `record_declaration` two levels up (via `class_body`).\n if (node.type === 'compact_constructor_declaration') {\n return node.parent?.parent?.childForFieldName('parameters') ?? undefined;\n }\n\n // C/C++ parameters hang off the (possibly pointer/reference-wrapped) declarator, not the\n // definition itself.\n let declarator: Parser.SyntaxNode | null | undefined = node.childForFieldName('declarator');\n while (declarator) {\n const parameters = declarator.childForFieldName('parameters');\n if (parameters) {\n return parameters;\n }\n declarator = nextDeclarator(declarator);\n }\n\n return node.namedChildren.find((child) => child.type === 'formal_parameters' || child.type === 'parameter_list');\n}\n\n/**\n * C++ `function_definition` also covers pure-virtual/`= default`/`= delete` members; those have no\n * `body` and are signatures, not implementations, matching how TypeScript method signatures are\n * excluded. Java `method_declaration` is NOT here: PMD reports abstract/interface methods as\n * methods (NCSS 1), so bodyless Java methods stay in the function list.\n */\nconst bodyRequiredFunctionTypes = new Set([\n 'function_definition',\n 'constructor_declaration',\n 'compact_constructor_declaration',\n // Rust trait method signatures (`fn required(&self);`) never carry a body.\n 'function_signature_item',\n]);\n\nfunction isImplementedFunction(node: Parser.SyntaxNode): boolean {\n if (!bodyRequiredFunctionTypes.has(node.type) || node.childForFieldName('body') !== null) {\n return true;\n }\n\n // C++ constructor/destructor function-try-blocks carry their `try_statement` outside the\n // `body` field; they are implementations, unlike `= 0`/`= default`/`= delete` members.\n return node.namedChildren.some((child) => child.type === 'try_statement');\n}\n\n/**\n * A Ruby stabby lambda (`->(x) { ... }`) wraps its body in a `block`/`do_block`, which is itself a\n * function node type; the wrapper is part of the lambda, not a separate function, so it must not\n * count as one or act as a nested-function boundary.\n */\nfunction isLambdaBodyBlock(node: Parser.SyntaxNode): boolean {\n return (node.type === 'block' || node.type === 'do_block') && node.parent?.type === 'lambda';\n}\n\nfunction isFunctionBoundary(node: Parser.SyntaxNode, functionNodeTypes: Set<string>): boolean {\n return functionNodeTypes.has(node.type) && !isLambdaBodyBlock(node);\n}\n\n// Sonar cognitive complexity charges a switch/match once as a whole, not per case label. Only\n// named nodes are consulted, so anonymous keyword tokens never match.\nconst switchLikeNodeTypes = new Set([\n 'switch_statement',\n 'switch_expression',\n 'expression_switch_statement',\n 'type_switch_statement',\n 'select_statement',\n 'match_expression',\n 'match_statement',\n 'case',\n 'case_match',\n]);\n\n// Per-case decision nodes add no cognitive point because the switch itself carries the cost.\nconst caseClauseNodeTypes = new Set([\n 'case_clause',\n 'switch_case',\n 'switch_block_statement_group',\n 'switch_rule',\n 'case_statement',\n 'expression_case',\n 'type_case',\n 'communication_case',\n 'match_arm',\n 'when',\n 'in_clause',\n]);\n\nconst ifLikeNodeTypes = new Set(['if_statement', 'if_expression', 'if', 'unless']);\n\ninterface ComplexityNodeSets {\n functionNodes: Set<string>;\n decisionNodes: Set<string>;\n nestingNodes: Set<string>;\n}\n\n// Cached per language: measureComplexity runs once per function plus once per file, so per-call\n// Set construction would add a measurable constant factor on large files.\nconst complexityNodeSetsCache = new WeakMap<LanguageDefinition, ComplexityNodeSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nfunction invalidateComplexityNodeSetsCache(language: LanguageDefinition): void {\n complexityNodeSetsCache.delete(language);\n}\n\nfunction getComplexityNodeSets(language: LanguageDefinition): ComplexityNodeSets {\n let sets = complexityNodeSetsCache.get(language);\n if (!sets) {\n sets = {\n functionNodes: new Set(language.functionNodeTypes),\n decisionNodes: new Set(language.decisionNodeTypes),\n nestingNodes: new Set(language.nestingNodeTypes),\n };\n complexityNodeSetsCache.set(language, sets);\n }\n return sets;\n}\n\ninterface FunctionBodyMetrics {\n cognitiveComplexity: number;\n nestingDepth: number;\n ncss: number;\n}\n\n/** Accumulator for one function body during measureFunctionBodyMetrics' post-order pass. */\ninterface FunctionBodyFrame {\n cognitiveComplexity: number;\n /** Count of `1 + nesting` cognitive increments, for re-basing on hoist into the parent frame. */\n nestingSensitiveCount: number;\n nestingDepth: number;\n ncss: number;\n hasOwnNcssContribution: boolean;\n /** Cognitive nesting (structural nesting + function/class bonuses) carried into this body. */\n entryCognitiveNesting: number;\n /** Structural nesting carried into this body (bonuses excluded), for nesting depth. */\n entryStructuralNesting: number;\n}\n\nfunction createFrame(entryCognitiveNesting: number, entryStructuralNesting: number): FunctionBodyFrame {\n return {\n cognitiveComplexity: 0,\n nestingSensitiveCount: 0,\n nestingDepth: 0,\n ncss: 0,\n hasOwnNcssContribution: false,\n entryCognitiveNesting,\n entryStructuralNesting,\n };\n}\n\n/**\n * Per-function complexity and NCSS for every function boundary, in one post-order pass so each\n * node is visited once instead of once per enclosing function (issue #35). A function's metrics\n * are its own-body contributions plus, per directly nested function, that function's\n * already-computed totals: NCSS hoists as-is; cognitive complexity re-bases the nested function's\n * nesting-sensitive increments (each worth `1 + nesting`) by the nesting offset at the embedding\n * site, while flat increments (else branches, boolean-operator sequences, chain continuations,\n * jumps, guards) hoist unchanged; nesting depth describes the own body only, so it does not hoist.\n */\nfunction measureFunctionBodyMetrics(\n root: Parser.SyntaxNode,\n language: LanguageDefinition\n): Map<number, FunctionBodyMetrics> {\n const { functionNodes, decisionNodes, nestingNodes } = getComplexityNodeSets(language);\n const { countable, containers } = getNcssSets(language);\n const results = new Map<number, FunctionBodyMetrics>();\n // frames[0] is a sentinel for top-level code; its accumulation is discarded.\n const frames: FunctionBodyFrame[] = [createFrame(0, 0)];\n\n function visit(\n current: Parser.SyntaxNode,\n currentNesting: number,\n functionNestingBonus: number,\n insideFunction: boolean,\n insideNestedRegion: boolean,\n insideChargedClassBody: boolean\n ): void {\n // A class body nested in a function (anonymous/local classes) raises the cognitive nesting\n // level once for everything inside it — PMD charges the class body, not the methods it holds\n // (verified: an anonymous-class instance initializer's `if` costs 1 + 1 nesting), so methods\n // directly inside a charged class body skip the function-boundary bonus.\n const isChargedClassBody = current.type === 'class_body' && insideFunction;\n if (isChargedClassBody) {\n insideNestedRegion = true;\n functionNestingBonus += 1;\n }\n const opensFrame = isFunctionBoundary(current, functionNodes);\n if (opensFrame) {\n if (insideFunction && !insideChargedClassBody) {\n functionNestingBonus += 1;\n }\n insideFunction = true;\n }\n // The node's own increments target the frame it is embedded in, not the one it opens; a\n // frame-opening or charged-class-body node contributes nothing to that frame's own body\n // (nesting), matching the per-function traversal this pass replaces.\n const frame = frames.at(-1) as FunctionBodyFrame;\n const relativeNesting = currentNesting + functionNestingBonus - frame.entryCognitiveNesting;\n const countsForOwnBody = !insideNestedRegion && !opensFrame;\n\n // Anonymous keyword tokens can share a type with named nodes (Ruby's `if` node contains an\n // `if` keyword token), so only named nodes count as decisions.\n const isDecision = current.isNamed && decisionNodes.has(current.type) && !isDefaultSwitchBranch(current);\n const isCaseClause = current.isNamed && caseClauseNodeTypes.has(current.type);\n // Ruby's `case ... else` arm is an `else` node; like every other language's default branch it\n // nests its contents inside the switch (it cannot go in the Ruby nesting set because\n // `if`/`begin` else branches would then double-nest under their already-nesting parent).\n const isNesting =\n current.isNamed &&\n (nestingNodes.has(current.type) ||\n (current.type === 'else' && (current.parent?.type === 'case' || current.parent?.type === 'case_match')));\n // `elsif`/`elif`/`else if` continue a flat chain: they add a decision without a nesting\n // surcharge, and their bodies stay at the chain's nesting level (Sonar cognitive-complexity\n // semantics); genuinely nested conditionals inside those bodies still deepen.\n const isContinuation = isDecision && isFlatChainContinuation(current);\n\n if (isDecision && !isCaseClause) {\n if (isContinuation) {\n frame.cognitiveComplexity += 1;\n } else {\n frame.cognitiveComplexity += 1 + relativeNesting;\n frame.nestingSensitiveCount += 1;\n }\n }\n if (current.isNamed && switchLikeNodeTypes.has(current.type)) {\n frame.cognitiveComplexity += 1 + relativeNesting;\n frame.nestingSensitiveCount += 1;\n }\n // A plain `else` branch adds one flat cognitive point; `else if` chains are charged on the\n // nested if instead.\n frame.cognitiveComplexity += countPlainElseBranches(current);\n // Sonar charges flow-breaking jumps: goto and labeled break/continue add one flat point.\n if (isFlowBreakingJump(current)) {\n frame.cognitiveComplexity += 1;\n }\n\n // A sequence of identical boolean operators reads as one condition, so only the operator\n // starting a sequence adds a cognitive point (Sonar spec).\n if (isBooleanOperator(current) && startsBooleanOperatorSequence(current)) {\n frame.cognitiveComplexity += 1;\n }\n\n // Pattern guards (Java `when`, Ruby `in y if ...`, Python `case n if ...`, Rust `n if ... =>`)\n // add one independent execution path without nesting.\n if (isPatternGuard(current)) {\n frame.cognitiveComplexity += 1;\n }\n\n const childNesting = isNesting && !isContinuation ? currentNesting + 1 : currentNesting;\n if (countsForOwnBody) {\n frame.nestingDepth = Math.max(frame.nestingDepth, childNesting - frame.entryStructuralNesting);\n }\n\n if (opensFrame) {\n frames.push(createFrame(childNesting + functionNestingBonus, childNesting));\n }\n // The node's NCSS contribution belongs to the innermost frame whose subtree holds it — the\n // frame the node opens, if any (per-function NCSS includes the declaration node itself).\n const ownNcss = ncssContribution(current, countable, containers);\n const ncssFrame = frames.at(-1) as FunctionBodyFrame;\n ncssFrame.ncss += ownNcss;\n if (opensFrame && ownNcss > 0) {\n ncssFrame.hasOwnNcssContribution = true;\n }\n\n for (const child of current.children) {\n visit(\n child,\n childNesting,\n functionNestingBonus,\n insideFunction,\n opensFrame ? false : insideNestedRegion,\n isChargedClassBody\n );\n }\n\n if (opensFrame) {\n const closed = frames.pop() as FunctionBodyFrame;\n results.set(current.id, {\n cognitiveComplexity: closed.cognitiveComplexity,\n nestingDepth: closed.nestingDepth,\n // A function node without a countable declaration of its own (arrow functions, lambdas,\n // blocks) still counts 1 for the declaration itself.\n ncss: closed.ncss + (closed.hasOwnNcssContribution ? 0 : 1),\n });\n const parent = frames.at(-1) as FunctionBodyFrame;\n parent.cognitiveComplexity +=\n closed.cognitiveComplexity +\n closed.nestingSensitiveCount * (closed.entryCognitiveNesting - parent.entryCognitiveNesting);\n parent.nestingSensitiveCount += closed.nestingSensitiveCount;\n parent.ncss += closed.ncss;\n }\n }\n\n visit(root, 0, 0, false, false, false);\n return results;\n}\n\n/**\n * File-level complexity over the whole tree. Cognitive complexity charges nested function/lambda\n * content one nesting level deeper per function boundary crossed (Sonar spec); nesting depth\n * counts every node once.\n */\nfunction measureComplexity(node: Parser.SyntaxNode, language: LanguageDefinition): ComplexityResult {\n let cognitiveComplexity = 0;\n let nestingDepth = 0;\n const { functionNodes, decisionNodes, nestingNodes } = getComplexityNodeSets(language);\n\n function visit(\n current: Parser.SyntaxNode,\n currentNesting: number,\n functionNestingBonus: number,\n insideFunction: boolean,\n insideChargedClassBody: boolean\n ): void {\n // A class body nested in a function (anonymous/local classes) raises the cognitive nesting\n // level once for everything inside it — PMD charges the class body, not the methods it holds,\n // so methods directly inside a charged class body skip the function-boundary bonus.\n const isChargedClassBody = current.type === 'class_body' && insideFunction;\n if (isChargedClassBody) {\n functionNestingBonus += 1;\n }\n if (isFunctionBoundary(current, functionNodes)) {\n if (insideFunction && !insideChargedClassBody) {\n functionNestingBonus += 1;\n }\n insideFunction = true;\n }\n const cognitiveNesting = currentNesting + functionNestingBonus;\n\n // Anonymous keyword tokens can share a type with named nodes (Ruby's `if` node contains an\n // `if` keyword token), so only named nodes count as decisions.\n const isDecision = current.isNamed && decisionNodes.has(current.type) && !isDefaultSwitchBranch(current);\n const isCaseClause = current.isNamed && caseClauseNodeTypes.has(current.type);\n // Ruby's `case ... else` arm is an `else` node; like every other language's default branch it\n // nests its contents inside the switch (it cannot go in the Ruby nesting set because\n // `if`/`begin` else branches would then double-nest under their already-nesting parent).\n const isNesting =\n current.isNamed &&\n (nestingNodes.has(current.type) ||\n (current.type === 'else' && (current.parent?.type === 'case' || current.parent?.type === 'case_match')));\n // `elsif`/`elif`/`else if` continue a flat chain: they add a decision without a nesting\n // surcharge, and their bodies stay at the chain's nesting level (Sonar cognitive-complexity\n // semantics); genuinely nested conditionals inside those bodies still deepen.\n const isContinuation = isDecision && isFlatChainContinuation(current);\n\n if (isDecision && !isCaseClause) {\n cognitiveComplexity += isContinuation ? 1 : 1 + cognitiveNesting;\n }\n if (current.isNamed && switchLikeNodeTypes.has(current.type)) {\n cognitiveComplexity += 1 + cognitiveNesting;\n }\n // A plain `else` branch adds one flat cognitive point; `else if` chains are charged on the\n // nested if instead.\n cognitiveComplexity += countPlainElseBranches(current);\n // Sonar charges flow-breaking jumps: goto and labeled break/continue add one flat point.\n if (isFlowBreakingJump(current)) {\n cognitiveComplexity += 1;\n }\n\n // A sequence of identical boolean operators reads as one condition, so only the operator\n // starting a sequence adds a cognitive point (Sonar spec).\n if (isBooleanOperator(current) && startsBooleanOperatorSequence(current)) {\n cognitiveComplexity += 1;\n }\n\n // Pattern guards (Java `when`, Ruby `in y if ...`, Python `case n if ...`, Rust `n if ... =>`)\n // add one independent execution path without nesting.\n if (isPatternGuard(current)) {\n cognitiveComplexity += 1;\n }\n\n const childNesting = isNesting && !isContinuation ? currentNesting + 1 : currentNesting;\n nestingDepth = Math.max(nestingDepth, childNesting);\n\n for (const child of current.children) {\n visit(child, childNesting, functionNestingBonus, insideFunction, isChargedClassBody);\n }\n }\n\n for (const child of node.children) {\n visit(child, 0, 0, false, false);\n }\n\n return { cognitiveComplexity, nestingDepth };\n}\n\n/**\n * Plain else branches attached to `current`: an `else_clause`/Ruby `else` whose branch is not an\n * `else if` continuation, or a bare Java/Go `alternative:` statement without a clause wrapper.\n */\nfunction countPlainElseBranches(current: Parser.SyntaxNode): number {\n if (!current.isNamed) {\n return 0;\n }\n if (current.type === 'else') {\n // A Ruby `case ... else` is the default arm of a switch, which already counts as a whole\n // (sonar-ruby models it as a match case, not an else branch); `if`/`unless`/`begin` else\n // branches count one point each.\n return current.parent?.type === 'case' || current.parent?.type === 'case_match' ? 0 : 1;\n }\n if (current.type === 'else_clause') {\n return current.namedChildren.some((child) => ifLikeNodeTypes.has(child.type)) ? 0 : 1;\n }\n if (current.type !== 'if_statement' && current.type !== 'if_expression') {\n return 0;\n }\n let count = 0;\n for (let index = 0; index < current.childCount; index += 1) {\n const child = current.child(index);\n if (\n child &&\n current.fieldNameForChild(index) === 'alternative' &&\n child.type !== 'else_clause' &&\n child.type !== 'elif_clause' &&\n !ifLikeNodeTypes.has(child.type)\n ) {\n count += 1;\n }\n }\n return count;\n}\n\n/** goto, and break/continue that jump to a label (their only named child is the label). */\nfunction isFlowBreakingJump(node: Parser.SyntaxNode): boolean {\n if (!node.isNamed) {\n return false;\n }\n if (node.type === 'goto_statement') {\n return true;\n }\n // Rust jumps are expressions; `break value` carries a named expression child, so only an\n // explicit `label` child marks a labeled jump.\n if (node.type === 'break_expression' || node.type === 'continue_expression') {\n return node.namedChildren.some((child) => child.type === 'label' || child.type === 'loop_label');\n }\n // Comments are named children too (`break /* done */;`), so only non-comment children mark a\n // label.\n return (\n (node.type === 'break_statement' || node.type === 'continue_statement') &&\n node.namedChildren.some((child) => !commentNodeTypes.has(child.type))\n );\n}\n\n// Wrappers that are transparent when locating the enclosing boolean operation: PMD/Sonar keep a\n// sequence continuous across parentheses (`a && (b && c)` costs one point).\nconst parenthesizedNodeTypes = new Set(['parenthesized_expression', 'parenthesized_statements']);\n\n/**\n * Whether this boolean operator token starts a new sequence, i.e. its binary node is the root of a\n * run of same-operator binaries (possibly through parentheses). Only the root operator counts one\n * cognitive point: `a && b && c` and `a && (b && c)` cost one, `a && b || c` costs two, matching\n * the Sonar specification and PMD 7.26.0.\n */\nfunction startsBooleanOperatorSequence(token: Parser.SyntaxNode): boolean {\n const binary = token.parent;\n if (!binary) {\n return true;\n }\n let ancestor = binary.parent;\n while (ancestor && parenthesizedNodeTypes.has(ancestor.type)) {\n ancestor = ancestor.parent;\n }\n if (!ancestor || ancestor.type !== binary.type) {\n return true;\n }\n return normalizeBooleanOperator(findBooleanOperatorText(ancestor)) !== normalizeBooleanOperator(token.text);\n}\n\n/** C++ `and`/`or` are alternative spellings of `&&`/`||`, so mixing them keeps one sequence. */\nfunction normalizeBooleanOperator(text: string | undefined): string | undefined {\n if (text === 'and') {\n return '&&';\n }\n if (text === 'or') {\n return '||';\n }\n return text;\n}\n\nfunction findBooleanOperatorText(binaryNode: Parser.SyntaxNode): string | undefined {\n const operator = binaryNode.childForFieldName('operator');\n if (operator) {\n return operator.text;\n }\n return binaryNode.children.find((child) => !child.isNamed && booleanOperators.has(child.text))?.text;\n}\n\n/** Java `guard`, Ruby `if_guard`, Python `if_clause`, and Rust guards inside `match_pattern`. */\nfunction isPatternGuard(node: Parser.SyntaxNode): boolean {\n if (!node.isNamed) {\n return false;\n }\n if (node.type === 'guard' || node.type === 'if_guard' || node.type === 'unless_guard' || node.type === 'if_clause') {\n return true;\n }\n return node.type === 'match_pattern' && node.children.some((child) => !child.isNamed && child.type === 'if');\n}\n\n/** Ruby `elsif`, Python `elif`, and `else if` (an if node in an else/alternative position). */\nfunction isFlatChainContinuation(node: Parser.SyntaxNode): boolean {\n if (node.type === 'elsif' || node.type === 'elif_clause') {\n return true;\n }\n if (node.type !== 'if_statement' && node.type !== 'if_expression' && node.type !== 'if') {\n return false;\n }\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // JS/C/C++/Rust wrap `else if` in an else clause; Java/Go put it directly in `alternative`.\n return parent.type === 'else_clause' || parent.childForFieldName('alternative')?.id === node.id;\n}\n\n/**\n * C/C++ `default:` shares the `case_statement` node type with `case` (only `case` has a `value`\n * field), and Java's default group/rule carries an expressionless `switch_label`; a default branch\n * adds no decision, though its contents still nest inside the switch like any other arm.\n */\nfunction isDefaultSwitchBranch(node: Parser.SyntaxNode): boolean {\n if (node.type === 'case_statement') {\n return node.childForFieldName('value') === null;\n }\n\n if (node.type === 'switch_block_statement_group' || node.type === 'switch_rule') {\n const label = node.namedChildren.find((child) => child.type === 'switch_label');\n return label !== undefined && label.namedChildCount === 0;\n }\n\n // Python `case _:` / `case y:` and Rust `_ =>` fallback arms are unconditional like `default`\n // (a bare Python name is always a capture); a guard is charged separately as a flat decision,\n // so the arm itself still adds nothing. Rust bare identifiers are NOT suppressed: they can name\n // constants or unit variants, which the grammar cannot distinguish from captures.\n if (node.type === 'case_clause' || node.type === 'match_arm') {\n const pattern = node.namedChildren.find((child) => child.type === 'case_pattern' || child.type === 'match_pattern');\n if (!pattern) {\n return false;\n }\n if (pattern.child(0)?.type === '_' && (pattern.childCount === 1 || pattern.child(1)?.type === 'if')) {\n return true;\n }\n const soleChild = pattern.namedChildCount === 1 ? pattern.namedChild(0) : undefined;\n return (\n node.type === 'case_clause' &&\n soleChild?.type === 'dotted_name' &&\n soleChild.namedChildCount === 1 &&\n soleChild.namedChild(0)?.type === 'identifier'\n );\n }\n\n // Ruby `in y` binds unconditionally (bare lowercase names are variable captures; constants and\n // literals are tests).\n if (node.type === 'in_clause') {\n return node.namedChild(0)?.type === 'identifier';\n }\n\n return false;\n}\n\n/** Parents under which `&&`/`||`/`and`/`or` tokens are actual boolean operators. */\nconst booleanOperatorParentTypes = new Set(['binary_expression', 'binary', 'boolean_operator']);\n\n/**\n * The parent guard is required because the same tokens appear in non-boolean syntax: C++ rvalue\n * references (`int&&`), ref-qualifiers, `operator&&`, and Rust's empty closure parameter list\n * (`|| 5`) must not count as decisions.\n */\nfunction isBooleanOperator(node: Parser.SyntaxNode): boolean {\n if (node.isNamed || !booleanOperators.has(node.text)) {\n return false;\n }\n\n const parent = node.parent;\n return parent !== null && booleanOperatorParentTypes.has(parent.type);\n}\n\nfunction collectNodes(root: Parser.SyntaxNode, nodeTypes: Set<string>): Parser.SyntaxNode[] {\n const nodes: Parser.SyntaxNode[] = [];\n\n function visit(node: Parser.SyntaxNode): void {\n if (nodeTypes.has(node.type)) {\n nodes.push(node);\n }\n\n for (const child of node.namedChildren) {\n visit(child);\n }\n }\n\n visit(root);\n return nodes;\n}\n\n/**\n * 1-based numbers of lines that are neither blank nor comment-only, matching measureLines'\n * classification so duplication line coverage and its code-line denominator agree.\n */\nfunction classifyLines(\n code: string,\n root: Parser.SyntaxNode\n): { lines: CodeMetrics['lines']; codeLineNumbers: Set<number> } {\n const sourceLines = code.length === 0 ? [] : code.split(/\\r\\n|\\n|\\r/);\n // Spans are bucketed by line so classification stays linear; scanning every span per line made\n // this pass quadratic on comment-heavy files.\n const commentSpansByLine = new Map<number, CommentSpan[]>();\n for (const span of collectCommentSpans(root)) {\n const spans = commentSpansByLine.get(span.line) ?? [];\n spans.push(span);\n commentSpansByLine.set(span.line, spans);\n }\n let blank = 0;\n let comment = 0;\n const codeLineNumbers = new Set<number>();\n\n for (const [index, line] of sourceLines.entries()) {\n if (line.trim() === '') {\n blank += 1;\n continue;\n }\n if (isCommentOnlyLine(line, commentSpansByLine.get(index) ?? [])) {\n comment += 1;\n } else {\n codeLineNumbers.add(index + 1);\n }\n }\n\n return {\n lines: {\n total: sourceLines.length,\n code: codeLineNumbers.size,\n comment,\n blank,\n },\n codeLineNumbers,\n };\n}\n\nfunction collectCommentSpans(root: Parser.SyntaxNode): CommentSpan[] {\n const spans: CommentSpan[] = [];\n\n function visit(node: Parser.SyntaxNode): void {\n if (node.type === 'comment' || node.type === 'line_comment' || node.type === 'block_comment') {\n for (let row = node.startPosition.row; row <= node.endPosition.row; row += 1) {\n spans.push({\n line: row,\n startColumn: row === node.startPosition.row ? node.startPosition.column : 0,\n endColumn: row === node.endPosition.row ? node.endPosition.column : Number.POSITIVE_INFINITY,\n });\n }\n }\n\n for (const child of node.namedChildren) {\n visit(child);\n }\n }\n\n visit(root);\n return spans;\n}\n\nfunction isCommentOnlyLine(line: string, relevantSpans: CommentSpan[]): boolean {\n if (relevantSpans.length === 0) {\n return false;\n }\n\n // A line may hold several comments (`/* one */ /* two */`), so every non-whitespace column must\n // be covered by the UNION of spans, not by a single span.\n for (let column = 0; column < line.length; column += 1) {\n if (/\\s/u.test(line[column] ?? ' ')) {\n continue;\n }\n if (!relevantSpans.some((span) => span.startColumn <= column && column < span.endColumn)) {\n return false;\n }\n }\n return true;\n}\n\nfunction measureHalstead(root: Parser.SyntaxNode, code: string): HalsteadMetrics {\n const operators = new Map<string, number>();\n const operands = new Map<string, number>();\n\n function visit(node: Parser.SyntaxNode): void {\n if (node.type === 'comment' || node.type === 'line_comment' || node.type === 'block_comment') {\n return;\n }\n\n if (atomicOperandNodeTypes.has(node.type)) {\n incrementCount(operands, code.slice(node.startIndex, node.endIndex));\n return;\n }\n\n // Operators are counted from leaf tokens only: keyword-named nodes (Ruby `return`, Python\n // `await`, ...) always contain a same-text anonymous keyword leaf, so counting the named node\n // as well would double-count.\n if (node.childCount === 0) {\n const text = code.slice(node.startIndex, node.endIndex);\n // Operands win over text matches so identifiers spelled like word operators (`cache.delete(...)`,\n // a Go parameter named `in`) stay operands; genuine keyword operators are anonymous leaves whose\n // types are never operand types. A blanket `isNamed` guard would break JS's named `optional_chain`.\n if (operandNodeTypes.has(node.type)) {\n incrementCount(operands, text);\n } else if ((operatorTexts.has(text) || operatorTexts.has(node.type)) && isCountableContextualToken(node, text)) {\n incrementCount(operators, text || node.type);\n }\n return;\n }\n\n for (const child of node.children) {\n visit(child);\n }\n }\n\n visit(root);\n\n return deriveHalsteadMetrics({\n distinctOperators: operators.size,\n distinctOperands: operands.size,\n totalOperators: sum(operators.values()),\n totalOperands: sum(operands.values()),\n });\n}\n\n/** Derives the full Halstead metrics from the four base counts (shared with the native backend). */\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nfunction findFunctionName(node: Parser.SyntaxNode): string | undefined {\n const wrappedName = findWrappedComponentName(node);\n if (wrappedName) {\n return wrappedName;\n }\n\n const nameNode = node.childForFieldName('name');\n if (nameNode) {\n return nameNode.text;\n }\n\n // C/C++ definitions name the function inside the (possibly pointer-wrapped) declarator chain.\n const declaratorName = findDeclaratorName(node);\n if (declaratorName) {\n return declaratorName;\n }\n\n const parent = node.parent;\n if (!parent) {\n return undefined;\n }\n\n // A Rust closure bound to a simple `let` identifier (`let add = |x| ...;`) takes that identifier\n // as its name, mirroring how JS arrow functions assigned to a variable are named, so calls to the\n // binding resolve as intra-file edges.\n if (node.type === 'closure_expression' && parent.type === 'let_declaration') {\n const patternNode = parent.childForFieldName('pattern');\n return patternNode?.type === 'identifier' ? patternNode.text : undefined;\n }\n\n // A C++ lambda assigned to a variable (`auto f = [](int x) { ... };`) takes the variable name,\n // like Rust `let` closures above, so calls to the binding resolve as intra-file edges.\n if (node.type === 'lambda_expression' && parent.type === 'init_declarator') {\n return unwrapDeclaratorName(parent.childForFieldName('declarator'));\n }\n\n // A Go func literal bound via `add := func...` or `var add = func...` takes the identifier at\n // the same list position; unpaired or non-identifier targets stay unnamed.\n if (node.type === 'func_literal' && parent.type === 'expression_list') {\n return findGoFuncLiteralName(node, parent);\n }\n\n // Ruby lambdas assigned to a name (`choose = ->(x) {...}` / `ADD = lambda { ... }`) take that\n // name; Ruby assignments use the `left` field, and `lambda { }` blocks hang off a `call`.\n if (node.type === 'lambda' && parent.type === 'assignment') {\n return findRubyAssignmentName(parent);\n }\n if ((node.type === 'block' || node.type === 'do_block') && isRubyLambdaCall(parent)) {\n return parent.parent?.type === 'assignment' ? findRubyAssignmentName(parent.parent) : undefined;\n }\n\n const parentName = parent.childForFieldName('name');\n return parentName?.text;\n}\n\nfunction findDeclaratorName(node: Parser.SyntaxNode): string | undefined {\n return unwrapDeclaratorName(node.childForFieldName('declarator'));\n}\n\n/**\n * Unwraps a C/C++ declarator chain to the declared name, handling parenthesized declarators\n * (function pointers), qualified names, destructors, and operator overloads explicitly; a\n * rightmost-identifier fallback would pick up parameter names from nested `function_declarator`s.\n */\nfunction unwrapDeclaratorName(declarator: Parser.SyntaxNode | null): string | undefined {\n let current: Parser.SyntaxNode | null | undefined = declarator;\n while (current) {\n switch (current.type) {\n case 'identifier':\n case 'field_identifier':\n case 'type_identifier':\n case 'destructor_name':\n case 'operator_name': {\n return current.text;\n }\n // A C++ conversion operator (`operator int()`) is its own declarator node whose text spans\n // the parameter list and qualifiers; only `operator <type>` is the name.\n case 'operator_cast': {\n return `operator ${current.childForFieldName('type')?.text ?? ''}`.trimEnd();\n }\n // Template specializations (`id<int>`) and qualified names both carry a `name` field.\n case 'template_function':\n case 'qualified_identifier': {\n current = current.childForFieldName('name');\n break;\n }\n default: {\n current = nextDeclarator(current);\n }\n }\n }\n return undefined;\n}\n\n/**\n * Steps into the inner declarator; `reference_declarator` and `parenthesized_declarator` do not\n * expose a `declarator` field in tree-sitter-cpp, so their sole named child is the inner node.\n */\nfunction nextDeclarator(node: Parser.SyntaxNode): Parser.SyntaxNode | undefined {\n const direct = node.childForFieldName('declarator');\n if (direct) {\n return direct;\n }\n if (node.type === 'reference_declarator' || node.type === 'parenthesized_declarator') {\n return node.namedChild(0) ?? undefined;\n }\n return undefined;\n}\n\nfunction findRubyAssignmentName(assignment: Parser.SyntaxNode): string | undefined {\n const leftNode = assignment.childForFieldName('left');\n return leftNode?.type === 'identifier' || leftNode?.type === 'constant' ? leftNode.text : undefined;\n}\n\nfunction isRubyLambdaCall(node: Parser.SyntaxNode): boolean {\n if (node.type !== 'call' || node.childForFieldName('receiver')) {\n return false;\n }\n const methodNode = node.childForFieldName('method');\n return methodNode?.type === 'identifier' && (methodNode.text === 'lambda' || methodNode.text === 'proc');\n}\n\nfunction findGoFuncLiteralName(node: Parser.SyntaxNode, expressionList: Parser.SyntaxNode): string | undefined {\n const holder = expressionList.parent;\n // Comments interleave with expressions in the list but have no matching binding target, so\n // positions are aligned over non-comment children on both sides.\n const values = expressionList.namedChildren.filter((child) => child.type !== 'comment');\n const valueIndex = values.findIndex((child) => child.id === node.id);\n if (!holder || valueIndex === -1) {\n return undefined;\n }\n\n if (holder.type === 'short_var_declaration') {\n const targets = holder.childForFieldName('left')?.namedChildren.filter((child) => child.type !== 'comment');\n return asGoBindingName(targets?.[valueIndex]);\n }\n\n if (holder.type === 'var_spec') {\n const target = findChildrenByFieldName(holder, 'name')[valueIndex];\n return asGoBindingName(target);\n }\n\n return undefined;\n}\n\n/** Go's blank identifier `_` discards the value and creates no callable binding. */\nfunction asGoBindingName(target: Parser.SyntaxNode | undefined): string | undefined {\n return target?.type === 'identifier' && target.text !== '_' ? target.text : undefined;\n}\n\nfunction findWrappedComponentName(node: Parser.SyntaxNode): string | undefined {\n let current: Parser.SyntaxNode | undefined = node;\n while (current) {\n const argumentsNode: Parser.SyntaxNode | null = current.parent;\n const callNode: Parser.SyntaxNode | null | undefined = argumentsNode?.parent;\n if (argumentsNode?.type !== 'arguments' || callNode?.type !== 'call_expression') {\n return undefined;\n }\n\n if (!isReactComponentWrapperCall(callNode)) {\n return undefined;\n }\n\n const declaratorNode = callNode.parent;\n if (declaratorNode?.type === 'variable_declarator') {\n return declaratorNode.childForFieldName('name')?.text;\n }\n\n current = callNode;\n }\n\n return undefined;\n}\n\nfunction isReactComponentWrapperCall(node: Parser.SyntaxNode): boolean {\n const calleeNode = node.childForFieldName('function') ?? node.namedChild(0);\n return (\n calleeNode?.text === 'memo' ||\n calleeNode?.text === 'React.memo' ||\n calleeNode?.text === 'forwardRef' ||\n calleeNode?.text === 'React.forwardRef'\n );\n}\n\n/** Ternary/conditional and Rust try parents make `?` an operator; TS optional markers do not. */\nconst questionOperatorParentTypes = new Set([\n 'ternary_expression',\n 'conditional_expression',\n 'conditional',\n 'try_expression',\n // TypeScript conditional types (`T extends U ? X : Y`) select like a ternary.\n 'conditional_type',\n]);\n\nfunction isCountableContextualToken(node: Parser.SyntaxNode, text: string): boolean {\n if (text === '@') {\n // Python matrix multiplication only; decorator/annotation `@` marks are not operators.\n const parentType = node.parent?.type;\n return parentType === 'binary_operator' || parentType === 'augmented_assignment';\n }\n if (text !== '?') {\n return true;\n }\n const parentType = node.parent?.type;\n return parentType !== undefined && questionOperatorParentTypes.has(parentType);\n}\n\nfunction findChildrenByFieldName(node: Parser.SyntaxNode, fieldName: string): Parser.SyntaxNode[] {\n const children: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === fieldName) {\n children.push(child);\n }\n }\n return children;\n}\n\nfunction incrementCount(map: Map<string, number>, value: string): void {\n map.set(value, (map.get(value) ?? 0) + 1);\n}\n\nfunction maxCognitiveComplexity(functions: FunctionMetrics[]): number {\n return functions.length === 0 ? 0 : Math.max(...functions.map((fn) => fn.cognitiveComplexity));\n}\n\nfunction sum(values: Iterable<number>): number {\n let total = 0;\n for (const value of values) {\n total += value;\n }\n return total;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["import type { CrossFileDuplicateCandidate, CrossFileDuplicationFileData } from './duplication.js';\nimport { createLanguageRegistry } from './languages.js';\nimport {\n collectCrossFileDataNative,\n collectFunctionTokenSequencesNative,\n measureCodeNative,\n type NativeHalsteadCounts,\n type NativeMetricsPayload,\n} from './nativeMetrics.js';\nimport type { CodeMetrics, HalsteadMetrics, LanguageDefinition, LanguageName, MeasureOptions } from './types.js';\n\n/**\n * Measures code metrics for the built-in languages. Parsing and metric passes run in the bundled\n * Rust addon (tree-sitter); this class resolves language aliases, crosses the N-API boundary, and\n * derives the Halstead float metrics from the natively measured counts.\n */\nexport class TreeMeasurer {\n private readonly registry = createLanguageRegistry();\n\n getSupportedLanguages(): LanguageName[] {\n return [...new Set([...this.registry.values()].map((language) => language.name))];\n }\n\n measure(code: string, options: MeasureOptions): CodeMetrics {\n const language = this.resolveLanguage(options.language);\n const includeSyntaxTree = options.includeSyntaxTree ?? false;\n const payload = measureCodeNative(code, language.name, includeSyntaxTree, options.duplication);\n return assembleNativeMetrics(payload, includeSyntaxTree);\n }\n\n /** Collects one file's duplicate candidates for cross-file clone detection. */\n collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return this.collectCrossFileDuplicationFileData(code, options).candidates;\n }\n\n /**\n * Collects one file's duplicate candidates, normalized tokens, and statement structure for\n * cross-file clone detection with measureCrossFileDuplication.\n */\n collectCrossFileDuplicationFileData(code: string, options: MeasureOptions): CrossFileDuplicationFileData {\n const language = this.resolveLanguage(options.language);\n const payload = collectCrossFileDataNative(code, language.name, options.duplication?.minTokens);\n return {\n candidates: payload.candidates,\n tokens: payload.tokens,\n containerStatements: payload.containerStatements,\n // Lets cross-file line coverage count only code lines, like within-file coverage.\n codeLineNumbers: new Set(payload.codeLineNumbers),\n };\n }\n\n /**\n * Normalized token hash sequences of every function, index-parallel to the functions array of\n * measure(): identifiers are anonymized by first occurrence within the function, literals by\n * kind, and keywords/operators kept verbatim, so the regression gate can re-match renamed or\n * moved functions across two revisions by token-LCS similarity.\n */\n collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n const language = this.resolveLanguage(options.language);\n return collectFunctionTokenSequencesNative(code, language.name);\n }\n\n private resolveLanguage(name: LanguageName): LanguageDefinition {\n const language = this.registry.get(name);\n if (!language) {\n throw new Error(`Unsupported language: ${name}`);\n }\n return language;\n }\n}\n\n/**\n * Completes a native measurement into CodeMetrics. The object is rebuilt field by field (rather\n * than spread from the parsed JSON) so the result has a stable shape, including\n * explicitly-undefined optional keys.\n */\nfunction assembleNativeMetrics(payload: NativeMetricsPayload, includeSyntaxTree: boolean): CodeMetrics {\n return {\n language: payload.language,\n bytes: payload.bytes,\n lines: payload.lines,\n functions: payload.functions.map((fn) => ({\n name: fn.name,\n nodeType: fn.nodeType,\n startLine: fn.startLine,\n startColumn: fn.startColumn,\n endLine: fn.endLine,\n cognitiveComplexity: fn.cognitiveComplexity,\n nestingDepth: fn.nestingDepth,\n ncss: fn.ncss,\n parameterCount: fn.parameterCount,\n halstead: deriveHalsteadMetrics(fn.halsteadCounts),\n depDegree: fn.depDegree,\n })),\n cognitiveComplexity: payload.cognitiveComplexity,\n maxCognitiveComplexity: payload.maxCognitiveComplexity,\n nestingDepth: payload.nestingDepth,\n ncssCount: payload.ncssCount,\n duplication: payload.duplication,\n halstead: deriveHalsteadMetrics(payload.halsteadCounts),\n syntaxTree: includeSyntaxTree ? payload.syntaxTree : undefined,\n };\n}\n\nfunction deriveHalsteadMetrics(counts: NativeHalsteadCounts): HalsteadMetrics {\n const { distinctOperators, distinctOperands, totalOperators, totalOperands } = counts;\n const vocabulary = distinctOperators + distinctOperands;\n const length = totalOperators + totalOperands;\n const volume = vocabulary === 0 ? 0 : length * Math.log2(vocabulary);\n const difficulty = distinctOperands === 0 ? 0 : (distinctOperators / 2) * (totalOperands / distinctOperands);\n const effort = difficulty * volume;\n\n return {\n distinctOperators,\n distinctOperands,\n totalOperators,\n totalOperands,\n vocabulary,\n length,\n volume,\n effort,\n };\n}\n\nexport const defaultMeasurer = new TreeMeasurer();\n\nexport function measureCode(code: string, options: MeasureOptions): CodeMetrics {\n return defaultMeasurer.measure(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectDuplicationCandidates(code: string, options: MeasureOptions): CrossFileDuplicateCandidate[] {\n return defaultMeasurer.collectDuplicationCandidates(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectFunctionTokenSequences(code: string, options: MeasureOptions): Int32Array[] {\n return defaultMeasurer.collectFunctionTokenSequences(code, options);\n}\n\n/** Standalone helper mirroring measureCode for the default measurer. */\nexport function collectCrossFileDuplicationFileData(\n code: string,\n options: MeasureOptions\n): CrossFileDuplicationFileData {\n return defaultMeasurer.collectCrossFileDuplicationFileData(code, options);\n}\n"],"mappings":"wLAgBA,IAAa,EAAb,KAA0B,CACxB,SAA4B,EAAuB,EAEnD,uBAAwC,CACtC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,IAAK,GAAa,EAAS,IAAI,CAAC,CAAC,CAClF,CAEA,QAAQ,EAAc,EAAsC,CAC1D,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAoB,EAAQ,mBAAqB,GAEvD,OAAO,EADS,EAAkB,EAAM,EAAS,KAAM,EAAmB,EAAQ,WAC/C,EAAG,CAAiB,CACzD,CAGA,6BAA6B,EAAc,EAAwD,CACjG,OAAO,KAAK,oCAAoC,EAAM,CAAO,CAAC,CAAC,UACjE,CAMA,oCAAoC,EAAc,EAAuD,CACvG,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EAChD,EAAU,EAA2B,EAAM,EAAS,KAAM,EAAQ,aAAa,SAAS,EAC9F,MAAO,CACL,WAAY,EAAQ,WACpB,OAAQ,EAAQ,OAChB,oBAAqB,EAAQ,oBAE7B,gBAAiB,IAAI,IAAI,EAAQ,eAAe,CAClD,CACF,CAQA,8BAA8B,EAAc,EAAuC,CACjF,IAAM,EAAW,KAAK,gBAAgB,EAAQ,QAAQ,EACtD,OAAO,EAAoC,EAAM,EAAS,IAAI,CAChE,CAEA,gBAAwB,EAAwC,CAC9D,IAAM,EAAW,KAAK,SAAS,IAAI,CAAI,EACvC,GAAI,CAAC,EACH,MAAU,MAAM,yBAAyB,GAAM,EAEjD,OAAO,CACT,CACF,EAOA,SAAS,EAAsB,EAA+B,EAAyC,CACrG,MAAO,CACL,SAAU,EAAQ,SAClB,MAAO,EAAQ,MACf,MAAO,EAAQ,MACf,UAAW,EAAQ,UAAU,IAAK,IAAQ,CACxC,KAAM,EAAG,KACT,SAAU,EAAG,SACb,UAAW,EAAG,UACd,YAAa,EAAG,YAChB,QAAS,EAAG,QACZ,oBAAqB,EAAG,oBACxB,aAAc,EAAG,aACjB,KAAM,EAAG,KACT,eAAgB,EAAG,eACnB,SAAU,EAAsB,EAAG,cAAc,EACjD,UAAW,EAAG,SAChB,EAAE,EACF,oBAAqB,EAAQ,oBAC7B,uBAAwB,EAAQ,uBAChC,aAAc,EAAQ,aACtB,UAAW,EAAQ,UACnB,YAAa,EAAQ,YACrB,SAAU,EAAsB,EAAQ,cAAc,EACtD,WAAY,EAAoB,EAAQ,WAAa,IAAA,EACvD,CACF,CAEA,SAAS,EAAsB,EAA+C,CAC5E,GAAM,CAAE,oBAAmB,mBAAkB,iBAAgB,iBAAkB,EACzE,EAAa,EAAoB,EACjC,EAAS,EAAiB,EAC1B,EAAS,IAAe,EAAI,EAAI,EAAS,KAAK,KAAK,CAAU,EAInE,MAAO,CACL,oBACA,mBACA,iBACA,gBACA,aACA,SACA,SACA,QAXiB,IAAqB,EAAI,EAAK,EAAoB,GAAM,EAAgB,IAC/D,CAW5B,CACF,CAEA,MAAa,EAAkB,IAAI,EAEnC,SAAgB,EAAY,EAAc,EAAsC,CAC9E,OAAO,EAAgB,QAAQ,EAAM,CAAO,CAC9C,CAGA,SAAgB,EAA6B,EAAc,EAAwD,CACjH,OAAO,EAAgB,6BAA6B,EAAM,CAAO,CACnE,CAGA,SAAgB,EAA8B,EAAc,EAAuC,CACjG,OAAO,EAAgB,8BAA8B,EAAM,CAAO,CACpE,CAGA,SAAgB,EACd,EACA,EAC8B,CAC9B,OAAO,EAAgB,oCAAoC,EAAM,CAAO,CAC1E"}