code-gauge 1.14.0 → 2.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.
package/dist/ncss.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";const e=new Set([`comment`,`line_comment`,`block_comment`]),t=new Set([`attribute_item`,`inner_attribute_item`,`empty_statement`,`heredoc_body`,`parenthesized_statements`]),n=new Set([`property_signature`,`method_signature`,`index_signature`,`construct_signature`,`call_signature`]),r=new Set([`else_clause`,`elif_clause`,`else`,`elsif`]),i=new Set([`if_statement`,`if_expression`]),a=new Set([`struct_specifier`,`enum_specifier`,`union_specifier`,`class_specifier`]),o=new WeakMap;function s(e){o.delete(e)}function c(e){let t=o.get(e);return t||(t={countable:new Set(e.ncssNodeTypes),containers:new Set(e.ncssContainerNodeTypes)},o.set(e,t)),t}function l(e,t){let{countable:n,containers:r}=c(t),i=0;function a(e){i+=d(e,n,r);for(let t of e.children)a(t)}return a(e),i}function u(e,t){let{countable:n,containers:r}=c(t),i=d(e,n,r);return l(e,t)+(i>0?0:1)}function d(n,r,a){if(!n.isNamed||e.has(n.type)||g(n))return 0;let o=0,s=p(n,a)&&!a.has(n.type)&&!t.has(n.type);return(f(n,r)||s||_(n))&&!m(n,r)&&(o+=1),i.has(n.type)&&(o+=b(n).length),o}function f(e,t){return t.has(e.type)?a.has(e.type)?e.childForFieldName(`body`)!==null:e.type!==`resource`||e.childForFieldName(`name`)!==null:!1}function p(e,t){let n=e.parent;for(;n&&n.type===`parenthesized_statements`;)n=n.parent;return n!==null&&t.has(n.type)}function m(e,t){if(e.type!==`export_statement`)return!1;let n=e.childForFieldName(`declaration`);return n!==null&&(t.has(n.type)||n.type===`internal_module`||n.type===`ambient_declaration`)}const h=new Set([`init`,`initializer`,`condition`,`update`,`increment`]);function g(e){let t=e.parent;if(!t)return!1;if(t.type===`init_statement`&&t.parent?.type===`for_range_loop`)return!0;if(t.type!==`for_statement`&&t.type!==`for_clause`&&t.type!==`for_range_loop`)return!1;for(let n=0;n<t.childCount;n+=1)if(t.child(n)?.id===e.id)return h.has(t.fieldNameForChild(n)??``);return!1}function _(e){let t=e.parent?.type;return e.type===`block`&&t===`class_body`||n.has(e.type)&&t===`interface_body`||t===`match_arm`&&e.type!==`block`&&y(e,`value`)||e.type===`method_signature`&&t===`class_body`||e.type===`internal_module`&&t!==`expression_statement`||(t===`method`||t===`singleton_method`)&&e.type!==`body_statement`&&y(e,`body`)?!0:e.type===`friend_declaration`?!e.namedChildren.some(e=>e.type===`declaration`||e.type===`function_definition`):e.type===`macro_invocation`&&(t===`source_file`||t===`declaration_list`)?!0:e.type===`field_declaration`&&t===`field_declaration_list`?v(e.parent?.parent,`struct_type`):e.type===`method_elem`||e.type===`method_spec`||e.type===`type_elem`?v(e.parent,`interface_type`):!1}function v(e,t){return e?.type===t?e.parent?.type===`type_spec`||e.parent?.type===`type_alias`:!1}function y(e,t){let n=e.parent;if(!n)return!1;for(let r=0;r<n.childCount;r+=1)if(n.child(r)?.id===e.id)return n.fieldNameForChild(r)===t;return!1}function b(e){let t=[];for(let n=0;n<e.childCount;n+=1){let i=e.child(n);i&&e.fieldNameForChild(n)===`alternative`&&!r.has(i.type)&&t.push(i)}return t}exports.commentNodeTypes=e,exports.countFunctionNcss=u,exports.countNcss=l,exports.invalidateNcssSetsCache=s;
1
+ "use strict";const e=new Set([`comment`,`line_comment`,`block_comment`]),t=new Set([`attribute_item`,`inner_attribute_item`,`empty_statement`,`heredoc_body`,`parenthesized_statements`]),n=new Set([`property_signature`,`method_signature`,`index_signature`,`construct_signature`,`call_signature`]),r=new Set([`else_clause`,`elif_clause`,`else`,`elsif`]),i=new Set([`if_statement`,`if_expression`]),a=new Set([`struct_specifier`,`enum_specifier`,`union_specifier`,`class_specifier`]),o=new WeakMap;function s(e){o.delete(e)}function c(e){let t=o.get(e);return t||(t={countable:new Set(e.ncssNodeTypes),containers:new Set(e.ncssContainerNodeTypes)},o.set(e,t)),t}function l(e,t){let{countable:n,containers:r}=c(t),i=0;function a(e){i+=u(e,n,r);for(let t of e.children)a(t)}return a(e),i}function u(n,r,a){if(!n.isNamed||e.has(n.type)||h(n))return 0;let o=0,s=f(n,a)&&!a.has(n.type)&&!t.has(n.type);return(d(n,r)||s||g(n))&&!p(n,r)&&(o+=1),i.has(n.type)&&(o+=y(n).length),o}function d(e,t){return t.has(e.type)?a.has(e.type)?e.childForFieldName(`body`)!==null:e.type!==`resource`||e.childForFieldName(`name`)!==null:!1}function f(e,t){let n=e.parent;for(;n&&n.type===`parenthesized_statements`;)n=n.parent;return n!==null&&t.has(n.type)}function p(e,t){if(e.type!==`export_statement`)return!1;let n=e.childForFieldName(`declaration`);return n!==null&&(t.has(n.type)||n.type===`internal_module`||n.type===`ambient_declaration`)}const m=new Set([`init`,`initializer`,`condition`,`update`,`increment`]);function h(e){let t=e.parent;if(!t)return!1;if(t.type===`init_statement`&&t.parent?.type===`for_range_loop`)return!0;if(t.type!==`for_statement`&&t.type!==`for_clause`&&t.type!==`for_range_loop`)return!1;for(let n=0;n<t.childCount;n+=1)if(t.child(n)?.id===e.id)return m.has(t.fieldNameForChild(n)??``);return!1}function g(e){let t=e.parent?.type;return e.type===`block`&&t===`class_body`||n.has(e.type)&&t===`interface_body`||t===`match_arm`&&e.type!==`block`&&v(e,`value`)||e.type===`method_signature`&&t===`class_body`||e.type===`internal_module`&&t!==`expression_statement`||(t===`method`||t===`singleton_method`)&&e.type!==`body_statement`&&v(e,`body`)?!0:e.type===`friend_declaration`?!e.namedChildren.some(e=>e.type===`declaration`||e.type===`function_definition`):e.type===`macro_invocation`&&(t===`source_file`||t===`declaration_list`)?!0:e.type===`field_declaration`&&t===`field_declaration_list`?_(e.parent?.parent,`struct_type`):e.type===`method_elem`||e.type===`method_spec`||e.type===`type_elem`?_(e.parent,`interface_type`):!1}function _(e,t){return e?.type===t?e.parent?.type===`type_spec`||e.parent?.type===`type_alias`:!1}function v(e,t){let n=e.parent;if(!n)return!1;for(let r=0;r<n.childCount;r+=1)if(n.child(r)?.id===e.id)return n.fieldNameForChild(r)===t;return!1}function y(e){let t=[];for(let n=0;n<e.childCount;n+=1){let i=e.child(n);i&&e.fieldNameForChild(n)===`alternative`&&!r.has(i.type)&&t.push(i)}return t}exports.commentNodeTypes=e,exports.countNcss=l,exports.getNcssSets=c,exports.invalidateNcssSetsCache=s,exports.ncssContribution=u;
2
2
  //# sourceMappingURL=ncss.cjs.map
