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
This commit is contained in:
@@ -3,9 +3,9 @@
|
|||||||
## A) Plumbing (Infrastruktur)
|
## A) Plumbing (Infrastruktur)
|
||||||
- [ ] `npm install tree-sitter-pascal` als Dependency
|
- [ ] `npm install tree-sitter-pascal` als Dependency
|
||||||
- [ ] `src/types.ts`: `'pascal'` zum `Language` Union-Type hinzufügen
|
- [ ] `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`: 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/grammars.ts`: Display-Name `pascal: 'Pascal / Delphi'`
|
||||||
- [ ] `src/extraction/tree-sitter.ts`: `LanguageExtractor` für `pascal` in `EXTRACTORS` Map
|
- [ ] `src/extraction/tree-sitter.ts`: `LanguageExtractor` für `pascal` in `EXTRACTORS` Map
|
||||||
|
|
||||||
@@ -53,9 +53,19 @@
|
|||||||
- [ ] Language in Supported-Languages-Dokumentation aufnehmen
|
- [ ] Language in Supported-Languages-Dokumentation aufnehmen
|
||||||
- [ ] CLI-Hilfe / Config-Validierung aktualisieren
|
- [ ] 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.
|
- [ ] **`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.
|
- [ ] **`.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.
|
- [ ] **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.
|
- [ ] **Generics** (Phase 2): `typerefTpl` und `genericTpl` für generische Typen.
|
||||||
|
|||||||
@@ -43,13 +43,32 @@ Dieses Dokument zeigt die explizite Zuordnung von Delphi/Pascal-Konzepten zu den
|
|||||||
| Unit enthält Klasse | AST Parent-Child | `contains` |
|
| Unit enthält Klasse | AST Parent-Child | `contains` |
|
||||||
| Klasse enthält Methode | 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+)
|
## Nicht abgebildete Konzepte (Phase 2+)
|
||||||
|
|
||||||
| Konzept | Grund |
|
| Konzept | Grund |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `with`-Statement | Kein eigener EdgeKind nötig; erschwert nur die Call-Qualifizierung |
|
| `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 |
|
| 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 |
|
| `exports`-Klauseln | `declExports` → könnte als `export` NodeKind abgebildet werden |
|
||||||
| Generics | `typerefTpl` → beeinflusst Typ-Referenzen, nicht den NodeKind |
|
| Generics | `typerefTpl` → beeinflusst Typ-Referenzen, nicht den NodeKind |
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
- `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.
|
- Die `getVisibility`-Funktion traversiert aufwärts zum `declSection`-Knoten.
|
||||||
- Records werden als `class` behandelt, da `declClass` mit `kRecord` Kind-Knoten beides abdeckt.
|
- 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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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).
|
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
|
## 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/03-AST-Referenz.md` | Verifizierte AST-Knotentypen aus tree-sitter-pascal |
|
||||||
| `Docs/04-Checklist.md` | Umsetzungs-Checkliste mit bekannten Einschränkungen |
|
| `Docs/04-Checklist.md` | Umsetzungs-Checkliste mit bekannten Einschränkungen |
|
||||||
| `Docs/05-NodeKind-Mapping.md` | Explizite Zuordnung Delphi → CodeGraph NodeKind/EdgeKind |
|
| `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` |
|
| `Docs/06-Integration-Guide.md` | Konkrete Code-Diffs für `grammars.ts`, `types.ts`, `tree-sitter.ts` + DfmExtractor |
|
||||||
| `fixtures/` | Delphi-Beispieldateien zum Testen der Extraktion |
|
| `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) |
|
| `resolution/` | Resolver-Heuristiken (Unit-Mapping, Call-Resolution) |
|
||||||
|
|
||||||
## Architektur-Übersicht
|
## 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)
|
4. **Resolver erweitern** (mindestens `uses` → Unit-File, einfache Call-Auflösung)
|
||||||
5. **Fixtures in die Tests aufnehmen** und Assertions hinzufügen
|
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`)
|
**Pascal-Dateien** (`.pas`, `.dpr`, `.dpk`, `.lpr`) werden mit [`tree-sitter-pascal`](https://github.com/Isopod/tree-sitter-pascal) geparst (AST-basierte Extraktion via `LanguageExtractor`).
|
||||||
- Unterstützt: Delphi, FreePascal, Standard-Pascal
|
|
||||||
- Dateierweiterungen: `.pas`, `.dpr`, `.dpk`, `.lpr`
|
**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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
Reference in New Issue
Block a user