Explorar el Código

Delphi-Konzept: DFM/FMX Support hinzugefügt

- Neues Dokument 07-DFM-FMX-Support.md: Format-Referenz, Extraktions-Strategie, DfmExtractor-Konzept
- Neues Fixture MainForm.dfm: Verschachtelte Komponenten, Event-Handler (FormCreate, btnLoginClick, etc.)
- Integration Guide erweitert: DfmExtractor-Implementierung (analog LiquidExtractor), Coding-Style-Hinweise
- NodeKind-Mapping erweitert: DFM-Komponenten als component, Event-Handler als references, Verschachtelung als contains
- Checklist: DFM/FMX von 'Zukunft' auf konkreten Plan (Phase 1b) hochgestuft
- README aktualisiert: DFM/FMX-Support in Ziel, Inhalt und Grammar-Abschnitt aufgenommen
Olaf Monien hace 4 meses
padre
commit
8dfc99572f

+ 14 - 4
Delphi/Docs/04-Checklist.md

@@ -3,9 +3,9 @@
 ## A) Plumbing (Infrastruktur)
 - [ ] `npm install tree-sitter-pascal` als Dependency
 - [ ] `src/types.ts`: `'pascal'` zum `Language` Union-Type hinzufügen
-- [ ] `src/types.ts`: `DEFAULT_CONFIG.include` um `'**/*.pas'`, `'**/*.dpr'`, `'**/*.dpk'`, `'**/*.lpr'` erweitern
+- [ ] `src/types.ts`: `DEFAULT_CONFIG.include` um `'**/*.pas'`, `'**/*.dpr'`, `'**/*.dpk'`, `'**/*.lpr'`, `'**/*.dfm'`, `'**/*.fmx'` erweitern
 - [ ] `src/extraction/grammars.ts`: Grammar-Loader `pascal: () => require('tree-sitter-pascal')`
-- [ ] `src/extraction/grammars.ts`: Extension-Mapping `.pas`, `.dpr`, `.dpk`, `.lpr` → `'pascal'`
+- [ ] `src/extraction/grammars.ts`: Extension-Mapping `.pas`, `.dpr`, `.dpk`, `.lpr`, `.dfm`, `.fmx` → `'pascal'`
 - [ ] `src/extraction/grammars.ts`: Display-Name `pascal: 'Pascal / Delphi'`
 - [ ] `src/extraction/tree-sitter.ts`: `LanguageExtractor` für `pascal` in `EXTRACTORS` Map
 
@@ -53,9 +53,19 @@
 - [ ] Language in Supported-Languages-Dokumentation aufnehmen
 - [ ] CLI-Hilfe / Config-Validierung aktualisieren
 
-## H) Bekannte Einschränkungen (für spätere Phasen)
+## H) DFM/FMX Form-Dateien (Phase 1b)
+- [ ] `DfmExtractor`-Klasse in `src/extraction/tree-sitter.ts` implementieren (analog `LiquidExtractor`)
+- [ ] Routing in `extractFromSource()`: `.dfm`/`.fmx` → `DfmExtractor`
+- [ ] Komponenten als NodeKind `component` extrahieren (`object <Name>: <Typ>`)
+- [ ] Verschachtelung als EdgeKind `contains` abbilden
+- [ ] Event-Handler (`OnClick = MethodName`) als `UnresolvedReference` speichern → EdgeKind `references`
+- [ ] Mehrzeilige Properties korrekt überspringen
+- [ ] `inherited`-Blöcke als `component` behandeln
+- [ ] DFM-Fixture (`MainForm.dfm`) in Test-Suite aufnehmen
+- [ ] Resolution: Event-Handler-Namen zu Methoden in zugehöriger `.pas`-Datei auflösen
+
+## I) Bekannte Einschränkungen (für spätere Phasen)
 - [ ] **`with`-Statements** (Phase 2): Verschleiern den Qualifier bei Aufrufen. Im MVP werden Calls innerhalb von `with`-Blöcken ohne Qualifier extrahiert.