package/dist/ncss.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ncss.cjs","names":[],"sources":["../src/ncss.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport type { LanguageDefinition } from './types.js';\n\nexport const commentNodeTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby\n// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),\n// and Ruby statement parentheses (transparent wrappers whose children count instead).\nconst positionalExclusionTypes = new Set([\n 'attribute_item',\n 'inner_attribute_item',\n 'empty_statement',\n 'heredoc_body',\n 'parenthesized_statements',\n]);\n\n// TypeScript interface members count like Java interface members, but the same node types appear\n// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so\n// they only count directly under an interface body.\nconst interfaceMemberNodeTypes = new Set([\n 'property_signature',\n 'method_signature',\n 'index_signature',\n 'construct_signature',\n 'call_signature',\n]);\n\n// An if-branch wrapped in one of these already counts through ncssNodeTypes; a bare `alternative`\n// (Java/Go put the else branch directly in the field) needs the extra `else` count instead.\nconst elseClauseNodeTypes = new Set(['else_clause', 'elif_clause', 'else', 'elsif']);\n\nconst ifNodeTypes = new Set(['if_statement', 'if_expression']);\n\n// C/C++ type specifiers only declare something when they carry a body (`struct S { ... }`);\n// without one they are mere type references inside other declarations.\nconst bodylessNcssSpecifierTypes = new Set([\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'class_specifier',\n]);\n\n/**\n * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,\n * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources\n * resource); `try` itself, braces, blank lines, and comments count 0.\n */\ninterface NcssSets {\n countable: Set<string>;\n containers: Set<string>;\n}\n\n// Cached per language: countNcss runs once per function plus once per file, so per-call Set\n// construction would add a measurable constant factor on large files.\nconst ncssSetsCache = new WeakMap<LanguageDefinition, NcssSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nexport function invalidateNcssSetsCache(language: LanguageDefinition): void {\n ncssSetsCache.delete(language);\n}\n\nfunction getNcssSets(language: LanguageDefinition): NcssSets {\n let sets = ncssSetsCache.get(language);\n if (!sets) {\n sets = { countable: new Set(language.ncssNodeTypes), containers: new Set(language.ncssContainerNodeTypes) };\n ncssSetsCache.set(language, sets);\n }\n return sets;\n}\n\nexport function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n let count = 0;\n\n function visit(current: Parser.SyntaxNode): void {\n count += ncssContribution(current, countable, containers);\n for (const child of current.children) {\n visit(child);\n }\n }\n\n visit(node);\n return count;\n}\n\n/**\n * Per-function NCSS: the function's whole subtree plus 1 for the declaration itself when the\n * function node carries no countable declaration of its own (arrow functions, lambdas, blocks).\n */\nexport function countFunctionNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n const selfContribution = ncssContribution(node, countable, containers);\n return countNcss(node, language) + (selfContribution > 0 ? 0 : 1);\n}\n\nfunction ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number {\n if (!node.isNamed || commentNodeTypes.has(node.type) || isForHeaderNode(node)) {\n return 0;\n }\n\n let contribution = 0;\n const positional =\n isInContainerPosition(node, containers) && !containers.has(node.type) && !positionalExclusionTypes.has(node.type);\n if (\n (countsThroughNodeType(node, countable) || positional || countsContextually(node)) &&\n !isDeclarationWrapper(node, countable)\n ) {\n contribution += 1;\n }\n\n // A bare else branch (Java/Go `alternative:` without an else-clause wrapper) counts 1 like the\n // `else` keyword does in PMD; an `else if` chain charges the nested if separately on top.\n if (ifNodeTypes.has(node.type)) {\n contribution += findBareAlternatives(node).length;\n }\n\n return contribution;\n}\n\nfunction countsThroughNodeType(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (!countable.has(node.type)) {\n return false;\n }\n if (bodylessNcssSpecifierTypes.has(node.type)) {\n return node.childForFieldName('body') !== null;\n }\n // A try-with-resources `resource` counts only when it declares a variable; `try (r)` reuses an\n // existing one and adds no statement (matching PMD).\n if (node.type === 'resource') {\n return node.childForFieldName('name') !== null;\n }\n return true;\n}\n\n/**\n * Direct container children count positionally; Ruby's `(foo; bar)` statement parentheses are\n * transparent (through arbitrary nesting), so their children count when the parentheses\n * themselves sit in a container.\n */\nfunction isInContainerPosition(node: Parser.SyntaxNode, containers: Set<string>): boolean {\n let ancestor = node.parent;\n while (ancestor && ancestor.type === 'parenthesized_statements') {\n ancestor = ancestor.parent;\n }\n return ancestor !== null && containers.has(ancestor.type);\n}\n\n/**\n * `export const x = 1` nests a countable declaration inside `export_statement`; only the inner\n * declaration counts, mirroring how PMD counts one statement per declared entity. The nested\n * declaration may also count contextually (`export namespace N {}` → internal_module) or sit one\n * level deeper (`export declare function f(): void;` → ambient_declaration > function_signature).\n */\nfunction isDeclarationWrapper(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (node.type !== 'export_statement') {\n return false;\n }\n const declaration = node.childForFieldName('declaration');\n return (\n declaration !== null &&\n (countable.has(declaration.type) ||\n declaration.type === 'internal_module' ||\n declaration.type === 'ambient_declaration')\n );\n}\n\nconst forHeaderFieldNames = new Set(['init', 'initializer', 'condition', 'update', 'increment']);\n\n/**\n * Statement-shaped nodes in a `for` header (`for (int i = 0; i < n; i++)`) are part of the loop\n * statement, which already counts; PMD does not count them separately. JavaScript parses the\n * condition as an `expression_statement` and Go parses the update as an `inc_statement`, so all\n * header fields must be excluded, not just the initializer.\n */\nfunction isForHeaderNode(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // C++20 range-for initializers nest one level deeper: for_range_loop > init_statement > node.\n if (parent.type === 'init_statement' && parent.parent?.type === 'for_range_loop') {\n return true;\n }\n if (parent.type !== 'for_statement' && parent.type !== 'for_clause' && parent.type !== 'for_range_loop') {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return forHeaderFieldNames.has(parent.fieldNameForChild(index) ?? '');\n }\n }\n return false;\n}\n\n/** Statements only countable by their position: constructs without a dedicated statement node. */\nfunction countsContextually(node: Parser.SyntaxNode): boolean {\n const parentType = node.parent?.type;\n // A Java instance initializer is a bare `block` in the class body; PMD counts it like the\n // `static_initializer` declaration it parallels.\n if (node.type === 'block' && parentType === 'class_body') {\n return true;\n }\n // TypeScript interface members (see interfaceMemberNodeTypes).\n if (interfaceMemberNodeTypes.has(node.type) && parentType === 'interface_body') {\n return true;\n }\n // A braceless Rust match-arm body (`1 => foo()`) has no expression_statement wrapper; count the\n // value expression so braced and unbraced arms measure alike.\n if (parentType === 'match_arm' && node.type !== 'block' && isFieldOfParent(node, 'value')) {\n return true;\n }\n // A TypeScript class-body method overload signature declares a member like its interface twin.\n if (node.type === 'method_signature' && parentType === 'class_body') {\n return true;\n }\n // An ambient `declare namespace M { ... }` is a bare `internal_module`; the non-ambient\n // `namespace N { ... }` is wrapped in an `expression_statement`, which already counts.\n if (node.type === 'internal_module' && parentType !== 'expression_statement') {\n return true;\n }\n // A Ruby endless method (`def f(x) = expr`) stores its single-statement body directly in the\n // `body` field instead of a positional `body_statement` container.\n if (\n (parentType === 'method' || parentType === 'singleton_method') &&\n node.type !== 'body_statement' &&\n isFieldOfParent(node, 'body')\n ) {\n return true;\n }\n // C++ `friend class X;` declares on its own; `friend void g() { ... }` merely wraps a counted\n // definition.\n if (node.type === 'friend_declaration') {\n return !node.namedChildren.some((child) => child.type === 'declaration' || child.type === 'function_definition');\n }\n // A Rust item-position macro invocation (`foo! {}` at module level) has no expression_statement\n // wrapper; the semicolon form does and already counts through it.\n if (node.type === 'macro_invocation' && (parentType === 'source_file' || parentType === 'declaration_list')) {\n return true;\n }\n // Go struct fields and interface members count like other languages' member declarations, but\n // only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,\n // `func f(h interface{ ... })`) are part of one declaration.\n if (node.type === 'field_declaration' && parentType === 'field_declaration_list') {\n return isGoDeclaredTypeBody(node.parent?.parent, 'struct_type');\n }\n if (node.type === 'method_elem' || node.type === 'method_spec' || node.type === 'type_elem') {\n return isGoDeclaredTypeBody(node.parent, 'interface_type');\n }\n return false;\n}\n\nfunction isGoDeclaredTypeBody(typeNode: Parser.SyntaxNode | null | undefined, expectedType: string): boolean {\n if (typeNode?.type !== expectedType) {\n return false;\n }\n return typeNode.parent?.type === 'type_spec' || typeNode.parent?.type === 'type_alias';\n}\n\nfunction isFieldOfParent(node: Parser.SyntaxNode, fieldName: string): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return parent.fieldNameForChild(index) === fieldName;\n }\n }\n return false;\n}\n\nfunction findBareAlternatives(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const alternatives: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === 'alternative' && !elseClauseNodeTypes.has(child.type)) {\n alternatives.push(child);\n }\n }\n return alternatives;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ncss.cjs","names":[],"sources":["../src/ncss.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport type { LanguageDefinition } from './types.js';\n\nexport const commentNodeTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby\n// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),\n// and Ruby statement parentheses (transparent wrappers whose children count instead).\nconst positionalExclusionTypes = new Set([\n 'attribute_item',\n 'inner_attribute_item',\n 'empty_statement',\n 'heredoc_body',\n 'parenthesized_statements',\n]);\n\n// TypeScript interface members count like Java interface members, but the same node types appear\n// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so\n// they only count directly under an interface body.\nconst interfaceMemberNodeTypes = new Set([\n 'property_signature',\n 'method_signature',\n 'index_signature',\n 'construct_signature',\n 'call_signature',\n]);\n\n// An if-branch wrapped in one of these already counts through ncssNodeTypes; a bare `alternative`\n// (Java/Go put the else branch directly in the field) needs the extra `else` count instead.\nconst elseClauseNodeTypes = new Set(['else_clause', 'elif_clause', 'else', 'elsif']);\n\nconst ifNodeTypes = new Set(['if_statement', 'if_expression']);\n\n// C/C++ type specifiers only declare something when they carry a body (`struct S { ... }`);\n// without one they are mere type references inside other declarations.\nconst bodylessNcssSpecifierTypes = new Set([\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'class_specifier',\n]);\n\n/**\n * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,\n * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources\n * resource); `try` itself, braces, blank lines, and comments count 0.\n */\nexport interface NcssSets {\n countable: Set<string>;\n containers: Set<string>;\n}\n\n// Cached per language: countNcss runs once per function plus once per file, so per-call Set\n// construction would add a measurable constant factor on large files.\nconst ncssSetsCache = new WeakMap<LanguageDefinition, NcssSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nexport function invalidateNcssSetsCache(language: LanguageDefinition): void {\n ncssSetsCache.delete(language);\n}\n\nexport function getNcssSets(language: LanguageDefinition): NcssSets {\n let sets = ncssSetsCache.get(language);\n if (!sets) {\n sets = { countable: new Set(language.ncssNodeTypes), containers: new Set(language.ncssContainerNodeTypes) };\n ncssSetsCache.set(language, sets);\n }\n return sets;\n}\n\nexport function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n let count = 0;\n\n function visit(current: Parser.SyntaxNode): void {\n count += ncssContribution(current, countable, containers);\n for (const child of current.children) {\n visit(child);\n }\n }\n\n visit(node);\n return count;\n}\n\nexport function ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number {\n if (!node.isNamed || commentNodeTypes.has(node.type) || isForHeaderNode(node)) {\n return 0;\n }\n\n let contribution = 0;\n const positional =\n isInContainerPosition(node, containers) && !containers.has(node.type) && !positionalExclusionTypes.has(node.type);\n if (\n (countsThroughNodeType(node, countable) || positional || countsContextually(node)) &&\n !isDeclarationWrapper(node, countable)\n ) {\n contribution += 1;\n }\n\n // A bare else branch (Java/Go `alternative:` without an else-clause wrapper) counts 1 like the\n // `else` keyword does in PMD; an `else if` chain charges the nested if separately on top.\n if (ifNodeTypes.has(node.type)) {\n contribution += findBareAlternatives(node).length;\n }\n\n return contribution;\n}\n\nfunction countsThroughNodeType(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (!countable.has(node.type)) {\n return false;\n }\n if (bodylessNcssSpecifierTypes.has(node.type)) {\n return node.childForFieldName('body') !== null;\n }\n // A try-with-resources `resource` counts only when it declares a variable; `try (r)` reuses an\n // existing one and adds no statement (matching PMD).\n if (node.type === 'resource') {\n return node.childForFieldName('name') !== null;\n }\n return true;\n}\n\n/**\n * Direct container children count positionally; Ruby's `(foo; bar)` statement parentheses are\n * transparent (through arbitrary nesting), so their children count when the parentheses\n * themselves sit in a container.\n */\nfunction isInContainerPosition(node: Parser.SyntaxNode, containers: Set<string>): boolean {\n let ancestor = node.parent;\n while (ancestor && ancestor.type === 'parenthesized_statements') {\n ancestor = ancestor.parent;\n }\n return ancestor !== null && containers.has(ancestor.type);\n}\n\n/**\n * `export const x = 1` nests a countable declaration inside `export_statement`; only the inner\n * declaration counts, mirroring how PMD counts one statement per declared entity. The nested\n * declaration may also count contextually (`export namespace N {}` → internal_module) or sit one\n * level deeper (`export declare function f(): void;` → ambient_declaration > function_signature).\n */\nfunction isDeclarationWrapper(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (node.type !== 'export_statement') {\n return false;\n }\n const declaration = node.childForFieldName('declaration');\n return (\n declaration !== null &&\n (countable.has(declaration.type) ||\n declaration.type === 'internal_module' ||\n declaration.type === 'ambient_declaration')\n );\n}\n\nconst forHeaderFieldNames = new Set(['init', 'initializer', 'condition', 'update', 'increment']);\n\n/**\n * Statement-shaped nodes in a `for` header (`for (int i = 0; i < n; i++)`) are part of the loop\n * statement, which already counts; PMD does not count them separately. JavaScript parses the\n * condition as an `expression_statement` and Go parses the update as an `inc_statement`, so all\n * header fields must be excluded, not just the initializer.\n */\nfunction isForHeaderNode(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // C++20 range-for initializers nest one level deeper: for_range_loop > init_statement > node.\n if (parent.type === 'init_statement' && parent.parent?.type === 'for_range_loop') {\n return true;\n }\n if (parent.type !== 'for_statement' && parent.type !== 'for_clause' && parent.type !== 'for_range_loop') {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return forHeaderFieldNames.has(parent.fieldNameForChild(index) ?? '');\n }\n }\n return false;\n}\n\n/** Statements only countable by their position: constructs without a dedicated statement node. */\nfunction countsContextually(node: Parser.SyntaxNode): boolean {\n const parentType = node.parent?.type;\n // A Java instance initializer is a bare `block` in the class body; PMD counts it like the\n // `static_initializer` declaration it parallels.\n if (node.type === 'block' && parentType === 'class_body') {\n return true;\n }\n // TypeScript interface members (see interfaceMemberNodeTypes).\n if (interfaceMemberNodeTypes.has(node.type) && parentType === 'interface_body') {\n return true;\n }\n // A braceless Rust match-arm body (`1 => foo()`) has no expression_statement wrapper; count the\n // value expression so braced and unbraced arms measure alike.\n if (parentType === 'match_arm' && node.type !== 'block' && isFieldOfParent(node, 'value')) {\n return true;\n }\n // A TypeScript class-body method overload signature declares a member like its interface twin.\n if (node.type === 'method_signature' && parentType === 'class_body') {\n return true;\n }\n // An ambient `declare namespace M { ... }` is a bare `internal_module`; the non-ambient\n // `namespace N { ... }` is wrapped in an `expression_statement`, which already counts.\n if (node.type === 'internal_module' && parentType !== 'expression_statement') {\n return true;\n }\n // A Ruby endless method (`def f(x) = expr`) stores its single-statement body directly in the\n // `body` field instead of a positional `body_statement` container.\n if (\n (parentType === 'method' || parentType === 'singleton_method') &&\n node.type !== 'body_statement' &&\n isFieldOfParent(node, 'body')\n ) {\n return true;\n }\n // C++ `friend class X;` declares on its own; `friend void g() { ... }` merely wraps a counted\n // definition.\n if (node.type === 'friend_declaration') {\n return !node.namedChildren.some((child) => child.type === 'declaration' || child.type === 'function_definition');\n }\n // A Rust item-position macro invocation (`foo! {}` at module level) has no expression_statement\n // wrapper; the semicolon form does and already counts through it.\n if (node.type === 'macro_invocation' && (parentType === 'source_file' || parentType === 'declaration_list')) {\n return true;\n }\n // Go struct fields and interface members count like other languages' member declarations, but\n // only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,\n // `func f(h interface{ ... })`) are part of one declaration.\n if (node.type === 'field_declaration' && parentType === 'field_declaration_list') {\n return isGoDeclaredTypeBody(node.parent?.parent, 'struct_type');\n }\n if (node.type === 'method_elem' || node.type === 'method_spec' || node.type === 'type_elem') {\n return isGoDeclaredTypeBody(node.parent, 'interface_type');\n }\n return false;\n}\n\nfunction isGoDeclaredTypeBody(typeNode: Parser.SyntaxNode | null | undefined, expectedType: string): boolean {\n if (typeNode?.type !== expectedType) {\n return false;\n }\n return typeNode.parent?.type === 'type_spec' || typeNode.parent?.type === 'type_alias';\n}\n\nfunction isFieldOfParent(node: Parser.SyntaxNode, fieldName: string): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return parent.fieldNameForChild(index) === fieldName;\n }\n }\n return false;\n}\n\nfunction findBareAlternatives(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const alternatives: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === 'alternative' && !elseClauseNodeTypes.has(child.type)) {\n alternatives.push(child);\n }\n }\n return alternatives;\n}\n"],"mappings":"aAGA,MAAa,EAAmB,IAAI,IAAI,CAAC,UAAW,eAAgB,eAAe,CAAC,EAK9E,EAA2B,IAAI,IAAI,CACvC,iBACA,uBACA,kBACA,eACA,0BACF,CAAC,EAKK,EAA2B,IAAI,IAAI,CACvC,qBACA,mBACA,kBACA,sBACA,gBACF,CAAC,EAIK,EAAsB,IAAI,IAAI,CAAC,cAAe,cAAe,OAAQ,OAAO,CAAC,EAE7E,EAAc,IAAI,IAAI,CAAC,eAAgB,eAAe,CAAC,EAIvD,EAA6B,IAAI,IAAI,CACzC,mBACA,iBACA,kBACA,iBACF,CAAC,EAcK,EAAgB,IAAI,QAG1B,SAAgB,EAAwB,EAAoC,CAC1E,EAAc,OAAO,CAAQ,CAC/B,CAEA,SAAgB,EAAY,EAAwC,CAClE,IAAI,EAAO,EAAc,IAAI,CAAQ,EAKrC,OAJK,IACH,EAAO,CAAE,UAAW,IAAI,IAAI,EAAS,aAAa,EAAG,WAAY,IAAI,IAAI,EAAS,sBAAsB,CAAE,EAC1G,EAAc,IAAI,EAAU,CAAI,GAE3B,CACT,CAEA,SAAgB,EAAU,EAAyB,EAAsC,CACvF,GAAM,CAAE,YAAW,cAAe,EAAY,CAAQ,EAClD,EAAQ,EAEZ,SAAS,EAAM,EAAkC,CAC/C,GAAS,EAAiB,EAAS,EAAW,CAAU,EACxD,IAAK,IAAM,KAAS,EAAQ,SAC1B,EAAM,CAAK,CAEf,CAGA,OADA,EAAM,CAAI,EACH,CACT,CAEA,SAAgB,EAAiB,EAAyB,EAAwB,EAAiC,CACjH,GAAI,CAAC,EAAK,SAAW,EAAiB,IAAI,EAAK,IAAI,GAAK,EAAgB,CAAI,EAC1E,MAAO,GAGT,IAAI,EAAe,EACb,EACJ,EAAsB,EAAM,CAAU,GAAK,CAAC,EAAW,IAAI,EAAK,IAAI,GAAK,CAAC,EAAyB,IAAI,EAAK,IAAI,EAclH,OAZG,EAAsB,EAAM,CAAS,GAAK,GAAc,EAAmB,CAAI,IAChF,CAAC,EAAqB,EAAM,CAAS,IAErC,GAAgB,GAKd,EAAY,IAAI,EAAK,IAAI,IAC3B,GAAgB,EAAqB,CAAI,CAAC,CAAC,QAGtC,CACT,CAEA,SAAS,EAAsB,EAAyB,EAAiC,CAYvF,OAXK,EAAU,IAAI,EAAK,IAAI,EAGxB,EAA2B,IAAI,EAAK,IAAI,EACnC,EAAK,kBAAkB,MAAM,IAAM,KAIxC,EAAK,OAAS,YACT,EAAK,kBAAkB,MAAM,IAAM,KARnC,EAWX,CAOA,SAAS,EAAsB,EAAyB,EAAkC,CACxF,IAAI,EAAW,EAAK,OACpB,KAAO,GAAY,EAAS,OAAS,4BACnC,EAAW,EAAS,OAEtB,OAAO,IAAa,MAAQ,EAAW,IAAI,EAAS,IAAI,CAC1D,CAQA,SAAS,EAAqB,EAAyB,EAAiC,CACtF,GAAI,EAAK,OAAS,mBAChB,MAAO,GAET,IAAM,EAAc,EAAK,kBAAkB,aAAa,EACxD,OACE,IAAgB,OACf,EAAU,IAAI,EAAY,IAAI,GAC7B,EAAY,OAAS,mBACrB,EAAY,OAAS,sBAE3B,CAEA,MAAM,EAAsB,IAAI,IAAI,CAAC,OAAQ,cAAe,YAAa,SAAU,WAAW,CAAC,EAQ/F,SAAS,EAAgB,EAAkC,CACzD,IAAM,EAAS,EAAK,OACpB,GAAI,CAAC,EACH,MAAO,GAGT,GAAI,EAAO,OAAS,kBAAoB,EAAO,QAAQ,OAAS,iBAC9D,MAAO,GAET,GAAI,EAAO,OAAS,iBAAmB,EAAO,OAAS,cAAgB,EAAO,OAAS,iBACrF,MAAO,GAET,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAO,WAAY,GAAS,EACtD,GAAI,EAAO,MAAM,CAAK,CAAC,EAAE,KAAO,EAAK,GACnC,OAAO,EAAoB,IAAI,EAAO,kBAAkB,CAAK,GAAK,EAAE,EAGxE,MAAO,EACT,CAGA,SAAS,EAAmB,EAAkC,CAC5D,IAAM,EAAa,EAAK,QAAQ,KAoDhC,OAjDI,EAAK,OAAS,SAAW,IAAe,cAIxC,EAAyB,IAAI,EAAK,IAAI,GAAK,IAAe,kBAK1D,IAAe,aAAe,EAAK,OAAS,SAAW,EAAgB,EAAM,OAAO,GAIpF,EAAK,OAAS,oBAAsB,IAAe,cAKnD,EAAK,OAAS,mBAAqB,IAAe,yBAMnD,IAAe,UAAY,IAAe,qBAC3C,EAAK,OAAS,kBACd,EAAgB,EAAM,MAAM,EAErB,GAIL,EAAK,OAAS,qBACT,CAAC,EAAK,cAAc,KAAM,GAAU,EAAM,OAAS,eAAiB,EAAM,OAAS,qBAAqB,EAI7G,EAAK,OAAS,qBAAuB,IAAe,eAAiB,IAAe,oBAC/E,GAKL,EAAK,OAAS,qBAAuB,IAAe,yBAC/C,EAAqB,EAAK,QAAQ,OAAQ,aAAa,EAE5D,EAAK,OAAS,eAAiB,EAAK,OAAS,eAAiB,EAAK,OAAS,YACvE,EAAqB,EAAK,OAAQ,gBAAgB,EAEpD,EACT,CAEA,SAAS,EAAqB,EAAgD,EAA+B,CAI3G,OAHI,GAAU,OAAS,EAGhB,EAAS,QAAQ,OAAS,aAAe,EAAS,QAAQ,OAAS,aAFjE,EAGX,CAEA,SAAS,EAAgB,EAAyB,EAA4B,CAC5E,IAAM,EAAS,EAAK,OACpB,GAAI,CAAC,EACH,MAAO,GAET,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAO,WAAY,GAAS,EACtD,GAAI,EAAO,MAAM,CAAK,CAAC,EAAE,KAAO,EAAK,GACnC,OAAO,EAAO,kBAAkB,CAAK,IAAM,EAG/C,MAAO,EACT,CAEA,SAAS,EAAqB,EAA8C,CAC1E,IAAM,EAAoC,CAAC,EAC3C,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,WAAY,GAAS,EAAG,CACvD,IAAM,EAAQ,EAAK,MAAM,CAAK,EAC1B,GAAS,EAAK,kBAAkB,CAAK,IAAM,eAAiB,CAAC,EAAoB,IAAI,EAAM,IAAI,GACjG,EAAa,KAAK,CAAK,CAE3B,CACA,OAAO,CACT"}
package/dist/ncss.d.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  import type Parser from 'tree-sitter';
2
2
  import type { LanguageDefinition } from './types.js';
