docs: Replace planning docs with DELPHI-SUPPORT.md for PR

Remove Delphi/ planning folder (concept docs, fixtures, resolution notes)
that served as implementation guidance. Add DELPHI-SUPPORT.md describing
the integration for stakeholder review: motivation, feature matrix,
architecture decisions, and test coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Olaf Monien
2026-02-11 10:04:55 +01:00
co-authored by Claude Opus 4.6
parent 2442c58932
commit 9f415002e4
14 changed files with 79 additions and 1370 deletions
+79
View File
@@ -0,0 +1,79 @@
# Pascal / Delphi Support for CodeGraph
## Why Delphi?
Delphi (Object Pascal) remains one of the most widely used languages for Windows desktop and enterprise applications. With an estimated **1.53 million active developers** and a strong presence in industries like healthcare, finance, logistics, and government, Delphi projects often involve large, long-lived codebases that benefit significantly from semantic code intelligence.
Many Delphi codebases have grown over decades — making structural understanding, impact analysis, and cross-file navigation exactly the kind of tooling gap CodeGraph is designed to fill.
Adding Delphi support positions CodeGraph as a uniquely valuable tool for a community that has historically been underserved by modern static analysis and AI-assisted development tools.
## What Was Implemented
### Pascal / Object Pascal (tree-sitter)
Full extraction support for `.pas`, `.dpr`, `.dpk`, and `.lpr` files using the `tree-sitter-pascal` grammar:
| Feature | NodeKind | Details |
|---------|----------|---------|
| Units / Programs | `module` | `unit`, `program`, `package`, `library` |
| Classes | `class` | Including inheritance and interface implementation |
| Records | `class` | Treated as classes (consistent with AST structure) |
| Interfaces | `interface` | With GUID support |
| Methods | `method` | Constructor, destructor, procedures, functions |
| Functions / Procedures | `function` | Top-level (non-class) routines |
| Properties | `property` | With read/write accessors |
| Fields | `field` | Class and record fields |
| Constants | `constant` | `const` declarations |
| Enums | `enum` | With enum members |
| Type Aliases | `type_alias` | `type TFoo = ...` |
| Uses / Imports | `import` | `uses` clause extraction |
| Function Calls | — | `calls` edges for call graph |
| Visibility | — | `public`, `private`, `protected` on methods/fields |
| Static Methods | — | `class function` / `class procedure` |
| Containment | — | `contains` edges (class → method, unit → type, etc.) |
| Inheritance | — | `extends` / `implements` edges |
### DFM / FMX Form Files (custom extractor)
Support for Delphi form files (`.dfm` for VCL, `.fmx` for FireMonkey) using a regex-based custom extractor — no tree-sitter grammar exists for this format:
| Feature | NodeKind / EdgeKind | Details |
|---------|---------------------|---------|
| Components | `component` | `object Button1: TButton` |
| Nested hierarchy | `contains` | Panel1 → Button1 |
| Event handlers | `references` (unresolved) | `OnClick = Button1Click` → links UI to Pascal methods |
| `inherited` keyword | `component` | Inherited form components |
| Multi-line properties | — | Correctly skipped during parsing |
| Item collections | — | `<item>...</end>` blocks correctly handled |
The DFM ↔ PAS linkage via event handlers enables **cross-file impact analysis**: renaming a method in `.pas` immediately reveals which UI components reference it.
## Architecture
The implementation follows CodeGraph's established patterns:
- **Pascal extraction** uses the standard `TreeSitterExtractor` with a Pascal-specific `LanguageExtractor` configuration and a `visitPascalNode()` hook for AST nodes that require special handling (e.g., `declType` wrappers, `defProc` implementation bodies)
- **DFM/FMX extraction** uses a `DfmExtractor` class — analogous to `LiquidExtractor` and `SvelteExtractor` — that parses the line-based format with regex
- **Routing** in `extractFromSource()` dispatches `.dfm`/`.fmx` files to `DfmExtractor` before reaching the tree-sitter path
- **`tree-sitter-pascal`** is declared as an `optionalDependency` (consistent with all other grammars), pinned to a specific commit for reproducible builds
## Files Changed
| File | Change |
|------|--------|
| `src/types.ts` | Added `'pascal'` to `Language` type, file patterns to `DEFAULT_CONFIG.include` |
| `src/extraction/grammars.ts` | Grammar loader, extension mappings (`.pas`, `.dpr`, `.dpk`, `.lpr`, `.dfm`, `.fmx`), display name |
| `src/extraction/tree-sitter.ts` | Pascal `LanguageExtractor`, `visitPascalNode()` with 7 helper methods, `DfmExtractor` class, routing in `extractFromSource()` |
| `package.json` | `tree-sitter-pascal` in `optionalDependencies` (pinned commit) |
| `__tests__/extraction.test.ts` | 36 new tests covering all Pascal and DFM extraction features |
## Test Results
- **36 new tests**, all passing
- **0 regressions** — the same 28 pre-existing failures (unrelated: missing Swift/Dart grammars, database path issues, MCP truncation test) are unchanged
- Tests cover: language detection, modules, imports, classes, records, interfaces, methods, visibility, static methods, enums, properties, constants, type aliases, calls, containment, full fixture files (UAuth.pas, UTypes.pas, MainForm.dfm)
## Dependency Note
The npm package `tree-sitter-pascal@0.0.1` is outdated (uses NAN bindings, incompatible with Node.js v24+). The implementation uses the actively maintained GitHub repository ([Isopod/tree-sitter-pascal](https://github.com/Isopod/tree-sitter-pascal), v0.10.2) with a pinned commit hash for deterministic builds. This is consistent with how `@sengac/tree-sitter-dart` handles a similar situation.
-118
View File
@@ -1,118 +0,0 @@
# Implementation Plan: Pascal/Delphi in CodeGraph
## 0) Prämissen
- CodeGraph extrahiert **Nodes** und **Edges** über **tree-sitter** und eine `LanguageExtractor`-Konfiguration pro Sprache in `src/extraction/tree-sitter.ts`.
- Sprachen werden zentral in drei Dateien registriert: `grammars.ts`, `types.ts`, und `tree-sitter.ts`.
- Danach folgt **Reference Resolution** (Calls → Definitionen, imports → Dateien, inheritance, framework patterns).
- **Grammar:** [`tree-sitter-pascal`](https://github.com/Isopod/tree-sitter-pascal) (unterstützt Delphi, FreePascal und Standard-Pascal).
## 1) Parser / Grammar
npm-Paket: `tree-sitter-pascal`
**Konkrete AST-Knoten (Auswahl):**
| tree-sitter-pascal Typ | Beschreibung |
|---|---|
| `unit`, `program`, `library` | Top-Level-Module |
| `moduleName` | Name des Moduls (Kind-Knoten von `unit`/`program`/`library`) |
| `declClass` | Klassen, Records, Objects (enthält `kClass`/`kRecord`/`kObject`) |
| `declIntf` | Interfaces (`kInterface`/`kDispInterface`) |
| `declProc` | Procedures, Functions, Methods, Constructors, Destructors |
| `declProp` | Properties |
| `declField` | Felder in Klassen/Records |
| `declEnum` | Aufzählungstypen |
| `declEnumValue` | Einzelne Enum-Werte |
| `declType` | Type-Deklarationen (enthält `declClass`/`declIntf`/`declEnum`/etc.) |
| `declUses` | `uses`-Klauseln (imports) |
| `declConst` | Konstanten-Deklarationen |
| `declSection` | Sichtbarkeitsbereiche (`kPrivate`/`kPublic`/`kProtected`/`kPublished`) |
| `declHelper` | Class/Record Helpers |
| `declExports` | exports-Klauseln |
| `defProc` | Implementations-Body einer Prozedur/Funktion |
| `exprCall` | Funktionsaufrufe |
| `exprDot` | Qualifizierte Zugriffe (`Obj.Method`) |
| `declArgs` | Parameterliste |
| `declArg` | Einzelner Parameter |
**Tasks:**
- `tree-sitter-pascal` als npm-Dependency hinzufügen
- Grammar-Loader in `src/extraction/grammars.ts` registrieren: `pascal: () => require('tree-sitter-pascal')`
- Extension-Mapping und Language-Typ anlegen (siehe Integration Guide)
## 2) File-Erkennung
Extensions (MVP):
- `.pas` (Units)
- `.dpr` (Delphi Program)
- `.dpk` (Delphi Package)
- `.lpr` (Lazarus Program)
Optional (Phase 2):
- `.inc` (Include-Fragmente) erst später, da Parser möglicherweise keinen vollständigen AST liefert.
## 3) Extraktion (MVP)
### 3.1 Nodes
| Delphi-Konzept | tree-sitter Typ | → CodeGraph `NodeKind` |
|---|---|---|
| Unit / Program / Library | `unit` / `program` / `library` | `module` |
| Klasse / Record / Object | `declClass` | `class` |
| Interface | `declIntf` | `interface` |
| Procedure / Function (top-level) | `declProc` (mit `kProcedure`/`kFunction`) | `function` |
| Methode / Constructor / Destructor | `declProc` (mit `kConstructor`/`kDestructor`, oder innerhalb `declClass`) | `method` |
| Property | `declProp` | `property` |
| Feld | `declField` | `field` |
| Enum | `declEnum` | `enum` |
| Enum-Wert | `declEnumValue` | `enum_member` |
| Type-Alias | `declType` (einfache Alias-Formen) | `type_alias` |
| Konstante | `declConst` | `constant` |
| Uses-Klausel | `declUses` | `import` |
### 3.2 Edges
| Beziehung | Erkennung | → CodeGraph `EdgeKind` |
|---|---|---|
| `uses X, Y;` | `declUses` → Kind-Knoten `identifier` | `imports` |
| Klassen-Vererbung | `declClass``parent` Feld → erster `typeref` | `extends` |
| Interface-Implementierung | `declClass``parent` Feld → weitere `typeref`s | `implements` |
| Funktionsaufruf | `exprCall` | `calls` |
| Qualifizierter Aufruf | `exprDot` als Elternteil von `exprCall` | `calls` |
| Enthaltensein | Parent-Child im AST (z.B. `declClass``declProc`) | `contains` |
| Instanziierung | `exprCall` mit `TClassName.Create` | `instantiates` |
## 4) Reference Resolution (MVP)
### 4.1 Unit-Auflösung (`uses`)
- Index-Phase: Map `unitName → fileId` (aus `moduleName` Knoten in `unit`/`program`/`library`)
- Resolution: Bei `uses Foo, Bar;` Edges auf Ziel-Units setzen.
- Spezialfälle (Phase 2): `Foo in 'path\Foo.pas'` (DPR/DPK), Namespaces (`System.SysUtils`).
### 4.2 Call-Auflösung (best effort)
- Extraktion: Name aus `exprCall`, Qualifier aus `exprDot`
- Heuristik-Reihenfolge:
1. lokale Procs/Funcs in der gleichen Unit
2. Methoden in der gleichen Klasse
3. Units aus `uses`
4. globaler Fallback nach Name
## 5) Tests
- Fixtures aus `fixtures/` in die Test-Suite aufnehmen
- Assertions:
- Node-Anzahl + zentrale Namen prüfen
- `uses`-Edges (imports)
- `extends`/`implements`-Edges
- einfache Call-Edges
- Sichtbarkeiten (public/private/protected)
- Properties, Felder, Enums
## 6) Definition of Done
- `codegraph index` indexiert Delphi/Pascal-Dateien ohne Crash
- `codegraph_search` findet Klassen/Methoden/Properties
- `codegraph_callers/callees` liefert sinnvolle Ergebnisse für einfache Fälle
- `uses`-Graph stimmt (Unit-Abhängigkeiten)
- Sichtbarkeiten werden korrekt extrahiert
-97
View File
@@ -1,97 +0,0 @@
# Capture Convention: Wie Pascal/Delphi an CodeGraph andockt
## Architektur-Übersicht
CodeGraph verwendet **keine** `.scm`-Query-Dateien für die Extraktion. Stattdessen wird die Logik über ein `LanguageExtractor`-Objekt in `src/extraction/tree-sitter.ts` definiert. Dieses Objekt mappt tree-sitter AST-Knotentypen auf die interne Verarbeitungslogik.
### Zentrale Dateien für die Integration
| Datei | Änderung |
|---|---|
| `src/extraction/grammars.ts` | Grammar-Loader, Extension-Mapping, Display-Name |
| `src/types.ts` | `Language`-Union-Type, `DEFAULT_CONFIG.include` Patterns |
| `src/extraction/tree-sitter.ts` | `EXTRACTORS`-Map mit `LanguageExtractor`-Konfiguration |
## LanguageExtractor-Interface
Die Konfiguration definiert, welche AST-Knotentypen welcher Semantik entsprechen:
```typescript
interface LanguageExtractor {
functionTypes: string[]; // Top-Level Funktionen/Prozeduren
classTypes: string[]; // Klassen, Records, Objects
methodTypes: string[]; // Methoden, Konstruktoren, Destruktoren
interfaceTypes: string[]; // Interfaces
structTypes: string[]; // Structs (in Delphi: leer)
enumTypes: string[]; // Enums
typeAliasTypes: string[]; // Type-Aliase
importTypes: string[]; // uses-Klauseln
callTypes: string[]; // Funktionsaufrufe
variableTypes: string[]; // Variable/Feld-Deklarationen
nameField: string; // AST-Feld für den Namen
bodyField: string; // AST-Feld für den Body
paramsField: string; // AST-Feld für Parameter
returnField?: string; // AST-Feld für Rückgabetyp
getSignature?: (node, source) => string | undefined;
getVisibility?: (node) => 'public' | 'private' | 'protected' | 'internal' | undefined;
isExported?: (node, source) => boolean;
isStatic?: (node) => boolean;
isConst?: (node) => boolean;
}
```
## Konkrete Zuordnung für Pascal/Delphi
| LanguageExtractor-Feld | tree-sitter-pascal Typ(en) | Anmerkung |
|---|---|---|
| `functionTypes` | `['declProc']` | Top-Level, gefiltert nach Kontext (kein Parent `declClass`) |
| `classTypes` | `['declClass']` | Enthält class, record, object |
| `methodTypes` | `['declProc']` | Innerhalb `declClass`, inkl. constructor/destructor |
| `interfaceTypes` | `['declIntf']` | interface, dispinterface |
| `structTypes` | `[]` | Records werden über `declClass` (mit `kRecord`) abgedeckt |
| `enumTypes` | `['declEnum']` | Innerhalb von `declType` |
| `typeAliasTypes` | `['declType']` | Einfache `type X = Y` Deklarationen |
| `importTypes` | `['declUses']` | `uses`-Klauseln |
| `callTypes` | `['exprCall']` | Funktions-/Methodenaufrufe |
| `variableTypes` | `['declField', 'declConst']` | Felder und Konstanten |
| `nameField` | `'name'` | `declProc`, `declConst`, `declField`, `declEnumValue` haben `name`-Feld |
| `bodyField` | `'body'` | Nur bei `defProc` (Implementierungen) |
| `paramsField` | `'args'` | `declProc` hat `args`-Feld (`declArgs`) |
| `returnField` | `'type'` | `declProc` hat optionales `type`-Feld |
## Delphi-spezifische Herausforderungen
### declProc: Funktion oder Methode?
`declProc` wird sowohl für Top-Level-Funktionen als auch für Methoden verwendet. Die Unterscheidung erfolgt über den Kontext:
- **Top-Level**: Parent ist `interface` (der Unit-interface-Abschnitt) oder `implementation`
- **Methode**: Parent ist `declClass` oder `declIntf`
- **Kind-Unterscheidung**: Enthält `kProcedure`, `kFunction`, `kConstructor`, oder `kDestructor`
### Sichtbarkeit über declSection
Delphi gruppiert Mitglieder in Sichtbarkeitsbereichen:
```
declClass → declSection (kPublic) → declProc, declField, declProp
→ declSection (kPrivate) → declField
```
Die `getVisibility`-Funktion muss den nächsten `declSection`-Vorfahren inspizieren.
### Vererbung und Interface-Implementierung
`declClass` hat ein `parent`-Feld mit einer Liste von `typeref`-Knoten:
- Der **erste** `typeref` ist typischerweise die Basisklasse (`extends`)
- Weitere `typeref`s sind implementierte Interfaces (`implements`)
### `with`-Statements (Phase 2)
`with`-Statements erzeugen einen impliziten Scope, der Call-Qualifier verschleiert:
```delphi
with MyObj do
DoSomething; // ist eigentlich MyObj.DoSomething
```
Im MVP wird dies **nicht** aufgelöst Calls innerhalb von `with` werden ohne Qualifier extrahiert.
### Namespaces
Delphi-Unit-Namen können Punkte enthalten (`System.SysUtils`). Der `moduleName`-Knoten enthält den vollständigen Namen. Die Resolution muss sowohl exakte Matches als auch Suffix-Matches unterstützen.
-139
View File
@@ -1,139 +0,0 @@
# AST-Referenz: tree-sitter-pascal (verifiziert)
Dieses Dokument zeigt die **tatsächlichen** AST-Knotentypen aus der [`tree-sitter-pascal`](https://github.com/Isopod/tree-sitter-pascal) Grammar, verifiziert anhand der `node-types.json` des Pakets.
> **Hinweis:** CodeGraph nutzt keine `.scm`-Dateien zur Extraktion, sondern ein `LanguageExtractor`-Objekt in TypeScript. Die hier dokumentierten AST-Strukturen zeigen, wie der Parser Delphi-Code als Baum darstellt.
## A) Top-Level Module
### unit
```
(unit (kUnit) (moduleName (identifier)) (interface ...) (implementation ...) (kEndDot))
```
### program
```
(program (kProgram) (moduleName (identifier)) ...body...)
```
### library
```
(library (kLibrary) (moduleName (identifier)) ...body...)
```
## B) Deklarationen
### declClass (Klassen, Records, Objects)
```
(declType
name: (identifier) ← "TMyClass"
value: (type (declClass
(kClass) ← oder (kRecord), (kObject)
parent: ((typeref) (typeref) ...) ← Basisklasse + Interfaces
(declSection (kPublic) ← Sichtbarkeit
(declProc ...) ← Methoden
(declField ...) ← Felder
(declProp ...) ← Properties
)
(kEnd))))
```
### declIntf (Interfaces)
```
(declType
name: (identifier) ← "IMyInterface"
value: (type (declIntf
(kInterface)
parent: ((typeref) ...) ← Basis-Interface
guid: (guid ...) ← optionale GUID
(declProc ...) (declProp ...)
(kEnd))))
```
### declProc (Procedures, Functions, Methods, Constructors, Destructors)
```
(declProc
(kFunction) ← oder kProcedure, kConstructor, kDestructor
name: (identifier) ← "MyMethod"
args: (declArgs (declArg name: (identifier) type: (type ...)))
type: (type ...) ← Rückgabetyp (nur bei functions)
attribute: (procAttribute ...)) ← z.B. override, virtual, static
```
**Unterscheidung Funktion vs. Methode:**
- Top-Level: `declProc` als Kind von `interface`/`implementation`
- Methode: `declProc` als Kind von `declClass` oder `declIntf`
- Statisch: enthält `kClass` Kind-Knoten (`class procedure`)
### declProp (Properties)
```
(declProp (kProperty) name: (identifier) type: (type ...) (kRead) (identifier) (kWrite) (identifier))
```
### declField (Felder)
```
(declField name: (identifier) type: (type ...))
```
### declEnum (Enums)
```
(declEnum (declEnumValue name: (identifier)) (declEnumValue name: (identifier)) ...)
```
### declUses (uses-Klauseln)
```
(declUses (kUses) (identifier) (identifier) ...)
```
Optional mit Pfadangabe: `(kIn) (literalString)` für `uses Foo in 'path/Foo.pas'`
### declConst (Konstanten)
```
(declConst name: (identifier) type: (type ...) defaultValue: (defaultValue ...))
```
## C) Ausdrücke
### exprCall (Funktionsaufruf)
```
(exprCall (identifier) ...args...) ← WriteLn(...)
```
### exprDot (qualifizierter Zugriff)
```
(exprDot lhs: (identifier) operator: (kDot) rhs: (exprCall (identifier) ...)) ← Svc.Login(...)
(exprDot lhs: (identifier) operator: (kDot) rhs: (identifier)) ← TAuthService.Create
```
## D) Sichtbarkeit (declSection)
```
(declSection (kPublic)) ← oder kPrivate, kProtected, kPublished, kStrict
```
Gilt für alle nachfolgenden Deklarationen bis zum nächsten `declSection` oder `kEnd`.
## E) Vererbung (parent-Feld von declClass)
Das `parent`-Feld enthält eine Liste von `typeref`-Knoten:
- **Erster** `typeref` = Basisklasse → EdgeKind `extends`
- **Weitere** `typeref`s = Interfaces → EdgeKind `implements`
```
(declClass (kClass) parent: ((typeref (identifier "TInterfacedObject")) (typeref (identifier "ITokenValidator"))) ...)
```
## F) Zusammenfassung: Relevante Knoten für CodeGraph
| AST-Knotentyp | Feld `name` | Weitere wichtige Felder |
|---|---|---|
| `unit`/`program`/`library` | Kind: `moduleName` | `interface`, `implementation` |
| `declClass` | via Parent `declType` | `parent` (Vererbung), `declSection` |
| `declIntf` | via Parent `declType` | `parent`, `guid` |
| `declProc` | `name` | `args`, `type`, `attribute` |
| `declProp` | `name` | `type` |
| `declField` | `name` | `type` |
| `declEnum` | — | Kind: `declEnumValue` |
| `declEnumValue` | `name` | `value` |
| `declConst` | `name` | `type`, `defaultValue` |
| `declUses` | — | Kind: `identifier` |
| `exprCall` | — | Kind: `identifier` (callee) |
| `exprDot` | — | `lhs`, `rhs` |
-71
View File
@@ -1,71 +0,0 @@
# Checklist: Pascal/Delphi Support in CodeGraph
## 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'`, `'**/*.dfm'`, `'**/*.fmx'` erweitern
- [ ] `src/extraction/grammars.ts`: Grammar-Loader `pascal: () => require('tree-sitter-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
## B) Node-Extraktion
- [ ] `unit` / `program` / `library` → NodeKind `module`
- [ ] `declClass` → NodeKind `class` (inkl. record, object)
- [ ] `declIntf` → NodeKind `interface`
- [ ] `declProc` (top-level) → NodeKind `function`
- [ ] `declProc` (in Klasse) → NodeKind `method` (inkl. constructor/destructor)
- [ ] `declProp` → NodeKind `property`
- [ ] `declField` → NodeKind `field`
- [ ] `declEnum` → NodeKind `enum`
- [ ] `declEnumValue` → NodeKind `enum_member`
- [ ] `declConst` → NodeKind `constant`
- [ ] `declType` (einfach) → NodeKind `type_alias`
## C) Edge-Extraktion
- [ ] `declUses` → EdgeKind `imports`
- [ ] `declClass.parent[0]` → EdgeKind `extends`
- [ ] `declClass.parent[1..]` → EdgeKind `implements`
- [ ] `exprCall` → EdgeKind `calls`
- [ ] `exprDot` + `exprCall` → EdgeKind `calls` (qualifiziert)
- [ ] `TClass.Create` → EdgeKind `instantiates`
- [ ] Parent-Child Containment → EdgeKind `contains`
## D) Sichtbarkeit & Attribute
- [ ] `getVisibility`: `declSection` → public/private/protected
- [ ] `isStatic`: `kClass` in `declProc` → true
- [ ] `getSignature`: Parameter + Rückgabetyp extrahieren
## E) Resolution
- [ ] `moduleName``unitName → fileId` Mapping
- [ ] `uses X;` → Unit-Node auflösen
- [ ] `uses X in 'path'` → Datei direkt auflösen (Phase 2)
- [ ] Call-Resolution: gleiche Unit → gleiche Klasse → uses-Units → global
## F) Testing
- [ ] Fixtures in Test-Suite aufnehmen (`UAuth.pas`, `App.dpr`, `UTypes.pas`)
- [ ] Nodes: Anzahl + Namen verifizieren
- [ ] Edges: imports/extends/implements/calls prüfen
- [ ] Sichtbarkeiten prüfen
- [ ] Properties, Enums, Konstanten prüfen
## G) Polishing
- [ ] Language in Supported-Languages-Dokumentation aufnehmen
- [ ] CLI-Hilfe / Config-Validierung aktualisieren
## 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.
- [ ] **`.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.
-86
View File
@@ -1,86 +0,0 @@
# NodeKind Mapping: Delphi → CodeGraph
Dieses Dokument zeigt die explizite Zuordnung von Delphi/Pascal-Konzepten zu den bestehenden CodeGraph `NodeKind`- und `EdgeKind`-Werten.
> **Ergebnis:** Alle Delphi-Konzepte lassen sich auf existierende `NodeKind`-Werte abbilden. Es werden **keine neuen** Kinds benötigt.
## Node-Mapping
| Delphi-Konzept | tree-sitter Typ | CodeGraph `NodeKind` | Anmerkungen |
|---|---|---|---|
| Unit | `unit` + `moduleName` | `module` | Kompilierungseinheit, vergleichbar mit Modulen |
| Program | `program` + `moduleName` | `module` | Ausführbarer Einstiegspunkt |
| Library | `library` + `moduleName` | `module` | DLL/shared library |
| Klasse | `declClass` (mit `kClass`) | `class` | Standard-Klasse |
| Record | `declClass` (mit `kRecord`) | `class` | Value-Type, aber strukturell wie Klasse |
| Object | `declClass` (mit `kObject`) | `class` | Legacy, wie Klasse behandeln |
| Interface | `declIntf` (mit `kInterface`) | `interface` | — |
| DispInterface | `declIntf` (mit `kDispInterface`) | `interface` | COM-spezifisch |
| Procedure (top-level) | `declProc` (mit `kProcedure`) | `function` | Ohne Rückgabetyp |
| Function (top-level) | `declProc` (mit `kFunction`) | `function` | Mit Rückgabetyp |
| Methode | `declProc` in `declClass` | `method` | Instance-Methode |
| Constructor | `declProc` (mit `kConstructor`) | `method` | Spezielle Methode |
| Destructor | `declProc` (mit `kDestructor`) | `method` | Spezielle Methode |
| Class Method | `declProc` (mit `kClass`) | `method` | Statisch, `isStatic = true` |
| Property | `declProp` | `property` | Mit Getter/Setter |
| Feld | `declField` | `field` | — |
| Enum | `declEnum` | `enum` | — |
| Enum-Wert | `declEnumValue` | `enum_member` | — |
| Type-Alias | `declType` (einfach) | `type_alias` | `type TMyInt = Integer;` |
| Konstante | `declConst` | `constant` | — |
| Uses-Eintrag | `declUses``identifier` | `import` | Pro Unit-Name ein Import-Node |
## Edge-Mapping
| Delphi-Beziehung | Erkennung | CodeGraph `EdgeKind` |
|---|---|---|
| `uses X;` | `declUses` → Kind `identifier` | `imports` |
| Basisklasse | `declClass.parent[0]` (erster `typeref`) | `extends` |
| Interface-Impl. | `declClass.parent[1..]` (weitere `typeref`s) | `implements` |
| Funktionsaufruf | `exprCall` | `calls` |
| Qualifizierter Aufruf | `exprDot``exprCall` | `calls` |
| `TClass.Create` | `exprDot` mit `.Create` Suffix | `instantiates` |
| 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 |
| `exports`-Klauseln | `declExports` → könnte als `export` NodeKind abgebildet werden |
| Generics | `typerefTpl` → beeinflusst Typ-Referenzen, nicht den NodeKind |
## Sichtbarkeit
| Delphi-Keyword | CodeGraph Visibility |
|---|---|
| `published` | `public` (CodeGraph kennt kein `published`) |
| `public` | `public` |
| `protected` | `protected` |
| `private` | `private` |
| `strict private` | `private` |
| `strict protected` | `protected` |
| (keine Angabe) | `public` (Default in Delphi-Klassen) |
-357
View File
@@ -1,357 +0,0 @@
# Integration Guide: Konkrete Code-Änderungen
Dieses Dokument zeigt die **exakten Änderungen** an den CodeGraph-Quelldateien, die für Pascal/Delphi-Support nötig sind.
## 1) Dependency installieren
```bash
npm install tree-sitter-pascal
```
## 2) `src/types.ts` — Language-Typ erweitern
```typescript
// Zum Language Union-Type hinzufügen (nach 'liquid'):
export type Language =
| 'typescript'
// ...bestehende Sprachen...
| 'liquid'
| 'pascal' // ← NEU
| 'unknown';
```
**DEFAULT_CONFIG.include** erweitern:
```typescript
include: [
// ...bestehende Patterns...
// Liquid (Shopify themes)
'**/*.liquid',
// Pascal / Delphi ← NEU
'**/*.pas',
'**/*.dpr',
'**/*.dpk',
'**/*.lpr',
],
```
## 3) `src/extraction/grammars.ts` — Grammar registrieren
### grammarLoaders Map:
```typescript
const grammarLoaders: Record<GrammarLanguage, GrammarLoader> = {
// ...bestehende Loader...
dart: () => {
return require('@sengac/tree-sitter-dart');
},
pascal: () => { // ← NEU
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('tree-sitter-pascal');
},
};
```
### EXTENSION_MAP:
```typescript
export const EXTENSION_MAP: Record<string, Language> = {
// ...bestehende Mappings...
'.liquid': 'liquid',
'.svelte': 'svelte',
'.pas': 'pascal', // ← NEU
'.dpr': 'pascal', // ← NEU
'.dpk': 'pascal', // ← NEU
'.lpr': 'pascal', // ← NEU
};
```
### getLanguageDisplayName():
```typescript
const names: Record<Language, string> = {
// ...bestehende Namen...
liquid: 'Liquid',
pascal: 'Pascal / Delphi', // ← NEU
unknown: 'Unknown',
};
```
## 4) `src/extraction/tree-sitter.ts` — LanguageExtractor
```typescript
pascal: {
functionTypes: ['declProc'], // Top-Level Funktionen/Prozeduren
classTypes: ['declClass'], // Klassen, Records, Objects
methodTypes: ['declProc'], // Methoden (in Klasse), Konstruktoren, Destruktoren
interfaceTypes: ['declIntf'], // Interfaces
structTypes: [], // Records über declClass abgedeckt
enumTypes: ['declEnum'], // Aufzählungen
typeAliasTypes: ['declType'], // Type-Aliase
importTypes: ['declUses'], // uses-Klauseln
callTypes: ['exprCall'], // Funktionsaufrufe
variableTypes: ['declField', 'declConst'], // Felder und Konstanten
nameField: 'name',
bodyField: 'body', // nur bei defProc (Implementierung)
paramsField: 'args', // declArgs
returnField: 'type',
getSignature: (node, source) => {
const args = getChildByField(node, 'args');
const returnType = getChildByField(node, 'type');
if (!args) return undefined;
const argsText = getNodeText(args, source);
return returnType
? getNodeText(returnType, source) + ' ' + argsText
: argsText;
},
getVisibility: (node) => {
// Suche den nächsten declSection-Vorfahren
let current = node.parent;
while (current) {
if (current.type === 'declSection') {
for (let i = 0; i < current.childCount; i++) {
const child = current.child(i);
if (child?.type === 'kPublic' || child?.type === 'kPublished')
return 'public';
if (child?.type === 'kPrivate') return 'private';
if (child?.type === 'kProtected') return 'protected';
}
}
current = current.parent;
}
return undefined;
},
isStatic: (node) => {
// class procedure / class function
for (let i = 0; i < node.childCount; i++) {
if (node.child(i)?.type === 'kClass') return true;
}
return false;
},
isConst: (node) => {
return node.type === 'declConst';
},
},
```
## 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
View File
@@ -1,149 +0,0 @@
# 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.
-41
View File
@@ -1,41 +0,0 @@
# CodeGraph Pascal/Delphi Support — Konzept & Bauplan
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. Zusätzlich werden **DFM/FMX-Formulardateien** unterstützt, um UI-Komponenten und Event-Handler-Verknüpfungen zu erfassen.
## Inhalt
| Verzeichnis / Datei | Beschreibung |
|---|---|
| `Docs/01-Implementation-Plan.md` | Implementierungs-Roadmap mit konkreten AST-Typen |
| `Docs/02-Capture-Convention.md` | Wie Pascal an CodeGraphs `LanguageExtractor`-Interface andockt |
| `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` + 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
CodeGraph verwendet **keine** per-Sprache Plugin-Verzeichnisse. Die Integration erfolgt zentral in drei Dateien:
1. **`src/types.ts`** — `Language` Union-Type und `DEFAULT_CONFIG.include` Patterns
2. **`src/extraction/grammars.ts`** — Grammar-Loader, Extension-Mapping, Display-Name
3. **`src/extraction/tree-sitter.ts`** — `LanguageExtractor`-Konfiguration in der `EXTRACTORS` Map
## Wie du damit arbeitest
1. **Integration Guide lesen** (`Docs/06-Integration-Guide.md`) — enthält die exakten Code-Änderungen
2. **`npm install tree-sitter-pascal`** als Dependency hinzufügen
3. **Änderungen in den drei Dateien** vornehmen (types.ts, grammars.ts, tree-sitter.ts)
4. **Resolver erweitern** (mindestens `uses` → Unit-File, einfache Call-Auflösung)
5. **Fixtures in die Tests aufnehmen** und Assertions hinzufügen
## Grammar & Parsing
**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.
-19
View File
@@ -1,19 +0,0 @@
program App;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
UAuth in 'UAuth.pas';
var
Svc: TAuthService;
begin
Svc := TAuthService.Create;
try
Writeln(Svc.Login('olaf', 'secret'));
finally
Svc.Free;
end;
end.
-92
View File
@@ -1,92 +0,0 @@
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
-81
View File
@@ -1,81 +0,0 @@
/// Unit UAuth: Authentication-Service mit Interface-Implementierung.
/// Testet: uses, interface (mit GUID), Klasse mit Vererbung + Interface,
/// Sichtbarkeitsbereiche (private/public), Felder, Properties,
/// Methoden, Constructor, Destructor, Funktionsaufrufe.
unit UAuth;
interface
uses
System.SysUtils,
System.Classes;
type
/// Interface für Token-Validierung
ITokenValidator = interface
['{11111111-1111-1111-1111-111111111111}']
function Validate(const AToken: string): Boolean;
end;
/// Auth-Service mit Token-Validierung
TAuthService = class(TInterfacedObject, ITokenValidator)
private
FToken: string;
FLoginCount: Integer;
procedure IncLoginCount;
protected
function GetToken: string;
public
constructor Create;
destructor Destroy; override;
function Validate(const AToken: string): Boolean;
function Login(const AUser, APass: string): string;
property Token: string read GetToken;
property LoginCount: Integer read FLoginCount;
end;
implementation
{ TAuthService }
constructor TAuthService.Create;
begin
inherited Create;
FToken := '';
FLoginCount := 0;
end;
destructor TAuthService.Destroy;
begin
FToken := '';
inherited Destroy;
end;
procedure TAuthService.IncLoginCount;
begin
Inc(FLoginCount);
end;
function TAuthService.GetToken: string;
begin
Result := FToken;
end;
function TAuthService.Validate(const AToken: string): Boolean;
begin
Result := AToken <> '';
end;
function TAuthService.Login(const AUser, APass: string): string;
begin
IncLoginCount;
if Validate(AUser + ':' + APass) then
begin
FToken := AUser;
Result := 'ok';
end
else
Result := '';
end;
end.
-75
View File
@@ -1,75 +0,0 @@
/// Unit UTypes: Delphi-Typsystem-Fixture.
/// Testet: Enums, Records (mit Feldern), Type-Aliase,
/// Konstanten, verschachtelte Typen, Class-Methoden (static).
unit UTypes;
interface
uses
System.SysUtils;
const
C_MAX_RETRIES = 3;
C_DEFAULT_NAME = 'Guest';
type
/// Enum: Benutzerrolle
TUserRole = (urAdmin, urEditor, urViewer);
/// Record: Punkt mit X/Y-Koordinaten
TPoint2D = record
X: Double;
Y: Double;
end;
/// Type-Alias
TUserName = string;
/// Klasse mit class method (static), verschachteltem Typ und Enum-Verwendung
TUserInfo = class
public
type
/// Verschachtelter Record innerhalb der Klasse
TAddress = record
Street: string;
City: string;
Zip: string;
end;
private
FName: TUserName;
FRole: TUserRole;
FAddress: TAddress;
public
constructor Create(const AName: TUserName; ARole: TUserRole);
function GetDisplayName: string;
class function CreateAdmin(const AName: TUserName): TUserInfo; static;
property Name: TUserName read FName write FName;
property Role: TUserRole read FRole;
property Address: TAddress read FAddress write FAddress;
end;
implementation
{ TUserInfo }
constructor TUserInfo.Create(const AName: TUserName; ARole: TUserRole);
begin
FName := AName;
FRole := ARole;
end;
function TUserInfo.GetDisplayName: string;
begin
if FRole = urAdmin then
Result := '[Admin] ' + FName
else
Result := FName;
end;
class function TUserInfo.CreateAdmin(const AName: TUserName): TUserInfo;
begin
Result := TUserInfo.Create(AName, urAdmin);
end;
end.
-45
View File
@@ -1,45 +0,0 @@
# Unit Resolution Heuristics (uses → File)
## Input
- Extrahierte Edges vom Typ `imports` aus `declUses`-Knoten.
- Extrahierte Nodes vom Typ `module` aus `unit`/`program`/`library` (Name aus `moduleName`-Kind-Knoten).
## AST-Struktur
```
(declUses
(kUses)
(identifier "System.SysUtils") ← Unit-Name
(identifier "UAuth") ← Unit-Name
(kIn) (literalString "'UAuth.pas'") ← optionaler Pfad
)
```
## MVP Algorithm
1. Build `unitName → nodeId` Map für alle indexierten Dateien (aus `moduleName`-Knoten).
2. Für jeden `imports`-Edge mit Zielname `X`:
- Exakter Match auf `X`
- Case-insensitiver Match (Delphi ist case-insensitive)
- Namespace-Normalisierung:
- `System.SysUtils` → versuche auch `SysUtils` (letzter Segment)
- Dateiname-basierter Fallback: `X``X.pas` in den indexierten Dateien suchen
3. Falls aufgelöst: `edge.to_node_id = targetNodeId`
4. Falls nicht aufgelöst: `edge.to_symbol = X` beibehalten (Suche funktioniert trotzdem)
## DPR/DPK `in 'path'` (Phase 2)
In Delphi-Projektdateien kann ein expliziter Pfad angegeben werden:
```delphi
uses
Foo in 'src/Foo.pas',
Bar in 'Bar.pas';
```
Im AST erkennbar durch `(kIn) (literalString)` nach dem `(identifier)`.
**Enhancement:**
- `literalString` extrahieren und direkt zur Datei auflösen
- Pfad relativ zur DPR/DPK-Datei interpretieren