Răsfoiți Sursa

perf: Speed up ref resolution for large Pascal codebases

Two optimizations that eliminate the O(n²) bottleneck:

1. Add Pascal built-in filtering to isBuiltInOrExternal() — skips
   resolution attempts for System.*, Vcl.*, Fmx.* and ~60 common
   RTL identifiers (WriteLn, Create, Free, TObject, etc.)

2. Cache getNodesByKind() results during warm cache phase — matchFuzzy()
   was calling the database 3 times per unresolved reference instead
   of using the in-memory cache that warmCaches() already builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Olaf Monien 4 luni în urmă
părinte
comite
f5d9a0efe5
1 a modificat fișierele cu 51 adăugiri și 0 ștergeri
  1. 51 0
      src/resolution/index.ts

+ 51 - 0
src/resolution/index.ts

@@ -38,6 +38,7 @@ export class ReferenceResolver {
   private fileCache: Map<string, string | null> = new Map();
   private nameCache: Map<string, Node[]> = new Map();
   private qualifiedNameCache: Map<string, Node[]> = new Map();
+  private kindCache: Map<string, Node[]> = new Map();
   private nodeByIdCache: Map<string, Node> = new Map();
   private cachesWarmed = false;
 
@@ -80,6 +81,14 @@ export class ReferenceResolver {
         this.qualifiedNameCache.set(node.qualifiedName, [node]);
       }
 
+      // Index by kind
+      const byKind = this.kindCache.get(node.kind);
+      if (byKind) {
+        byKind.push(node);
+      } else {
+        this.kindCache.set(node.kind, [node]);
+      }
+
       // Index by ID
       this.nodeByIdCache.set(node.id, node);
     }
@@ -95,6 +104,7 @@ export class ReferenceResolver {
     this.fileCache.clear();
     this.nameCache.clear();
     this.qualifiedNameCache.clear();
+    this.kindCache.clear();
     this.nodeByIdCache.clear();
     this.cachesWarmed = false;
   }
@@ -131,6 +141,9 @@ export class ReferenceResolver {
       },
 
       getNodesByKind: (kind: Node['kind']) => {
+        if (this.cachesWarmed) {
+          return this.kindCache.get(kind) ?? [];
+        }
         return this.queries.getNodesByKind(kind);
       },
 
@@ -363,6 +376,44 @@ export class ReferenceResolver {
       return true;
     }
 
+    // Pascal/Delphi built-ins and standard library units
+    if (ref.language === 'pascal') {
+      // Standard RTL/VCL/FMX unit prefixes — these are external dependencies
+      const pascalUnitPrefixes = [
+        'System.', 'Winapi.', 'Vcl.', 'Fmx.', 'Data.', 'Datasnap.',
+        'Soap.', 'Xml.', 'Web.', 'REST.', 'FireDAC.', 'IBX.',
+        'IdHTTP', 'IdTCP', 'IdSSL', 'Id',
+      ];
+      if (pascalUnitPrefixes.some((p) => name.startsWith(p))) {
+        return true;
+      }
+
+      // Common standalone RTL units and built-in identifiers
+      const pascalBuiltIns = [
+        'System', 'SysUtils', 'Classes', 'Types', 'Variants', 'StrUtils',
+        'Math', 'DateUtils', 'IOUtils', 'Generics.Collections', 'Generics.Defaults',
+        'Rtti', 'TypInfo', 'SyncObjs', 'RegularExpressions',
+        'SysInit', 'Windows', 'Messages', 'Graphics', 'Controls', 'Forms',
+        'Dialogs', 'StdCtrls', 'ExtCtrls', 'ComCtrls', 'Menus', 'ActnList',
+        'WriteLn', 'Write', 'ReadLn', 'Read', 'Inc', 'Dec', 'Ord', 'Chr',
+        'Length', 'SetLength', 'High', 'Low', 'Assigned', 'FreeAndNil',
+        'Format', 'IntToStr', 'StrToInt', 'FloatToStr', 'StrToFloat',
+        'Trim', 'UpperCase', 'LowerCase', 'Pos', 'Copy', 'Delete', 'Insert',
+        'Now', 'Date', 'Time', 'DateToStr', 'StrToDate',
+        'Raise', 'Exit', 'Break', 'Continue', 'Abort',
+        'True', 'False', 'nil', 'Self', 'Result',
+        'Create', 'Destroy', 'Free',
+        'TObject', 'TComponent', 'TPersistent', 'TInterfacedObject',
+        'TList', 'TStringList', 'TStrings', 'TStream', 'TMemoryStream', 'TFileStream',
+        'Exception', 'EAbort', 'EConvertError', 'EAccessViolation',
+        'IInterface', 'IUnknown',
+      ];
+
+      if (pascalBuiltIns.includes(name)) {
+        return true;
+      }
+    }
+
     return false;
   }