Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
## 2026-04-08 - [Fast Dense Integer Set Tracking]
**Learning:** When keeping track of seen integer IDs that are dense and bounded (e.g. from 0 to N), using `new Set<number>()` incurs heavy allocation and insertion overhead compared to a fixed-size byte array.
**Action:** Replace `Set<number>` with `new Uint8Array(maxIndex)` and use `array[id] = 1` to track presence, which is ~15x faster and avoids garbage collection pauses in hot paths. (Benchmark context: `N=100,000` IDs, `bun` version 1.2.14, Linux x86_64, Intel Xeon 2.30GHz, 4 cores, 8GB RAM, averaged over 100 iterations comparing `Set<number>` addition vs `new Uint8Array(maxIndex)` indexed assignment `array[id] = 1`).

## 2024-05-01 - Optimizing TreeSitter AST node type checking
**Learning:** In hot paths or AST traversal loops (e.g., searching for parent class declarations or extracting symbols based on node type), dynamic string manipulation (`.toLowerCase().includes('class_declaration')`) or regular expression checks (`/class_declaration|class_definition|^class$/.test(nodeType)`) create unnecessary overhead and memory allocations.
**Action:** Replace these checks with a pre-defined static `Set` of exact node names (e.g., `['class_declaration', 'class_definition', 'class']`) to perform O(1) `.has()` lookups. This eliminates redundant allocations and improves parsing latency for large source files.
34 changes: 19 additions & 15 deletions language-server/src/core/tree-sitter-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ interface TreeSitterLib {
}

export class TreeSitterParser {
private static readonly CLASS_NODE_TYPES = new Set(['class_declaration', 'class_definition', 'class', 'struct_declaration', 'struct_definition', 'struct']);
private static readonly INTERFACE_NODE_TYPES = new Set(['interface_declaration', 'interface_definition', 'interface', 'trait_declaration', 'trait_definition', 'trait']);
private static readonly ENUM_NODE_TYPES = new Set(['enum_declaration', 'enum_definition', 'enum']);
private static readonly METHOD_NODE_TYPES = new Set(['method_declaration', 'method_definition', 'method']);
private static readonly FUNCTION_NODE_TYPES = new Set(['function_declaration', 'function_definition', 'function']);
private static readonly PROPERTY_NODE_TYPES = new Set(['property_declaration', 'property_definition']);
private static readonly VARIABLE_NODE_TYPES = new Set(['variable_declaration', 'variable_declarator']);
Comment on lines +42 to +48
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚑ Quick win

Keep controller ancestor detection class-only.

CLASS_NODE_TYPES now includes struct*, so getControllerRoutePrefix() can stop on an enclosing struct and treat its [Route] as a controller prefix. That changes C# endpoint resolution semantics; the old walk only targeted class containers. Use a narrower set for controller containers instead of reusing the broader symbol-classification set.

Suggested fix
+    private static readonly CONTROLLER_CONTAINER_NODE_TYPES = new Set(['class_declaration', 'class_definition', 'class']);
+
     private static readonly CLASS_NODE_TYPES = new Set(['class_declaration', 'class_definition', 'class', 'struct_declaration', 'struct_definition', 'struct']);
     private static readonly INTERFACE_NODE_TYPES = new Set(['interface_declaration', 'interface_definition', 'interface', 'trait_declaration', 'trait_definition', 'trait']);
         while (
             parent &&
-            !TreeSitterParser.CLASS_NODE_TYPES.has(parent.type) &&
+            !TreeSitterParser.CONTROLLER_CONTAINER_NODE_TYPES.has(parent.type) &&
             parent.type !== 'compilation_unit'
         ) {
             parent = this.getParent(parent);
         }

-        if (parent && TreeSitterParser.CLASS_NODE_TYPES.has(parent.type)) {
+        if (parent && TreeSitterParser.CONTROLLER_CONTAINER_NODE_TYPES.has(parent.type)) {
             const results: { method: string | null; route: string | null } = {
                 method: null,
                 route: null,
             };

Also applies to: 476-483

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@language-server/src/core/tree-sitter-parser.ts` around lines 42 - 48,
CLASS_NODE_TYPES currently includes struct entries which causes
getControllerRoutePrefix() to treat structs as controllers; create a narrower
set (e.g., CONTROLLER_CLASS_NODE_TYPES) containing only 'class_declaration',
'class_definition', 'class' and use that new set in getControllerRoutePrefix()
(and the other controller-ancestor checks referenced around the second
occurrence) instead of CLASS_NODE_TYPES so only actual classes are considered
controller containers.


private isInitialized: boolean = false;
private readonly languages: Map<string, unknown> = new Map();
private ParserClass: ParserConstructor | undefined = undefined;
Expand Down Expand Up @@ -465,13 +473,14 @@ export class TreeSitterParser {
let parent = this.getParent(methodNode);
while (
parent &&
!parent.type.toLowerCase().includes('class_declaration') &&
// ⚑ Bolt: Fast O(1) Set lookup replacing dynamic string manipulation
!TreeSitterParser.CLASS_NODE_TYPES.has(parent.type) &&
parent.type !== 'compilation_unit'
) {
parent = this.getParent(parent);
}

if (parent?.type.toLowerCase().includes('class_declaration')) {
if (parent && TreeSitterParser.CLASS_NODE_TYPES.has(parent.type)) {
const results: { method: string | null; route: string | null } = {
method: null,
route: null,
Expand Down Expand Up @@ -607,28 +616,23 @@ export class TreeSitterParser {
}

private getSearchItemType(nodeType: string, langId: string): SearchItemType | undefined {
// ⚑ Bolt: Fast O(1) Set lookups replacing RegExp checks
// Classes & Types
if (/class_declaration|class_definition|^class$/.test(nodeType)) {
if (TreeSitterParser.CLASS_NODE_TYPES.has(nodeType)) {
return SearchItemType.CLASS;
}
if (/interface_declaration|interface_definition|^interface$/.test(nodeType)) {
if (TreeSitterParser.INTERFACE_NODE_TYPES.has(nodeType)) {
return SearchItemType.INTERFACE;
}
if (/enum_declaration|enum_definition|^enum$/.test(nodeType)) {
if (TreeSitterParser.ENUM_NODE_TYPES.has(nodeType)) {
return SearchItemType.ENUM;
}
if (/struct_declaration|struct_definition|^struct$/.test(nodeType)) {
return SearchItemType.CLASS;
}
if (/trait_declaration|trait_definition|^trait$/.test(nodeType)) {
return SearchItemType.INTERFACE;
}

// Functions & Methods
if (/method_declaration|method_definition|^method$/.test(nodeType)) {
if (TreeSitterParser.METHOD_NODE_TYPES.has(nodeType)) {
return SearchItemType.METHOD;
}
if (/function_declaration|function_definition|^function$/.test(nodeType)) {
if (TreeSitterParser.FUNCTION_NODE_TYPES.has(nodeType)) {
return SearchItemType.FUNCTION;
}

Expand All @@ -652,10 +656,10 @@ export class TreeSitterParser {
}

// Fallback for common properties and variables
if (/property_declaration|property_definition/.test(nodeType)) {
if (TreeSitterParser.PROPERTY_NODE_TYPES.has(nodeType)) {
return SearchItemType.PROPERTY;
}
if (/variable_declaration|variable_declarator/.test(nodeType)) {
if (TreeSitterParser.VARIABLE_NODE_TYPES.has(nodeType)) {
return SearchItemType.VARIABLE;
}

Expand Down
Loading