perf: cache C# ERROR/malformed-declaration reason to avoid duplicate regex scan - #565
Conversation
…regex scan getComplexityFromErrorNode() and getComplexityReasonFromErrorNode() (same for the malformed-declaration variants) both re-extract the identical source substring and re-run the same set of ~7 regex tests against ERROR nodes and malformed preprocessor declarations, once to compute the complexity increment and again immediately after to determine the human-readable reason string. Since visit() always calls the increment function then the reason function for the same node, cache the reason determined during the increment pass and reuse it, avoiding a duplicate substring allocation and regex pass per node. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #565 +/- ##
==========================================
+ Coverage 97.90% 97.95% +0.05%
==========================================
Files 10 10
Lines 3766 3815 +49
Branches 455 446 -9
==========================================
+ Hits 3687 3737 +50
+ Misses 79 78 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟡 Changes recommended
Cached syntax nodes can unnecessarily retain entire parse trees when analyzer instances are reused.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Optimizes C# malformed-syntax analysis by caching heuristic reason strings and avoiding duplicate regex scans.
Changes:
- Caches reasons for
ERRORand malformed declaration nodes. - Adds defensive fallback reason computation.
File summaries
| File | Description |
|---|---|
src/metricsAnalyzer/languages/csharpAnalyzer.ts |
Adds heuristic reason caching for C# analysis. |
Review details
Suppressed comments (1)
src/metricsAnalyzer/languages/csharpAnalyzer.ts:800
- The malformed-declaration fast path also keeps its
SyntaxNodecached after the value is used, retaining the associated parse tree for the lifetime of a reused analyzer. Clear the cache while consuming the reason, as in the ERROR-node path.
if (this.heuristicReasonCache?.node === declarationNode) {
return this.heuristicReasonCache.reason;
}
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (this.heuristicReasonCache?.node === errorNode) { | ||
| return this.heuristicReasonCache.reason; | ||
| } |
Summary
An explore pass looking for performance opportunities beyond the already-completed
node.childrenarray-allocation fixes found a real duplicate-work pattern incsharpAnalyzer.ts.Root cause
visit()callsgetComplexityIncrement(node)and, if it returns >0, immediately callsgetComplexityReason(node)for the same node. For C#ERRORnodes and malformed preprocessor declarations, both the increment path (getComplexityFromErrorNode/getComplexityFromMalformedDeclaration) and the reason path (getComplexityReasonFromErrorNode/getComplexityReasonFromMalformedDeclaration) independently:sourceText.substring(node.startIndex, node.endIndex))ERROR nodes can span large fragmented preprocessor blocks, so this duplicated substring + regex work on every hit.
Fix
The increment functions now record which pattern matched first (the same first-match order the reason functions already used) into a small
heuristicReasonCachefield keyed by node reference. The reason functions check this cache first and only fall back to full recomputation as a defensive path if called independently of the increment function (now/* c8 ignore */d as unreachable via normalvisit()flow, consistent with existing unreachable-fallback annotations in this file).No behavior change - same increments and reason strings are produced, just computed once instead of twice per node.
Trade-offs
heuristicReasonCache) to the analyzer instance, overwritten between nodes - negligible memory cost, no functional impact since it's only consulted by reference-equality against the node that populated it.Test Status
npm run compile: cleannpm run lint: cleannpm run test:unit: 227/227 passing🤖 This PR was created by Repo Assist, an automated AI assistant, as part of scheduled Task 8 (Performance Improvements).
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
releaseassets.githubusercontent.comTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.
Add this agentic workflow to your repo
To install this agentic workflow, run