-- [ ] **`.dfm`/`.fmx` Form-Dateien** (Zukunft): Enthalten Komponenten-Deklarationen und Event-Verknüpfungen. Aktuell nicht unterstützt.
 - [ ] **`.inc` Include-Dateien** (Optional): Enthalten Code-Fragmente, die per `{$I filename}` eingebunden werden. Parser liefert möglicherweise keinen vollständigen AST.
 - [ ] **Class Helpers / Record Helpers** (Phase 2): `declHelper`-Knoten erweitern existierende Typen. Erfordert spezielle Resolution-Logik.
 - [ ] **Generics** (Phase 2): `typerefTpl` und `genericTpl` für generische Typen.

+ 20 - 1
Delphi/Docs/05-NodeKind-Mapping.md

@@ -43,13 +43,32 @@ Dieses Dokument zeigt die explizite Zuordnung von Delphi/Pascal-Konzepten zu den
 | Unit enthält Klasse | AST Parent-Child | `contains` |
 | Klasse enthält Methode | AST Parent-Child | `contains` |
 
+## DFM/FMX Node-Mapping
+
+DFM/FMX-Dateien werden durch einen eigenen `DfmExtractor` (Regex-basiert, analog `LiquidExtractor`) verarbeitet.
+
+| DFM-Konzept | Erkennung | CodeGraph `NodeKind` | Anmerkungen |
+|---|---|---|---|
+| Formular | `object Form1: TForm1` (Top-Level) | `component` | Root-Komponente der DFM-Datei |
+| UI-Komponente | `object Button1: TButton` (verschachtelt) | `component` | Jede Komponente wird ein Node |
+| Geerbte Komponente | `inherited Form1: TForm1` | `component` | Formular-Vererbung |
+
+## DFM/FMX Edge-Mapping
+
+| DFM-Beziehung | Erkennung | CodeGraph `EdgeKind` | Beschreibung |
+|---|---|---|---|
+| Verschachtelung | `object` innerhalb `object` | `contains` | Panel1 enthält Button1 |
+| Event-Handler | `OnClick = Button1Click` | `references` | Komponente → Methode in `.pas` |
+| Datei enthält Formular | Top-Level `object` | `contains` | DFM-Datei → Root-Komponente |
+
+> **Hinweis:** Event-Handler-Verknüpfungen werden als `UnresolvedReference` gespeichert und in der Resolution-Phase zur entsprechenden Methode in der zugehörigen `.pas`-Datei aufgelöst.
+
 ## Nicht abgebildete Konzepte (Phase 2+)
 
 | Konzept | Grund |
 |---|---|
 | `with`-Statement | Kein eigener EdgeKind nötig; erschwert nur die Call-Qualifizierung |
 | Class Helper | Kein eigener NodeKind; als `class` mit speziellem Bezug zum erweiterten Typ |
-| `.dfm`/`.fmx` Komponenten | Kein Code im Pascal-Sinne; erfordert eigenen Parser |
 | `exports`-Klauseln | `declExports` → könnte als `export` NodeKind abgebildet werden |
 | Generics | `typerefTpl` → beeinflusst Typ-Referenzen, nicht den NodeKind |
 

+ 216 - 1
Delphi/Docs/06-Integration-Guide.md

