From 8cd7de18cbf183fb37ab8d9b87009b8b722dfbdc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 02:18:09 +0000 Subject: [PATCH 1/2] perf: avoid node.children array allocation in hot AST traversal loops Tree-sitter's node.children getter materializes a new JS array on every access rather than returning a cached property. The main visit()/traversal loops in goAnalyzer, javaAnalyzer, jsLikeAnalyzer (used by JS/TS/TSX), pythonAnalyzer, rustAnalyzer, and two remaining loops in csharpAnalyzer ran this allocation on every single AST node visited during analysis - the hottest path in the extension. csharpAnalyzer.ts already used the childCount/child(i) pattern in most places with a comment explaining why; this change applies the same already-proven, behavior-preserving pattern consistently to the remaining loops across all language analyzers, eliminating one throwaway array allocation per node visited. No behavioral change: iterates the same children in the same order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/metricsAnalyzer/languages/csharpAnalyzer.ts | 6 ++++-- src/metricsAnalyzer/languages/goAnalyzer.ts | 9 ++++++--- src/metricsAnalyzer/languages/javaAnalyzer.ts | 9 ++++++--- src/metricsAnalyzer/languages/jsLikeAnalyzer.ts | 6 ++++-- src/metricsAnalyzer/languages/pythonAnalyzer.ts | 12 ++++++++---- src/metricsAnalyzer/languages/rustAnalyzer.ts | 6 ++++-- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/metricsAnalyzer/languages/csharpAnalyzer.ts b/src/metricsAnalyzer/languages/csharpAnalyzer.ts index d8aeac1..c458f25 100644 --- a/src/metricsAnalyzer/languages/csharpAnalyzer.ts +++ b/src/metricsAnalyzer/languages/csharpAnalyzer.ts @@ -171,7 +171,8 @@ export class CSharpMetricsAnalyzer { } // Continue traversing child nodes - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; visit(child); } }; @@ -444,7 +445,8 @@ export class CSharpMetricsAnalyzer { const isPreproc = node.type.startsWith("preproc_"); if (nests) { this.nesting++; } if (isPreproc) { this.preprocessorDepth++; } - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; if (!this.isFunctionDeclaration(child)) { this.visit(child); } diff --git a/src/metricsAnalyzer/languages/goAnalyzer.ts b/src/metricsAnalyzer/languages/goAnalyzer.ts index 7e84103..5f15d33 100644 --- a/src/metricsAnalyzer/languages/goAnalyzer.ts +++ b/src/metricsAnalyzer/languages/goAnalyzer.ts @@ -147,7 +147,8 @@ export class GoMetricsAnalyzer { return; } - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; visit(child); } }; @@ -350,7 +351,8 @@ export class GoMetricsAnalyzer { ? node.childForFieldName("alternative") : null; - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; if (this.isFunctionDeclaration(child)) { continue; } if (alternative && child === alternative) { this.visitAlternative(child); @@ -395,7 +397,8 @@ export class GoMetricsAnalyzer { // (do NOT bump nesting again — the outer if already did). // We must also intercept any nested alternative (further else-if/else chains). const innerAlt = node.childForFieldName("alternative"); - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; if (this.isFunctionDeclaration(child)) { continue; } if (child.type === "else") { continue; } // skip the 'else' keyword token if (innerAlt && child === innerAlt) { diff --git a/src/metricsAnalyzer/languages/javaAnalyzer.ts b/src/metricsAnalyzer/languages/javaAnalyzer.ts index 8ed3c17..b697c06 100644 --- a/src/metricsAnalyzer/languages/javaAnalyzer.ts +++ b/src/metricsAnalyzer/languages/javaAnalyzer.ts @@ -130,7 +130,8 @@ export class JavaMetricsAnalyzer { // Do not recurse into the method body again — analyzeMethod handles it return; } - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; visit(child); } }; @@ -222,7 +223,8 @@ export class JavaMetricsAnalyzer { * @param node - The block or body node to visit */ private visitBody(node: Parser.SyntaxNode): void { - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; this.visit(child); } } @@ -271,7 +273,8 @@ export class JavaMetricsAnalyzer { // to avoid double-counting (the else_clause +1 already accounts for it). const nests = this.increasesNesting(node); if (nests) { this.nesting++; } - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; if (!this.isMethodDeclaration(child)) { this.visit(child, elseBranchNode !== null && child === elseBranchNode); } diff --git a/src/metricsAnalyzer/languages/jsLikeAnalyzer.ts b/src/metricsAnalyzer/languages/jsLikeAnalyzer.ts index 1b565d5..33f82d7 100644 --- a/src/metricsAnalyzer/languages/jsLikeAnalyzer.ts +++ b/src/metricsAnalyzer/languages/jsLikeAnalyzer.ts @@ -174,7 +174,8 @@ export class JsLikeMetricsAnalyzer { this.details = savedDetails; this.nesting = savedNesting; } else { - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; this.collectFunctions(child, functions); } } @@ -341,7 +342,8 @@ export class JsLikeMetricsAnalyzer { this.nesting++; } - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; // Nested functions: add a nesting penalty and continue analyzing their body // as part of the outer function at an increased nesting level, so that any // complexity inside the nested body (ternaries, loops, etc.) counts toward diff --git a/src/metricsAnalyzer/languages/pythonAnalyzer.ts b/src/metricsAnalyzer/languages/pythonAnalyzer.ts index 2f97838..d7d9fcf 100644 --- a/src/metricsAnalyzer/languages/pythonAnalyzer.ts +++ b/src/metricsAnalyzer/languages/pythonAnalyzer.ts @@ -122,7 +122,8 @@ export class PythonMetricsAnalyzer { const name = nameNode ? this.sourceText.substring(nameNode.startIndex, nameNode.endIndex) : ""; - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; visit(child, name); } return; @@ -144,7 +145,8 @@ export class PythonMetricsAnalyzer { return; } - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; visit(child, className); } }; @@ -231,7 +233,8 @@ export class PythonMetricsAnalyzer { }); } this.nesting++; - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; this.visit(child); } this.nesting--; @@ -253,7 +256,8 @@ export class PythonMetricsAnalyzer { // Conditionally bump nesting, iterate children once, then restore. const nests = this.increasesNesting(node); if (nests) { this.nesting++; } - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; this.visit(child); } if (nests) { this.nesting--; } diff --git a/src/metricsAnalyzer/languages/rustAnalyzer.ts b/src/metricsAnalyzer/languages/rustAnalyzer.ts index bc44e86..1ed4105 100644 --- a/src/metricsAnalyzer/languages/rustAnalyzer.ts +++ b/src/metricsAnalyzer/languages/rustAnalyzer.ts @@ -149,7 +149,8 @@ export class RustMetricsAnalyzer { } } } else { - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; visit(child); } } @@ -269,7 +270,8 @@ export class RustMetricsAnalyzer { // shouldSkipChildStructuralIncrement handles else-if chains to avoid double-counting. const nests = this.increasesNesting(node); if (nests) { this.nesting++; } - for (const child of node.children) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i)!; if (!this.isFunctionDeclaration(child)) { this.visit(child, this.shouldSkipChildStructuralIncrement(node, child)); } From f8f4f07817939295563d62392b1ecb7532713e16 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:23:32 +0000 Subject: [PATCH 2/2] ci: raise coverage thresholds to match current levels (#551) --- .c8rc.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.c8rc.json b/.c8rc.json index 77fc22d..21e1904 100644 --- a/.c8rc.json +++ b/.c8rc.json @@ -5,7 +5,10 @@ ], "exclude": [ "out/test/**/*", - "out/unit/**/*" + "out/unit/**/*", + "out/extension.js", + "out/configuration.js", + "out/providers/**" ], "reporter": [ "text", @@ -15,8 +18,8 @@ "reports-dir": "./coverage", "clean": true, "check-coverage": true, - "lines": 80, - "statements": 80, + "lines": 95, + "statements": 95, "branches": 88, - "functions": 95 + "functions": 97 }