3
3
  export declare const commentNodeTypes: Set<string>;
4
+ /**
5
+ * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,
6
+ * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources
7
+ * resource); `try` itself, braces, blank lines, and comments count 0.
8
+ */
9
+ export interface NcssSets {
10
+ countable: Set<string>;
11
+ containers: Set<string>;
12
+ }
4
13
  /** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */
5
14
  export declare function invalidateNcssSetsCache(language: LanguageDefinition): void;
15
+ export declare function getNcssSets(language: LanguageDefinition): NcssSets;
6
16
  export declare function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number;
7
- /**
8
- * Per-function NCSS: the function's whole subtree plus 1 for the declaration itself when the
9
- * function node carries no countable declaration of its own (arrow functions, lambdas, blocks).
10
- */
11
- export declare function countFunctionNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number;
17
+ export declare function ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number;
package/dist/ncss.js CHANGED
@@ -1,2 +1,2 @@
1
- const e=new Set([`comment`,`line_comment`,`block_comment`]),t=new Set([`attribute_item`,`inner_attribute_item`,`empty_statement`,`heredoc_body`,`parenthesized_statements`]),n=new Set([`property_signature`,`method_signature`,`index_signature`,`construct_signature`,`call_signature`]),r=new Set([`else_clause`,`elif_clause`,`else`,`elsif`]),i=new Set([`if_statement`,`if_expression`]),a=new Set([`struct_specifier`,`enum_specifier`,`union_specifier`,`class_specifier`]),o=new WeakMap;function s(e){o.delete(e)}function c(e){let t=o.get(e);return t||(t={countable:new Set(e.ncssNodeTypes),containers:new Set(e.ncssContainerNodeTypes)},o.set(e,t)),t}function l(e,t){let{countable:n,containers:r}=c(t),i=0;function a(e){i+=d(e,n,r);for(let t of e.children)a(t)}return a(e),i}function u(e,t){let{countable:n,containers:r}=c(t),i=d(e,n,r);return l(e,t)+(i>0?0:1)}function d(n,r,a){if(!n.isNamed||e.has(n.type)||g(n))return 0;let o=0,s=p(n,a)&&!a.has(n.type)&&!t.has(n.type);return(f(n,r)||s||_(n))&&!m(n,r)&&(o+=1),i.has(n.type)&&(o+=b(n).length),o}function f(e,t){return t.has(e.type)?a.has(e.type)?e.childForFieldName(`body`)!==null:e.type!==`resource`||e.childForFieldName(`name`)!==null:!1}function p(e,t){let n=e.parent;for(;n&&n.type===`parenthesized_statements`;)n=n.parent;return n!==null&&t.has(n.type)}function m(e,t){if(e.type!==`export_statement`)return!1;let n=e.childForFieldName(`declaration`);return n!==null&&(t.has(n.type)||n.type===`internal_module`||n.type===`ambient_declaration`)}const h=new Set([`init`,`initializer`,`condition`,`update`,`increment`]);function g(e){let t=e.parent;if(!t)return!1;if(t.type===`init_statement`&&t.parent?.type===`for_range_loop`)return!0;if(t.type!==`for_statement`&&t.type!==`for_clause`&&t.type!==`for_range_loop`)return!1;for(let n=0;n<t.childCount;n+=1)if(t.child(n)?.id===e.id)return h.has(t.fieldNameForChild(n)??``);return!1}function _(e){let t=e.parent?.type;return e.type===`block`&&t===`class_body`||n.has(e.type)&&t===`interface_body`||t===`match_arm`&&e.type!==`block`&&y(e,`value`)||e.type===`method_signature`&&t===`class_body`||e.type===`internal_module`&&t!==`expression_statement`||(t===`method`||t===`singleton_method`)&&e.type!==`body_statement`&&y(e,`body`)?!0:e.type===`friend_declaration`?!e.namedChildren.some(e=>e.type===`declaration`||e.type===`function_definition`):e.type===`macro_invocation`&&(t===`source_file`||t===`declaration_list`)?!0:e.type===`field_declaration`&&t===`field_declaration_list`?v(e.parent?.parent,`struct_type`):e.type===`method_elem`||e.type===`method_spec`||e.type===`type_elem`?v(e.parent,`interface_type`):!1}function v(e,t){return e?.type===t?e.parent?.type===`type_spec`||e.parent?.type===`type_alias`:!1}function y(e,t){let n=e.parent;if(!n)return!1;for(let r=0;r<n.childCount;r+=1)if(n.child(r)?.id===e.id)return n.fieldNameForChild(r)===t;return!1}function b(e){let t=[];for(let n=0;n<e.childCount;n+=1){let i=e.child(n);i&&e.fieldNameForChild(n)===`alternative`&&!r.has(i.type)&&t.push(i)}return t}export{e as commentNodeTypes,u as countFunctionNcss,l as countNcss,s as invalidateNcssSetsCache};
1
+ const e=new Set([`comment`,`line_comment`,`block_comment`]),t=new Set([`attribute_item`,`inner_attribute_item`,`empty_statement`,`heredoc_body`,`parenthesized_statements`]),n=new Set([`property_signature`,`method_signature`,`index_signature`,`construct_signature`,`call_signature`]),r=new Set([`else_clause`,`elif_clause`,`else`,`elsif`]),i=new Set([`if_statement`,`if_expression`]),a=new Set([`struct_specifier`,`enum_specifier`,`union_specifier`,`class_specifier`]),o=new WeakMap;function s(e){o.delete(e)}function c(e){let t=o.get(e);return t||(t={countable:new Set(e.ncssNodeTypes),containers:new Set(e.ncssContainerNodeTypes)},o.set(e,t)),t}function l(e,t){let{countable:n,containers:r}=c(t),i=0;function a(e){i+=u(e,n,r);for(let t of e.children)a(t)}return a(e),i}function u(n,r,a){if(!n.isNamed||e.has(n.type)||h(n))return 0;let o=0,s=f(n,a)&&!a.has(n.type)&&!t.has(n.type);return(d(n,r)||s||g(n))&&!p(n,r)&&(o+=1),i.has(n.type)&&(o+=y(n).length),o}function d(e,t){return t.has(e.type)?a.has(e.type)?e.childForFieldName(`body`)!==null:e.type!==`resource`||e.childForFieldName(`name`)!==null:!1}function f(e,t){let n=e.parent;for(;n&&n.type===`parenthesized_statements`;)n=n.parent;return n!==null&&t.has(n.type)}function p(e,t){if(e.type!==`export_statement`)return!1;let n=e.childForFieldName(`declaration`);return n!==null&&(t.has(n.type)||n.type===`internal_module`||n.type===`ambient_declaration`)}const m=new Set([`init`,`initializer`,`condition`,`update`,`increment`]);function h(e){let t=e.parent;if(!t)return!1;if(t.type===`init_statement`&&t.parent?.type===`for_range_loop`)return!0;if(t.type!==`for_statement`&&t.type!==`for_clause`&&t.type!==`for_range_loop`)return!1;for(let n=0;n<t.childCount;n+=1)if(t.child(n)?.id===e.id)return m.has(t.fieldNameForChild(n)??``);return!1}function g(e){let t=e.parent?.type;return e.type===`block`&&t===`class_body`||n.has(e.type)&&t===`interface_body`||t===`match_arm`&&e.type!==`block`&&v(e,`value`)||e.type===`method_signature`&&t===`class_body`||e.type===`internal_module`&&t!==`expression_statement`||(t===`method`||t===`singleton_method`)&&e.type!==`body_statement`&&v(e,`body`)?!0:e.type===`friend_declaration`?!e.namedChildren.some(e=>e.type===`declaration`||e.type===`function_definition`):e.type===`macro_invocation`&&(t===`source_file`||t===`declaration_list`)?!0:e.type===`field_declaration`&&t===`field_declaration_list`?_(e.parent?.parent,`struct_type`):e.type===`method_elem`||e.type===`method_spec`||e.type===`type_elem`?_(e.parent,`interface_type`):!1}function _(e,t){return e?.type===t?e.parent?.type===`type_spec`||e.parent?.type===`type_alias`:!1}function v(e,t){let n=e.parent;if(!n)return!1;for(let r=0;r<n.childCount;r+=1)if(n.child(r)?.id===e.id)return n.fieldNameForChild(r)===t;return!1}function y(e){let t=[];for(let n=0;n<e.childCount;n+=1){let i=e.child(n);i&&e.fieldNameForChild(n)===`alternative`&&!r.has(i.type)&&t.push(i)}return t}export{e as commentNodeTypes,l as countNcss,c as getNcssSets,s as invalidateNcssSetsCache,u as ncssContribution};
2
2
  //# sourceMappingURL=ncss.js.map