@@ -134,9 +134,224 @@ pascal: {
 },
 ```
 
-## Hinweise
+## Hinweise zur Pascal-Integration
 
 - `declProc` wird sowohl für `functionTypes` als auch `methodTypes` verwendet. CodeGraph unterscheidet anhand des AST-Kontexts (Parent ist `declClass` → method, sonst → function).
 - Die `getVisibility`-Funktion traversiert aufwärts zum `declSection`-Knoten.
 - Records werden als `class` behandelt, da `declClass` mit `kRecord` Kind-Knoten beides abdeckt.
 
+---
+
+## 5) DFM/FMX-Support: Custom Extractor
+
+DFM/FMX-Dateien verwenden ein einfaches zeilenbasiertes Textformat (kein tree-sitter Parser vorhanden). Die Integration erfolgt analog zu `LiquidExtractor` und `SvelteExtractor` als **Custom Extractor**.
+
+### Extension-Mapping ergänzen (`src/extraction/grammars.ts`)
+
+```typescript
+export const EXTENSION_MAP: Record<string, Language> = {
+  // ...bestehende Mappings...
+  '.pas': 'pascal',
+  '.dpr': 'pascal',
+  '.dpk': 'pascal',
+  '.lpr': 'pascal',
+  '.dfm': 'pascal',                      // ← NEU: DFM-Formulare
+  '.fmx': 'pascal',                      // ← NEU: FMX-Formulare
+};
+```
+
+> **Hinweis:** DFM/FMX werden als Sprache `pascal` registriert. Die Unterscheidung zwischen tree-sitter-Parsing (`.pas`) und Custom Extractor (`.dfm`/`.fmx`) erfolgt in `extractFromSource()` anhand der Dateiendung.
+
+### DEFAULT_CONFIG.include ergänzen (`src/types.ts`)
+
+```typescript
+include: [
+  // ...bestehende Patterns...
+  '**/*.pas',
+  '**/*.dpr',
+  '**/*.dpk',
+  '**/*.lpr',
+  '**/*.dfm',                             // ← NEU
+  '**/*.fmx',                             // ← NEU
+],
+```
+
+### Routing in `extractFromSource()` (`src/extraction/tree-sitter.ts`)
+
+```typescript
+export function extractFromSource(
+  filePath: string,
+  source: string,
+  language?: Language
+): ExtractionResult {
+  const detectedLanguage = language || detectLanguage(filePath);
+
+  // Use custom extractor for Svelte
+  if (detectedLanguage === 'svelte') {
+    const extractor = new SvelteExtractor(filePath, source);
+    return extractor.extract();
+  }
+
+  // Use custom extractor for Liquid
+  if (detectedLanguage === 'liquid') {
+    const extractor = new LiquidExtractor(filePath, source);
+    return extractor.extract();
+  }
+
+  // Use custom extractor for DFM/FMX form files          ← NEU
+  if (detectedLanguage === 'pascal' &&
+      (filePath.endsWith('.dfm') || filePath.endsWith('.fmx'))) {
+    const extractor = new DfmExtractor(filePath, source);
+    return extractor.extract();
+  }
+
+  const extractor = new TreeSitterExtractor(filePath, source, detectedLanguage);
+  return extractor.extract();
+}
+```
+
+### DfmExtractor Klasse (Coding Style analog LiquidExtractor)
+
+```typescript
+/**
+ * Custom extractor for Delphi DFM/FMX form files.
+ *
+ * DFM/FMX files describe the visual component hierarchy and event handler
+ * bindings. They use a simple text format (object/end blocks) that we parse
+ * with regex — no tree-sitter grammar exists for this format.
+ *
+ * Extracted information:
+ * - Components as NodeKind `component`
+ * - Nesting as EdgeKind `contains`
+ * - Event handlers (OnClick = MethodName) as UnresolvedReference → EdgeKind `references`
+ */
+export class DfmExtractor {
+  private filePath: string;
+  private source: string;
+  private nodes: Node[] = [];
+  private edges: Edge[] = [];
+  private unresolvedReferences: UnresolvedReference[] = [];
+  private errors: ExtractionError[] = [];
+
+  constructor(filePath: string, source: string) {
+    this.filePath = filePath;
+    this.source = source;
+  }
+
+  /**
+   * Extract components and event handler references from DFM/FMX source
+   */
+  extract(): ExtractionResult {
+    const startTime = Date.now();
+
+    try {
+      const fileNode = this.createFileNode();
+      this.parseComponents(fileNode.id);
+    } catch (error) {
+      captureException(error, { operation: 'dfm-extraction', filePath: this.filePath });
+      this.errors.push({
+        message: `DFM extraction error: ${error instanceof Error ? error.message : String(error)}`,
+        severity: 'error',
+      });
+    }
+
+    return {
+      nodes: this.nodes,
+      edges: this.edges,
+      unresolvedReferences: this.unresolvedReferences,
+      errors: this.errors,
+      durationMs: Date.now() - startTime,
+    };
+  }
+
+  /** Create a file node for the DFM form file */
+  private createFileNode(): Node { /* ... analog LiquidExtractor ... */ }
+
+  /** Parse object/end blocks and extract components + event handlers */
+  private parseComponents(fileNodeId: string): void {
+    const lines = this.source.split('\n');
+    const stack: string[] = [fileNodeId]; // Stack der Parent-Node-IDs
+
+    const objectPattern = /^\s*(object|inherited|inline)\s+(\w+)\s*:\s*(\w+)/;
+    const eventPattern = /^\s*(On\w+)\s*=\s*(\w+)/;
+    const endPattern = /^\s*end\s*$/;
+    const multiLineStart = /=\s*\(\s*$/;
+    let inMultiLine = false;
+
+    for (let i = 0; i < lines.length; i++) {
+      const line = lines[i];
+      const lineNum = i + 1;
+
+      // Mehrzeilige Properties überspringen
+      if (inMultiLine) {
+        if (line.trimEnd().endsWith(')')) inMultiLine = false;
+        continue;
+      }
+      if (multiLineStart.test(line)) {
+        inMultiLine = true;
+        continue;
+      }
+
+      // Component-Deklaration
+      const objMatch = line.match(objectPattern);
+      if (objMatch) {
+        const [, , name, typeName] = objMatch;
+        const nodeId = generateNodeId(this.filePath, 'component', name, lineNum);
+        this.nodes.push({
+          id: nodeId,
+          kind: 'component',
+          name,
+          qualifiedName: `${this.filePath}#${name}`,
+          filePath: this.filePath,
+          language: 'pascal',
+          startLine: lineNum,
+          endLine: lineNum, // wird beim zugehörigen 'end' aktualisiert
+          startColumn: 0,
+          endColumn: line.length,
+          metadata: { componentType: typeName },
+          updatedAt: Date.now(),
+        });
+        this.edges.push({
+          source: stack[stack.length - 1],
+          target: nodeId,
+          kind: 'contains',
+        });
+        stack.push(nodeId);
+        continue;
+      }
+
+      // Event-Handler
+      const eventMatch = line.match(eventPattern);
+      if (eventMatch) {
+        const [, , methodName] = eventMatch;
+        this.unresolvedReferences.push({
+          sourceNodeId: stack[stack.length - 1],
+          targetName: methodName,
+          kind: 'references',
+          filePath: this.filePath,
+          line: lineNum,
+        });
+        continue;
+      }
+
+      // Block-Ende
+      if (endPattern.test(line)) {
+        if (stack.length > 1) stack.pop();
+      }
+    }
+  }
+}
+```
+
+### Coding-Style-Hinweise
+
+Der `DfmExtractor` folgt dem bestehenden Coding Style der Custom Extractors:
+
+- **JSDoc-Kommentare** (`/** */`) für jede öffentliche Methode und die Klasse selbst
+- **Private Felder** für `nodes`, `edges`, `unresolvedReferences`, `errors`
+- **`generateNodeId()`** für deterministische Node-IDs
+- **`captureException()`** für Error-Tracking in catch-Blöcken
+- **`ExtractionResult`** als Return-Type von `extract()`
+- **`UnresolvedReference`** für Event-Handler (werden in der Resolution-Phase aufgelöst)
+- **Kein tree-sitter** — rein Regex/zeilenbasiertes Parsing
+

+ 149 - 0
Delphi/Docs/07-DFM-FMX-Support.md

@@ -0,0 +1,149 @@
+# DFM/FMX Form-Dateien: Format-Referenz & Extraktions-Konzept
+
+Delphi-Projekte bestehen nicht nur aus `.pas`-Dateien, sondern auch aus **Form-Dateien** (`.dfm` für VCL, `.fmx` für FireMonkey). Diese enthalten die visuelle Komponentenhierarchie und — besonders wertvoll für CodeGraph — die **Event-Handler-Verknüpfungen** zwischen UI-Komponenten und Pascal-Methoden.
+
+## Warum DFM/FMX für CodeGraph relevant sind
+
+| Information | Wert für CodeGraph |
+|---|---|
+| Komponenten-Deklarationen (`object Button1: TButton`) | NodeKind `component` — zeigt welche UI-Elemente existieren |
+| Event-Handler (`OnClick = Button1Click`) | EdgeKind `references` — verknüpft UI mit Code in `.pas` |
+| Komponenten-Hierarchie (verschachtelte `object`-Blöcke) | EdgeKind `contains` — zeigt Parent-Child-Beziehungen |
+| Komponenten-Typ (`TButton`, `TPanel`, `TEdit`) | Metadata — Typ-Information für Impact-Analyse |
+
+**Besonders wertvoll:** Wenn ein Entwickler eine Methode in der `.pas`-Datei umbenennt, zeigt CodeGraph sofort, dass ein DFM-Event-Handler darauf verweist → Impact-Analyse funktioniert über Dateigrenzen hinweg.
+
+## DFM-Textformat
+
+DFM-Dateien können in **Text** oder **Binär** gespeichert werden. Moderne Delphi-Projekte verwenden fast ausschließlich das Textformat (besser für Versionskontrolle). FMX-Dateien verwenden das gleiche Textformat.
+
+### Grundstruktur
+
+```
+object Form1: TForm1
+  Left = 0
+  Top = 0
+  Caption = 'Hauptformular'
+  ClientHeight = 400
+  ClientWidth = 600
+  OnCreate = FormCreate
+  OnDestroy = FormDestroy
+  object Panel1: TPanel
+    Left = 0
+    Top = 0
+    Width = 600
+    Height = 50
+    Align = alTop
+    object Label1: TLabel
+      Left = 16
+      Top = 16
+      Caption = 'Willkommen'
+    end
+    object Button1: TButton
+      Left = 500
+      Top = 12
+      Caption = 'Login'
+      OnClick = Button1Click
+    end
+  end
+  object Memo1: TMemo
+    Left = 0
+    Top = 50
+    Width = 600
+    Height = 350
+    Align = alClient
+  end
+end
+```
+
+### Syntax-Regeln
+
+1. **Top-Level:** `object <Name>: <Typ>` — das Formular selbst
+2. **Verschachtelte Komponenten:** `object <Name>: <Typ>` innerhalb eines anderen `object`-Blocks
+3. **Properties:** `<Key> = <Value>` — einfache Zuweisung
+4. **Event-Handler:** `On<Event> = <MethodenName>` — Verknüpfung zu Pascal-Methode
+5. **Ende:** `end` schließt jeden `object`-Block
+6. **Vererbung:** `inherited <Name>: <Typ>` statt `object` bei geerbten Formularen
+7. **Inline-Objekte:** `inline <Name>: <Typ>` für inline erstellte Objekte
+
+### Event-Handler erkennen
+
+Event-Handler sind Properties, deren **Key mit `On` beginnt** und deren **Value ein Bezeichner** (kein String, keine Zahl) ist:
+
+```
+OnClick = Button1Click          ← Event-Handler → references Button1Click
+OnChange = EditChanged          ← Event-Handler → references EditChanged
+Caption = 'Nicht ein Event'     ← Normales Property (String-Wert)
+Left = 100                      ← Normales Property (Zahl-Wert)
+Align = alTop                   ← Normales Property (Enum-Wert)
+```
+
+### Mehrzeilige Properties
+
+Einige Properties erstrecken sich über mehrere Zeilen:
+
+```
+  SQL.Strings = (
+    'SELECT * FROM users'
+    'WHERE active = 1'
+    'ORDER BY name')
+  Items.Strings = (
+    'Option A'
+    'Option B')
+```
+
+Diese sind für CodeGraph weniger relevant, müssen aber beim Parsen korrekt übersprungen werden.
+
+## Extraktions-Strategie: DfmExtractor
+
+Analog zum `LiquidExtractor` und `SvelteExtractor` wird ein **`DfmExtractor`** als Custom Extractor implementiert — **ohne tree-sitter**, rein Regex/Zeilen-basiert.
+
+### Zu extrahierende Nodes
+
+| DFM-Element | CodeGraph NodeKind | Beispiel |
+|---|---|---|
+| Top-Level `object` | `component` | `Form1: TForm1` |
+| Verschachtelte `object` | `component` | `Button1: TButton` |
+| `inherited` | `component` | Geerbte Komponente |
+
+### Zu extrahierende Edges
+
+| DFM-Beziehung | CodeGraph EdgeKind | Beschreibung |
+|---|---|---|
+| Verschachtelung | `contains` | Panel1 enthält Button1 |
+| Event-Handler | `references` | Button1 → `Button1Click` (Methode in .pas) |
+| Datei enthält Komponente | `contains` | DFM-Datei → Form1 |
+
+### Parsing-Algorithmus (Pseudocode)
+
+```
+für jede Zeile:
+  wenn "object <Name>: <Typ>" oder "inherited <Name>: <Typ>":
+    → neuen component-Node erstellen
+    → contains-Edge vom Parent
+    → auf Stack pushen
+  wenn "end":
+    → Stack poppen
+  wenn "<Key> = <Value>" und Key beginnt mit "On" und Value ist Bezeichner:
+    → references-Edge: aktuelle Komponente → Value (Methodenname)
+    → als UnresolvedReference speichern (wird später zur .pas-Methode aufgelöst)
+  wenn mehrzeiliges Property (endet mit "("):
+    → Zeilen überspringen bis ")"
+```
+
+### Verknüpfung DFM ↔ PAS
+
+Die Zuordnung DFM → PAS erfolgt über den **Dateinamen**:
+- `MainForm.dfm` gehört zu `MainForm.pas` (gleicher Basename)
+- Event-Handler `Button1Click` → Methode `TForm1.Button1Click` in der zugehörigen `.pas`-Datei
+
+Diese Verknüpfung erfolgt in der **Resolution-Phase**, nicht beim Parsen.
+
+## Kein tree-sitter nötig
+
+Es gibt keinen tree-sitter Parser für DFM/FMX. Das Format ist aber so einfach strukturiert (zeilenbasiert, keine komplexe Grammatik), dass ein Regex-basierter Parser vollkommen ausreicht — genau wie bei Liquid-Templates.
+
+## Phase
+
+DFM/FMX-Support wird als **Phase 1b** eingeplant — parallel zum Pascal-Support, da die Verknüpfung DFM ↔ PAS den größten Mehrwert liefert, wenn beides gleichzeitig verfügbar ist.
+

+ 8 - 7
Delphi/README.md

@@ -2,7 +2,7 @@
 
 Dieses Verzeichnis enthält einen **umsetzbaren Bauplan** für Pascal/Delphi-Support in CodeGraph. Es basiert auf der tatsächlichen CodeGraph-Architektur und den verifizierten AST-Knotentypen von [`tree-sitter-pascal`](https://github.com/Isopod/tree-sitter-pascal).
 
-**Ziel:** CodeGraph soll **Pascal/Delphi**-Dateien indexieren und daraus **Nodes** (Units, Klassen, Methoden, Properties, Enums, …) sowie **Edges** (uses/imports, calls, extends, implements, …) extrahieren.
+**Ziel:** CodeGraph soll **Pascal/Delphi**-Dateien indexieren und daraus **Nodes** (Units, Klassen, Methoden, Properties, Enums, …) sowie **Edges** (uses/imports, calls, extends, implements, …) extrahieren. Zusätzlich werden **DFM/FMX-Formulardateien** unterstützt, um UI-Komponenten und Event-Handler-Verknüpfungen zu erfassen.
 
 ## Inhalt
 
@@ -13,8 +13,9 @@ Dieses Verzeichnis enthält einen **umsetzbaren Bauplan** für Pascal/Delphi-Sup
 | `Docs/03-AST-Referenz.md` | Verifizierte AST-Knotentypen aus tree-sitter-pascal |
 | `Docs/04-Checklist.md` | Umsetzungs-Checkliste mit bekannten Einschränkungen |
 | `Docs/05-NodeKind-Mapping.md` | Explizite Zuordnung Delphi → CodeGraph NodeKind/EdgeKind |
-| `Docs/06-Integration-Guide.md` | Konkrete Code-Diffs für `grammars.ts`, `types.ts`, `tree-sitter.ts` |
-| `fixtures/` | Delphi-Beispieldateien zum Testen der Extraktion |
+| `Docs/06-Integration-Guide.md` | Konkrete Code-Diffs für `grammars.ts`, `types.ts`, `tree-sitter.ts` + DfmExtractor |
+| `Docs/07-DFM-FMX-Support.md` | DFM/FMX Form-Dateien: Format-Referenz & Extraktions-Konzept |
+| `fixtures/` | Delphi-Beispieldateien zum Testen (`.pas`, `.dpr`, `.dfm`) |
 | `resolution/` | Resolver-Heuristiken (Unit-Mapping, Call-Resolution) |
 
 ## Architektur-Übersicht
@@ -33,8 +34,8 @@ CodeGraph verwendet **keine** per-Sprache Plugin-Verzeichnisse. Die Integration
 4. **Resolver erweitern** (mindestens `uses` → Unit-File, einfache Call-Auflösung)
 5. **Fixtures in die Tests aufnehmen** und Assertions hinzufügen
 
-## Grammar
+## Grammar & Parsing
 
-Verwendet wird: [`tree-sitter-pascal`](https://github.com/Isopod/tree-sitter-pascal) (npm: `tree-sitter-pascal`)
-- Unterstützt: Delphi, FreePascal, Standard-Pascal
-- Dateierweiterungen: `.pas`, `.dpr`, `.dpk`, `.lpr`
+**Pascal-Dateien** (`.pas`, `.dpr`, `.dpk`, `.lpr`) werden mit [`tree-sitter-pascal`](https://github.com/Isopod/tree-sitter-pascal) geparst (AST-basierte Extraktion via `LanguageExtractor`).
+
+**DFM/FMX-Dateien** (`.dfm`, `.fmx`) werden mit einem **Custom Extractor** (`DfmExtractor`) verarbeitet — analog zu `LiquidExtractor` und `SvelteExtractor`. Das DFM-Textformat ist zeilenbasiert und wird per Regex geparst. Besonders wertvoll: Event-Handler wie `OnClick = Button1Click` erzeugen `references`-Edges zu den zugehörigen Methoden in der `.pas`-Datei.

+ 92 - 0
Delphi/fixtures/MainForm.dfm

@@ -0,0 +1,92 @@
+object frmMain: TfrmMain
+  Left = 0
+  Top = 0
+  Caption = 'CodeGraph DFM Fixture'
+  ClientHeight = 480
+  ClientWidth = 640
+  Color = clBtnFace
+  Font.Charset = DEFAULT_CHARSET
+  Font.Color = clWindowText
+  Font.Height = -12
+  Font.Name = 'Segoe UI'
+  OnCreate = FormCreate
+  OnDestroy = FormDestroy
+  PixelsPerInch = 96
+  TextHeight = 15
+  object pnlTop: TPanel
+    Left = 0
+    Top = 0
+    Width = 640
+    Height = 50
+    Align = alTop
+    BevelOuter = bvNone
+    TabOrder = 0
+    object lblTitle: TLabel
+      Left = 16
+      Top = 16
+      Width = 200
+      Height = 15
+      Caption = 'Authentication Service'
+    end
+    object btnLogin: TButton
+      Left = 540
+      Top = 12
+      Width = 80
+      Height = 25
+      Caption = 'Login'
+      TabOrder = 0
+      OnClick = btnLoginClick
+    end
+  end
+  object pnlContent: TPanel
+    Left = 0
+    Top = 50
+    Width = 640
+    Height = 390
+    Align = alClient
+    BevelOuter = bvNone
+    TabOrder = 1
+    object edtUsername: TEdit
+      Left = 16
+      Top = 16
+      Width = 300
+      Height = 23
+      TabOrder = 0
+      TextHint = 'Username'
+      OnChange = edtUsernameChange
+    end
+    object edtPassword: TEdit
+      Left = 16
+      Top = 48
+      Width = 300
+      Height = 23
+      PasswordChar = '*'
+      TabOrder = 1
+      TextHint = 'Password'
+      OnKeyPress = edtPasswordKeyPress
+    end
+    object mmoLog: TMemo
+      Left = 16
+      Top = 88
+      Width = 608
+      Height = 280
+      ReadOnly = True
+      ScrollBars = ssVertical
+      TabOrder = 2
+    end
+  end
+  object pnlStatus: TStatusBar
+    Left = 0
+    Top = 440
+    Width = 640
+    Height = 40
+    Panels = <
+      item
+        Width = 200
+      end
+      item
+        Width = 200
+      end>
+  end
+end
+