From 64450a4504faa51d4601b3915f35f069c850db39 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 01:28:36 +0000 Subject: [PATCH 1/2] perf: replace O(n) indexOf+children with O(1) nextSibling traversal in C# analyzer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In CSharpMetricsAnalyzer two methods used parent.children.indexOf(node) which is an O(n) linear scan over the children array: 1. getMethodBody: when searching for a preproc_if sibling after a split method, previously called parent.children.indexOf(node) then iterated by index. Now uses node.nextSibling directly, eliminating both the O(n) indexOf scan and the intermediate children array allocation. 2. hasMatchingColonInSiblings: previously called parent.children.indexOf(errorNode) then iterated over the next 2 children by index. Now walks errorNode.nextSibling directly for up to 2 steps. tree-sitter SyntaxNode.nextSibling is O(1) (it is a pointer walk in the underlying C structure), so both hot paths now run in O(k) where k ≤ 2 or k = number of preprocessor siblings found — independent of class size. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../languages/csharpAnalyzer.ts | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/src/metricsAnalyzer/languages/csharpAnalyzer.ts b/src/metricsAnalyzer/languages/csharpAnalyzer.ts index 860beb1..2acd2e0 100644 --- a/src/metricsAnalyzer/languages/csharpAnalyzer.ts +++ b/src/metricsAnalyzer/languages/csharpAnalyzer.ts @@ -374,29 +374,25 @@ export class CSharpMetricsAnalyzer { // Check if this method ends with a semicolon (indicating it's incomplete due to preprocessing) const lastChild = node.children[node.children.length - 1]; if (lastChild && lastChild.type === ";") { - // This looks like a method signature split by preprocessor - try to find body in siblings - const parent = node.parent; - if (parent) { - // Look for preprocessor blocks that immediately follow this method - const methodIndex = parent.children.indexOf(node); - for (let i = methodIndex + 1; i < parent.children.length; i++) { - const sibling = parent.children[i]; - - // If we find a preproc_if, look inside it for method body content - if (sibling.type === "preproc_if") { - // Create a synthetic body by analyzing the content within the preprocessor block - return this.createSyntheticBodyFromPreprocessor(sibling); - } + // This looks like a method signature split by preprocessor - try to find body in siblings. + // Use nextSibling traversal (O(1) per step) instead of indexOf + index loop (O(n) scan). + let sibling = node.nextSibling; + while (sibling) { + // If we find a preproc_if, look inside it for method body content + if (sibling.type === "preproc_if") { + // Create a synthetic body by analyzing the content within the preprocessor block + return this.createSyntheticBodyFromPreprocessor(sibling); + } - // Stop searching if we hit another method or major declaration - if ( - this.isFunctionDeclaration(sibling) || - sibling.type === "class_declaration" || - sibling.type === "interface_declaration" - ) { - break; - } + // Stop searching if we hit another method or major declaration + if ( + this.isFunctionDeclaration(sibling) || + sibling.type === "class_declaration" || + sibling.type === "interface_declaration" + ) { + break; } + sibling = sibling.nextSibling; } } @@ -607,20 +603,14 @@ export class CSharpMetricsAnalyzer { * @returns True if a matching colon is found in nearby nodes */ private hasMatchingColonInSiblings(errorNode: Parser.SyntaxNode): boolean { - const parent = errorNode.parent; - if (!parent) { + if (!errorNode.parent) { return false; } - const errorIndex = parent.children.indexOf(errorNode); - - // Look in the next few sibling nodes for a colon - for ( - let i = errorIndex + 1; - i < Math.min(errorIndex + 3, parent.children.length); - i++ - ) { - const sibling = parent.children[i]; + // Walk up to 2 next siblings using O(1) nextSibling traversal (avoids the previous + // O(n) indexOf scan over parent.children). + let sibling = errorNode.nextSibling; + for (let steps = 0; sibling && steps < 2; steps++, sibling = sibling.nextSibling) { const siblingText = this.sourceText.substring( sibling.startIndex, sibling.endIndex From badb8e25e0ff6aa136529ee74286a9682cf1d15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <2493377+askpt@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:47:36 +0100 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/metricsAnalyzer/languages/csharpAnalyzer.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/metricsAnalyzer/languages/csharpAnalyzer.ts b/src/metricsAnalyzer/languages/csharpAnalyzer.ts index 2acd2e0..0237d6c 100644 --- a/src/metricsAnalyzer/languages/csharpAnalyzer.ts +++ b/src/metricsAnalyzer/languages/csharpAnalyzer.ts @@ -384,11 +384,9 @@ export class CSharpMetricsAnalyzer { return this.createSyntheticBodyFromPreprocessor(sibling); } - // Stop searching if we hit another method or major declaration if ( this.isFunctionDeclaration(sibling) || - sibling.type === "class_declaration" || - sibling.type === "interface_declaration" + CSharpMetricsAnalyzer.TYPE_DECLARATION_TYPES.has(sibling.type) ) { break; }