diff --git a/src/metricsAnalyzer/languages/complexityHelpers.ts b/src/metricsAnalyzer/languages/complexityHelpers.ts index 97f1768..f47cadc 100644 --- a/src/metricsAnalyzer/languages/complexityHelpers.ts +++ b/src/metricsAnalyzer/languages/complexityHelpers.ts @@ -81,3 +81,35 @@ export function hasLabelChild(node: Parser.SyntaxNode, labelTypes: string | read } return typeof labelTypes === "string" ? childType === labelTypes : labelTypes.includes(childType); } + +/** + * Walks up from `node` to find the name of the nearest enclosing type + * declaration (class, struct, interface, record, or enum), returning `null` + * if no such ancestor exists (e.g. top-level local functions). + * + * Shared by the C# and Java analyzers, which both identify the enclosing + * type by walking `node.parent` until a type-declaration node type is found, + * then reading its `name` field. + * + * @param node - The syntax node to start searching from (typically a function/method declaration) + * @param typeDeclarationTypes - Node type name(s) that represent a type declaration + * @param sourceText - The full source text, used to extract the name substring + * @returns The enclosing type name, or null if none found + */ +export function findEnclosingTypeName( + node: Parser.SyntaxNode, + typeDeclarationTypes: ReadonlySet, + sourceText: string +): string | null { + let parent = node.parent; + while (parent) { + if (typeDeclarationTypes.has(parent.type)) { + const nameNode = parent.childForFieldName("name"); + if (nameNode) { + return sourceText.substring(nameNode.startIndex, nameNode.endIndex); + } + } + parent = parent.parent; + } + return null; +} diff --git a/src/metricsAnalyzer/languages/csharpAnalyzer.ts b/src/metricsAnalyzer/languages/csharpAnalyzer.ts index 0e0676f..3ca8acf 100644 --- a/src/metricsAnalyzer/languages/csharpAnalyzer.ts +++ b/src/metricsAnalyzer/languages/csharpAnalyzer.ts @@ -11,7 +11,7 @@ import Parser from "tree-sitter"; import CSharp from "tree-sitter-c-sharp"; -import { isOutermostInSameOperatorChain, getBinaryLogicalOperator } from "./complexityHelpers"; +import { isOutermostInSameOperatorChain, getBinaryLogicalOperator, findEnclosingTypeName } from "./complexityHelpers"; // Module-level singleton: parser initialization is expensive, so we reuse one instance per language. const _parser = new Parser(); @@ -296,17 +296,7 @@ export class CSharpMetricsAnalyzer { * @returns The enclosing type name, or null if none found */ private getEnclosingTypeName(node: Parser.SyntaxNode): string | null { - let parent = node.parent; - while (parent) { - if (CSharpMetricsAnalyzer.TYPE_DECLARATION_TYPES.has(parent.type)) { - const nameNode = parent.childForFieldName("name"); - if (nameNode) { - return this.sourceText.substring(nameNode.startIndex, nameNode.endIndex); - } - } - parent = parent.parent; - } - return null; + return findEnclosingTypeName(node, CSharpMetricsAnalyzer.TYPE_DECLARATION_TYPES, this.sourceText); } /** diff --git a/src/metricsAnalyzer/languages/javaAnalyzer.ts b/src/metricsAnalyzer/languages/javaAnalyzer.ts index 49bff73..181b824 100644 --- a/src/metricsAnalyzer/languages/javaAnalyzer.ts +++ b/src/metricsAnalyzer/languages/javaAnalyzer.ts @@ -11,7 +11,7 @@ import Parser from "tree-sitter"; import Java from "tree-sitter-java"; -import { isOutermostInSameOperatorChain, getBinaryLogicalOperator } from "./complexityHelpers"; +import { isOutermostInSameOperatorChain, getBinaryLogicalOperator, findEnclosingTypeName } from "./complexityHelpers"; // Module-level singleton: parser initialization is expensive, so we reuse one instance per language. const _parser = new Parser(); @@ -201,24 +201,10 @@ export class JavaMetricsAnalyzer { ? this.sourceText.substring(nameNode.startIndex, nameNode.endIndex) : ""; - // Walk up the AST to find the enclosing class, interface, enum, or record name - let parent = node.parent; - while (parent) { - if (JavaMetricsAnalyzer.TYPE_DECLARATION_TYPES.has(parent.type)) { - const classNameNode = parent.childForFieldName("name"); - if (classNameNode) { - const className = this.sourceText.substring( - classNameNode.startIndex, - classNameNode.endIndex - ); - return `${className}.${methodName}`; - } - } - parent = parent.parent; - } + const className = findEnclosingTypeName(node, JavaMetricsAnalyzer.TYPE_DECLARATION_TYPES, this.sourceText); /* c8 ignore next */ - return methodName; + return className ? `${className}.${methodName}` : methodName; } /**