package/dist/ncss.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ncss.js","names":[],"sources":["../src/ncss.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport type { LanguageDefinition } from './types.js';\n\nexport const commentNodeTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby\n// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),\n// and Ruby statement parentheses (transparent wrappers whose children count instead).\nconst positionalExclusionTypes = new Set([\n 'attribute_item',\n 'inner_attribute_item',\n 'empty_statement',\n 'heredoc_body',\n 'parenthesized_statements',\n]);\n\n// TypeScript interface members count like Java interface members, but the same node types appear\n// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so\n// they only count directly under an interface body.\nconst interfaceMemberNodeTypes = new Set([\n 'property_signature',\n 'method_signature',\n 'index_signature',\n 'construct_signature',\n 'call_signature',\n]);\n\n// An if-branch wrapped in one of these already counts through ncssNodeTypes; a bare `alternative`\n// (Java/Go put the else branch directly in the field) needs the extra `else` count instead.\nconst elseClauseNodeTypes = new Set(['else_clause', 'elif_clause', 'else', 'elsif']);\n\nconst ifNodeTypes = new Set(['if_statement', 'if_expression']);\n\n// C/C++ type specifiers only declare something when they carry a body (`struct S { ... }`);\n// without one they are mere type references inside other declarations.\nconst bodylessNcssSpecifierTypes = new Set([\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'class_specifier',\n]);\n\n/**\n * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,\n * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources\n * resource); `try` itself, braces, blank lines, and comments count 0.\n */\ninterface NcssSets {\n countable: Set<string>;\n containers: Set<string>;\n}\n\n// Cached per language: countNcss runs once per function plus once per file, so per-call Set\n// construction would add a measurable constant factor on large files.\nconst ncssSetsCache = new WeakMap<LanguageDefinition, NcssSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nexport function invalidateNcssSetsCache(language: LanguageDefinition): void {\n ncssSetsCache.delete(language);\n}\n\nfunction getNcssSets(language: LanguageDefinition): NcssSets {\n let sets = ncssSetsCache.get(language);\n if (!sets) {\n sets = { countable: new Set(language.ncssNodeTypes), containers: new Set(language.ncssContainerNodeTypes) };\n ncssSetsCache.set(language, sets);\n }\n return sets;\n}\n\nexport function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n let count = 0;\n\n function visit(current: Parser.SyntaxNode): void {\n count += ncssContribution(current, countable, containers);\n for (const child of current.children) {\n visit(child);\n }\n }\n\n visit(node);\n return count;\n}\n\n/**\n * Per-function NCSS: the function's whole subtree plus 1 for the declaration itself when the\n * function node carries no countable declaration of its own (arrow functions, lambdas, blocks).\n */\nexport function countFunctionNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n const selfContribution = ncssContribution(node, countable, containers);\n return countNcss(node, language) + (selfContribution > 0 ? 0 : 1);\n}\n\nfunction ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number {\n if (!node.isNamed || commentNodeTypes.has(node.type) || isForHeaderNode(node)) {\n return 0;\n }\n\n let contribution = 0;\n const positional =\n isInContainerPosition(node, containers) && !containers.has(node.type) && !positionalExclusionTypes.has(node.type);\n if (\n (countsThroughNodeType(node, countable) || positional || countsContextually(node)) &&\n !isDeclarationWrapper(node, countable)\n ) {\n contribution += 1;\n }\n\n // A bare else branch (Java/Go `alternative:` without an else-clause wrapper) counts 1 like the\n // `else` keyword does in PMD; an `else if` chain charges the nested if separately on top.\n if (ifNodeTypes.has(node.type)) {\n contribution += findBareAlternatives(node).length;\n }\n\n return contribution;\n}\n\nfunction countsThroughNodeType(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (!countable.has(node.type)) {\n return false;\n }\n if (bodylessNcssSpecifierTypes.has(node.type)) {\n return node.childForFieldName('body') !== null;\n }\n // A try-with-resources `resource` counts only when it declares a variable; `try (r)` reuses an\n // existing one and adds no statement (matching PMD).\n if (node.type === 'resource') {\n return node.childForFieldName('name') !== null;\n }\n return true;\n}\n\n/**\n * Direct container children count positionally; Ruby's `(foo; bar)` statement parentheses are\n * transparent (through arbitrary nesting), so their children count when the parentheses\n * themselves sit in a container.\n */\nfunction isInContainerPosition(node: Parser.SyntaxNode, containers: Set<string>): boolean {\n let ancestor = node.parent;\n while (ancestor && ancestor.type === 'parenthesized_statements') {\n ancestor = ancestor.parent;\n }\n return ancestor !== null && containers.has(ancestor.type);\n}\n\n/**\n * `export const x = 1` nests a countable declaration inside `export_statement`; only the inner\n * declaration counts, mirroring how PMD counts one statement per declared entity. The nested\n * declaration may also count contextually (`export namespace N {}` → internal_module) or sit one\n * level deeper (`export declare function f(): void;` → ambient_declaration > function_signature).\n */\nfunction isDeclarationWrapper(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (node.type !== 'export_statement') {\n return false;\n }\n const declaration = node.childForFieldName('declaration');\n return (\n declaration !== null &&\n (countable.has(declaration.type) ||\n declaration.type === 'internal_module' ||\n declaration.type === 'ambient_declaration')\n );\n}\n\nconst forHeaderFieldNames = new Set(['init', 'initializer', 'condition', 'update', 'increment']);\n\n/**\n * Statement-shaped nodes in a `for` header (`for (int i = 0; i < n; i++)`) are part of the loop\n * statement, which already counts; PMD does not count them separately. JavaScript parses the\n * condition as an `expression_statement` and Go parses the update as an `inc_statement`, so all\n * header fields must be excluded, not just the initializer.\n */\nfunction isForHeaderNode(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // C++20 range-for initializers nest one level deeper: for_range_loop > init_statement > node.\n if (parent.type === 'init_statement' && parent.parent?.type === 'for_range_loop') {\n return true;\n }\n if (parent.type !== 'for_statement' && parent.type !== 'for_clause' && parent.type !== 'for_range_loop') {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return forHeaderFieldNames.has(parent.fieldNameForChild(index) ?? '');\n }\n }\n return false;\n}\n\n/** Statements only countable by their position: constructs without a dedicated statement node. */\nfunction countsContextually(node: Parser.SyntaxNode): boolean {\n const parentType = node.parent?.type;\n // A Java instance initializer is a bare `block` in the class body; PMD counts it like the\n // `static_initializer` declaration it parallels.\n if (node.type === 'block' && parentType === 'class_body') {\n return true;\n }\n // TypeScript interface members (see interfaceMemberNodeTypes).\n if (interfaceMemberNodeTypes.has(node.type) && parentType === 'interface_body') {\n return true;\n }\n // A braceless Rust match-arm body (`1 => foo()`) has no expression_statement wrapper; count the\n // value expression so braced and unbraced arms measure alike.\n if (parentType === 'match_arm' && node.type !== 'block' && isFieldOfParent(node, 'value')) {\n return true;\n }\n // A TypeScript class-body method overload signature declares a member like its interface twin.\n if (node.type === 'method_signature' && parentType === 'class_body') {\n return true;\n }\n // An ambient `declare namespace M { ... }` is a bare `internal_module`; the non-ambient\n // `namespace N { ... }` is wrapped in an `expression_statement`, which already counts.\n if (node.type === 'internal_module' && parentType !== 'expression_statement') {\n return true;\n }\n // A Ruby endless method (`def f(x) = expr`) stores its single-statement body directly in the\n // `body` field instead of a positional `body_statement` container.\n if (\n (parentType === 'method' || parentType === 'singleton_method') &&\n node.type !== 'body_statement' &&\n isFieldOfParent(node, 'body')\n ) {\n return true;\n }\n // C++ `friend class X;` declares on its own; `friend void g() { ... }` merely wraps a counted\n // definition.\n if (node.type === 'friend_declaration') {\n return !node.namedChildren.some((child) => child.type === 'declaration' || child.type === 'function_definition');\n }\n // A Rust item-position macro invocation (`foo! {}` at module level) has no expression_statement\n // wrapper; the semicolon form does and already counts through it.\n if (node.type === 'macro_invocation' && (parentType === 'source_file' || parentType === 'declaration_list')) {\n return true;\n }\n // Go struct fields and interface members count like other languages' member declarations, but\n // only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,\n // `func f(h interface{ ... })`) are part of one declaration.\n if (node.type === 'field_declaration' && parentType === 'field_declaration_list') {\n return isGoDeclaredTypeBody(node.parent?.parent, 'struct_type');\n }\n if (node.type === 'method_elem' || node.type === 'method_spec' || node.type === 'type_elem') {\n return isGoDeclaredTypeBody(node.parent, 'interface_type');\n }\n return false;\n}\n\nfunction isGoDeclaredTypeBody(typeNode: Parser.SyntaxNode | null | undefined, expectedType: string): boolean {\n if (typeNode?.type !== expectedType) {\n return false;\n }\n return typeNode.parent?.type === 'type_spec' || typeNode.parent?.type === 'type_alias';\n}\n\nfunction isFieldOfParent(node: Parser.SyntaxNode, fieldName: string): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return parent.fieldNameForChild(index) === fieldName;\n }\n }\n return false;\n}\n\nfunction findBareAlternatives(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const alternatives: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === 'alternative' && !elseClauseNodeTypes.has(child.type)) {\n alternatives.push(child);\n }\n }\n return alternatives;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ncss.js","names":[],"sources":["../src/ncss.ts"],"sourcesContent":["import type Parser from 'tree-sitter';\nimport type { LanguageDefinition } from './types.js';\n\nexport const commentNodeTypes = new Set(['comment', 'line_comment', 'block_comment']);\n\n// Nodes never counted positionally inside NCSS containers: metadata, empty statements, Ruby\n// heredoc bodies (tree-sitter emits them as siblings of the statement that opened the heredoc),\n// and Ruby statement parentheses (transparent wrappers whose children count instead).\nconst positionalExclusionTypes = new Set([\n 'attribute_item',\n 'inner_attribute_item',\n 'empty_statement',\n 'heredoc_body',\n 'parenthesized_statements',\n]);\n\n// TypeScript interface members count like Java interface members, but the same node types appear\n// inside object-type annotations (`let x: { a: number }`), which are part of one declaration, so\n// they only count directly under an interface body.\nconst interfaceMemberNodeTypes = new Set([\n 'property_signature',\n 'method_signature',\n 'index_signature',\n 'construct_signature',\n 'call_signature',\n]);\n\n// An if-branch wrapped in one of these already counts through ncssNodeTypes; a bare `alternative`\n// (Java/Go put the else branch directly in the field) needs the extra `else` count instead.\nconst elseClauseNodeTypes = new Set(['else_clause', 'elif_clause', 'else', 'elsif']);\n\nconst ifNodeTypes = new Set(['if_statement', 'if_expression']);\n\n// C/C++ type specifiers only declare something when they carry a body (`struct S { ... }`);\n// without one they are mere type references inside other declarations.\nconst bodylessNcssSpecifierTypes = new Set([\n 'struct_specifier',\n 'enum_specifier',\n 'union_specifier',\n 'class_specifier',\n]);\n\n/**\n * Counts non-commenting source statements (NCSS) in the subtree, PMD-style: one per declaration,\n * statement, and clause (`else`, `case`/`default` label, `catch`, `finally`, try-with-resources\n * resource); `try` itself, braces, blank lines, and comments count 0.\n */\nexport interface NcssSets {\n countable: Set<string>;\n containers: Set<string>;\n}\n\n// Cached per language: countNcss runs once per function plus once per file, so per-call Set\n// construction would add a measurable constant factor on large files.\nconst ncssSetsCache = new WeakMap<LanguageDefinition, NcssSets>();\n\n/** Drops the cached sets so a re-registered (possibly mutated) definition rebuilds them. */\nexport function invalidateNcssSetsCache(language: LanguageDefinition): void {\n ncssSetsCache.delete(language);\n}\n\nexport function getNcssSets(language: LanguageDefinition): NcssSets {\n let sets = ncssSetsCache.get(language);\n if (!sets) {\n sets = { countable: new Set(language.ncssNodeTypes), containers: new Set(language.ncssContainerNodeTypes) };\n ncssSetsCache.set(language, sets);\n }\n return sets;\n}\n\nexport function countNcss(node: Parser.SyntaxNode, language: LanguageDefinition): number {\n const { countable, containers } = getNcssSets(language);\n let count = 0;\n\n function visit(current: Parser.SyntaxNode): void {\n count += ncssContribution(current, countable, containers);\n for (const child of current.children) {\n visit(child);\n }\n }\n\n visit(node);\n return count;\n}\n\nexport function ncssContribution(node: Parser.SyntaxNode, countable: Set<string>, containers: Set<string>): number {\n if (!node.isNamed || commentNodeTypes.has(node.type) || isForHeaderNode(node)) {\n return 0;\n }\n\n let contribution = 0;\n const positional =\n isInContainerPosition(node, containers) && !containers.has(node.type) && !positionalExclusionTypes.has(node.type);\n if (\n (countsThroughNodeType(node, countable) || positional || countsContextually(node)) &&\n !isDeclarationWrapper(node, countable)\n ) {\n contribution += 1;\n }\n\n // A bare else branch (Java/Go `alternative:` without an else-clause wrapper) counts 1 like the\n // `else` keyword does in PMD; an `else if` chain charges the nested if separately on top.\n if (ifNodeTypes.has(node.type)) {\n contribution += findBareAlternatives(node).length;\n }\n\n return contribution;\n}\n\nfunction countsThroughNodeType(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (!countable.has(node.type)) {\n return false;\n }\n if (bodylessNcssSpecifierTypes.has(node.type)) {\n return node.childForFieldName('body') !== null;\n }\n // A try-with-resources `resource` counts only when it declares a variable; `try (r)` reuses an\n // existing one and adds no statement (matching PMD).\n if (node.type === 'resource') {\n return node.childForFieldName('name') !== null;\n }\n return true;\n}\n\n/**\n * Direct container children count positionally; Ruby's `(foo; bar)` statement parentheses are\n * transparent (through arbitrary nesting), so their children count when the parentheses\n * themselves sit in a container.\n */\nfunction isInContainerPosition(node: Parser.SyntaxNode, containers: Set<string>): boolean {\n let ancestor = node.parent;\n while (ancestor && ancestor.type === 'parenthesized_statements') {\n ancestor = ancestor.parent;\n }\n return ancestor !== null && containers.has(ancestor.type);\n}\n\n/**\n * `export const x = 1` nests a countable declaration inside `export_statement`; only the inner\n * declaration counts, mirroring how PMD counts one statement per declared entity. The nested\n * declaration may also count contextually (`export namespace N {}` → internal_module) or sit one\n * level deeper (`export declare function f(): void;` → ambient_declaration > function_signature).\n */\nfunction isDeclarationWrapper(node: Parser.SyntaxNode, countable: Set<string>): boolean {\n if (node.type !== 'export_statement') {\n return false;\n }\n const declaration = node.childForFieldName('declaration');\n return (\n declaration !== null &&\n (countable.has(declaration.type) ||\n declaration.type === 'internal_module' ||\n declaration.type === 'ambient_declaration')\n );\n}\n\nconst forHeaderFieldNames = new Set(['init', 'initializer', 'condition', 'update', 'increment']);\n\n/**\n * Statement-shaped nodes in a `for` header (`for (int i = 0; i < n; i++)`) are part of the loop\n * statement, which already counts; PMD does not count them separately. JavaScript parses the\n * condition as an `expression_statement` and Go parses the update as an `inc_statement`, so all\n * header fields must be excluded, not just the initializer.\n */\nfunction isForHeaderNode(node: Parser.SyntaxNode): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n // C++20 range-for initializers nest one level deeper: for_range_loop > init_statement > node.\n if (parent.type === 'init_statement' && parent.parent?.type === 'for_range_loop') {\n return true;\n }\n if (parent.type !== 'for_statement' && parent.type !== 'for_clause' && parent.type !== 'for_range_loop') {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return forHeaderFieldNames.has(parent.fieldNameForChild(index) ?? '');\n }\n }\n return false;\n}\n\n/** Statements only countable by their position: constructs without a dedicated statement node. */\nfunction countsContextually(node: Parser.SyntaxNode): boolean {\n const parentType = node.parent?.type;\n // A Java instance initializer is a bare `block` in the class body; PMD counts it like the\n // `static_initializer` declaration it parallels.\n if (node.type === 'block' && parentType === 'class_body') {\n return true;\n }\n // TypeScript interface members (see interfaceMemberNodeTypes).\n if (interfaceMemberNodeTypes.has(node.type) && parentType === 'interface_body') {\n return true;\n }\n // A braceless Rust match-arm body (`1 => foo()`) has no expression_statement wrapper; count the\n // value expression so braced and unbraced arms measure alike.\n if (parentType === 'match_arm' && node.type !== 'block' && isFieldOfParent(node, 'value')) {\n return true;\n }\n // A TypeScript class-body method overload signature declares a member like its interface twin.\n if (node.type === 'method_signature' && parentType === 'class_body') {\n return true;\n }\n // An ambient `declare namespace M { ... }` is a bare `internal_module`; the non-ambient\n // `namespace N { ... }` is wrapped in an `expression_statement`, which already counts.\n if (node.type === 'internal_module' && parentType !== 'expression_statement') {\n return true;\n }\n // A Ruby endless method (`def f(x) = expr`) stores its single-statement body directly in the\n // `body` field instead of a positional `body_statement` container.\n if (\n (parentType === 'method' || parentType === 'singleton_method') &&\n node.type !== 'body_statement' &&\n isFieldOfParent(node, 'body')\n ) {\n return true;\n }\n // C++ `friend class X;` declares on its own; `friend void g() { ... }` merely wraps a counted\n // definition.\n if (node.type === 'friend_declaration') {\n return !node.namedChildren.some((child) => child.type === 'declaration' || child.type === 'function_definition');\n }\n // A Rust item-position macro invocation (`foo! {}` at module level) has no expression_statement\n // wrapper; the semicolon form does and already counts through it.\n if (node.type === 'macro_invocation' && (parentType === 'source_file' || parentType === 'declaration_list')) {\n return true;\n }\n // Go struct fields and interface members count like other languages' member declarations, but\n // only inside a named type declaration; inline anonymous types (`var x struct{ ... }`,\n // `func f(h interface{ ... })`) are part of one declaration.\n if (node.type === 'field_declaration' && parentType === 'field_declaration_list') {\n return isGoDeclaredTypeBody(node.parent?.parent, 'struct_type');\n }\n if (node.type === 'method_elem' || node.type === 'method_spec' || node.type === 'type_elem') {\n return isGoDeclaredTypeBody(node.parent, 'interface_type');\n }\n return false;\n}\n\nfunction isGoDeclaredTypeBody(typeNode: Parser.SyntaxNode | null | undefined, expectedType: string): boolean {\n if (typeNode?.type !== expectedType) {\n return false;\n }\n return typeNode.parent?.type === 'type_spec' || typeNode.parent?.type === 'type_alias';\n}\n\nfunction isFieldOfParent(node: Parser.SyntaxNode, fieldName: string): boolean {\n const parent = node.parent;\n if (!parent) {\n return false;\n }\n for (let index = 0; index < parent.childCount; index += 1) {\n if (parent.child(index)?.id === node.id) {\n return parent.fieldNameForChild(index) === fieldName;\n }\n }\n return false;\n}\n\nfunction findBareAlternatives(node: Parser.SyntaxNode): Parser.SyntaxNode[] {\n const alternatives: Parser.SyntaxNode[] = [];\n for (let index = 0; index < node.childCount; index += 1) {\n const child = node.child(index);\n if (child && node.fieldNameForChild(index) === 'alternative' && !elseClauseNodeTypes.has(child.type)) {\n alternatives.push(child);\n }\n }\n return alternatives;\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -23,8 +23,9 @@ export interface DuplicationOptions {
23
23
  minTokens?: number;
24
24
  /**
25
25
  * Maximum normalized-token gap between two adjacent duplicate groups merged into one gapped
26
- * (Type-3) clone group (default 30). 0 disables merging. Applies to within-file detection only:
27
- * cross-file matching compares whole candidates and does not merge across gaps yet.
26
+ * (Type-3) clone group (default 30). 0 disables merging. Applies to within-file and cross-file
27
+ * detection: two near-identical regions in separate files that differ by one edited statement
28
+ * merge into one cross-file group when each file's fragments are gap-adjacent.
28
29
  */
29
30
  maxGapTokens?: number;
30
31
  /**
@@ -92,7 +93,6 @@ export interface FunctionMetrics {
92
93
  export interface CallGraphMetrics {
93
94
  callCount: number;
94
95
  uniqueCalleeCount: number;
95
- internalCallCount: number;
96
96
  internalEdgeCount: number;
97
97
  recursiveFunctionCount: number;
98
98
  maxFanIn: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-gauge",
3
- "version": "1.14.0",
3
+ "version": "2.0.0",
4
4
  "description": "Measure code metrics with tree-sitter.",
5
5
  "keywords": [
6
6
  "cli",