@colbymchenry/codegraph 0.5.7 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +682 -681
- package/dist/bin/codegraph.js +14 -14
- package/dist/bin/codegraph.js.map +1 -1
- package/dist/bin/uninstall.d.ts +15 -0
- package/dist/bin/uninstall.d.ts.map +1 -0
- package/dist/bin/uninstall.js +174 -0
- package/dist/bin/uninstall.js.map +1 -0
- package/dist/db/migrations.js +11 -11
- package/dist/db/queries.js +86 -86
- package/dist/db/schema.sql +163 -163
- package/dist/directory.js +16 -16
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +50 -4
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +64 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +586 -1
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/installer/banner.d.ts +1 -1
- package/dist/installer/banner.d.ts.map +1 -1
- package/dist/installer/banner.js +12 -10
- package/dist/installer/banner.js.map +1 -1
- package/dist/installer/claude-md-template.js +32 -32
- package/dist/installer/config-writer.d.ts +0 -1
- package/dist/installer/config-writer.d.ts.map +1 -1
- package/dist/installer/config-writer.js +8 -26
- package/dist/installer/config-writer.js.map +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +11 -26
- package/dist/installer/index.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts +4 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +9 -0
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +1 -1
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +39 -4
- package/dist/resolution/index.js.map +1 -1
- package/dist/sync/git-hooks.d.ts +66 -0
- package/dist/sync/git-hooks.d.ts.map +1 -0
- package/dist/sync/git-hooks.js +281 -0
- package/dist/sync/git-hooks.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -1
- package/dist/vectors/search.js +33 -33
- package/package.json +60 -59
- package/scripts/patch-tree-sitter-dart.js +112 -112
- package/scripts/postinstall.js +68 -68
|
@@ -38,7 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
};
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
exports.SvelteExtractor = exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
|
|
41
|
+
exports.DfmExtractor = exports.SvelteExtractor = exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
|
|
42
42
|
exports.generateNodeId = generateNodeId;
|
|
43
43
|
exports.extractFromSource = extractFromSource;
|
|
44
44
|
const crypto = __importStar(require("crypto"));
|
|
@@ -767,6 +767,67 @@ const EXTRACTORS = {
|
|
|
767
767
|
return false;
|
|
768
768
|
},
|
|
769
769
|
},
|
|
770
|
+
pascal: {
|
|
771
|
+
functionTypes: ['declProc'],
|
|
772
|
+
classTypes: ['declClass'],
|
|
773
|
+
methodTypes: ['declProc'],
|
|
774
|
+
interfaceTypes: ['declIntf'],
|
|
775
|
+
structTypes: [],
|
|
776
|
+
enumTypes: ['declEnum'],
|
|
777
|
+
typeAliasTypes: ['declType'],
|
|
778
|
+
importTypes: ['declUses'],
|
|
779
|
+
callTypes: ['exprCall'],
|
|
780
|
+
variableTypes: ['declField', 'declConst'],
|
|
781
|
+
nameField: 'name',
|
|
782
|
+
bodyField: 'body',
|
|
783
|
+
paramsField: 'args',
|
|
784
|
+
returnField: 'type',
|
|
785
|
+
getSignature: (node, source) => {
|
|
786
|
+
const args = getChildByField(node, 'args');
|
|
787
|
+
const returnType = node.namedChildren.find((c) => c.type === 'typeref');
|
|
788
|
+
if (!args && !returnType)
|
|
789
|
+
return undefined;
|
|
790
|
+
let sig = '';
|
|
791
|
+
if (args)
|
|
792
|
+
sig = getNodeText(args, source);
|
|
793
|
+
if (returnType) {
|
|
794
|
+
sig += ': ' + getNodeText(returnType, source);
|
|
795
|
+
}
|
|
796
|
+
return sig || undefined;
|
|
797
|
+
},
|
|
798
|
+
getVisibility: (node) => {
|
|
799
|
+
let current = node.parent;
|
|
800
|
+
while (current) {
|
|
801
|
+
if (current.type === 'declSection') {
|
|
802
|
+
for (let i = 0; i < current.childCount; i++) {
|
|
803
|
+
const child = current.child(i);
|
|
804
|
+
if (child?.type === 'kPublic' || child?.type === 'kPublished')
|
|
805
|
+
return 'public';
|
|
806
|
+
if (child?.type === 'kPrivate')
|
|
807
|
+
return 'private';
|
|
808
|
+
if (child?.type === 'kProtected')
|
|
809
|
+
return 'protected';
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
current = current.parent;
|
|
813
|
+
}
|
|
814
|
+
return undefined;
|
|
815
|
+
},
|
|
816
|
+
isExported: (_node, _source) => {
|
|
817
|
+
// In Pascal, symbols declared in the interface section are exported
|
|
818
|
+
return false;
|
|
819
|
+
},
|
|
820
|
+
isStatic: (node) => {
|
|
821
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
822
|
+
if (node.child(i)?.type === 'kClass')
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
return false;
|
|
826
|
+
},
|
|
827
|
+
isConst: (node) => {
|
|
828
|
+
return node.type === 'declConst';
|
|
829
|
+
},
|
|
830
|
+
},
|
|
770
831
|
};
|
|
771
832
|
// TSX and JSX use the same extractors as their base languages
|
|
772
833
|
EXTRACTORS.tsx = EXTRACTORS.typescript;
|
|
@@ -831,6 +892,7 @@ class TreeSitterExtractor {
|
|
|
831
892
|
errors = [];
|
|
832
893
|
extractor = null;
|
|
833
894
|
nodeStack = []; // Stack of parent node IDs
|
|
895
|
+
methodIndex = null; // lookup key → node ID for Pascal defProc lookup
|
|
834
896
|
constructor(filePath, source, language) {
|
|
835
897
|
this.filePath = filePath;
|
|
836
898
|
this.source = source;
|
|
@@ -920,6 +982,12 @@ class TreeSitterExtractor {
|
|
|
920
982
|
return;
|
|
921
983
|
const nodeType = node.type;
|
|
922
984
|
let skipChildren = false;
|
|
985
|
+
// Pascal-specific AST handling
|
|
986
|
+
if (this.language === 'pascal') {
|
|
987
|
+
skipChildren = this.visitPascalNode(node);
|
|
988
|
+
if (skipChildren)
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
923
991
|
// Check for function declarations
|
|
924
992
|
// For Python/Ruby, function_definition inside a class should be treated as method
|
|
925
993
|
if (this.extractor.functionTypes.includes(nodeType)) {
|
|
@@ -1913,6 +1981,370 @@ class TreeSitterExtractor {
|
|
|
1913
1981
|
}
|
|
1914
1982
|
}
|
|
1915
1983
|
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Handle Pascal-specific AST structures.
|
|
1986
|
+
* Returns true if the node was fully handled and children should be skipped.
|
|
1987
|
+
*/
|
|
1988
|
+
visitPascalNode(node) {
|
|
1989
|
+
const nodeType = node.type;
|
|
1990
|
+
// Unit/Program/Library → module node
|
|
1991
|
+
if (nodeType === 'unit' || nodeType === 'program' || nodeType === 'library') {
|
|
1992
|
+
const moduleNameNode = node.namedChildren.find((c) => c.type === 'moduleName');
|
|
1993
|
+
const name = moduleNameNode ? getNodeText(moduleNameNode, this.source) : '';
|
|
1994
|
+
// Fallback to filename without extension if module name is empty
|
|
1995
|
+
const moduleName = name || path.basename(this.filePath).replace(/\.[^.]+$/, '');
|
|
1996
|
+
this.createNode('module', moduleName, node);
|
|
1997
|
+
// Continue visiting children (interface/implementation sections)
|
|
1998
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1999
|
+
const child = node.namedChild(i);
|
|
2000
|
+
if (child)
|
|
2001
|
+
this.visitNode(child);
|
|
2002
|
+
}
|
|
2003
|
+
return true;
|
|
2004
|
+
}
|
|
2005
|
+
// declType wraps declClass/declIntf/declEnum/type-alias
|
|
2006
|
+
// The name lives on declType, the inner node determines the kind
|
|
2007
|
+
if (nodeType === 'declType') {
|
|
2008
|
+
this.extractPascalDeclType(node);
|
|
2009
|
+
return true;
|
|
2010
|
+
}
|
|
2011
|
+
// declUses → import nodes for each unit name
|
|
2012
|
+
if (nodeType === 'declUses') {
|
|
2013
|
+
this.extractPascalUses(node);
|
|
2014
|
+
return true;
|
|
2015
|
+
}
|
|
2016
|
+
// declConsts → container; visit children for individual declConst
|
|
2017
|
+
if (nodeType === 'declConsts') {
|
|
2018
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2019
|
+
const child = node.namedChild(i);
|
|
2020
|
+
if (child?.type === 'declConst') {
|
|
2021
|
+
this.extractPascalConst(child);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
return true;
|
|
2025
|
+
}
|
|
2026
|
+
// declConst at top level (outside declConsts)
|
|
2027
|
+
if (nodeType === 'declConst') {
|
|
2028
|
+
this.extractPascalConst(node);
|
|
2029
|
+
return true;
|
|
2030
|
+
}
|
|
2031
|
+
// declTypes → container for type declarations
|
|
2032
|
+
if (nodeType === 'declTypes') {
|
|
2033
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2034
|
+
const child = node.namedChild(i);
|
|
2035
|
+
if (child)
|
|
2036
|
+
this.visitNode(child);
|
|
2037
|
+
}
|
|
2038
|
+
return true;
|
|
2039
|
+
}
|
|
2040
|
+
// declVars → container for variable declarations
|
|
2041
|
+
if (nodeType === 'declVars') {
|
|
2042
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2043
|
+
const child = node.namedChild(i);
|
|
2044
|
+
if (child?.type === 'declVar') {
|
|
2045
|
+
const nameNode = getChildByField(child, 'name');
|
|
2046
|
+
if (nameNode) {
|
|
2047
|
+
const name = getNodeText(nameNode, this.source);
|
|
2048
|
+
this.createNode('variable', name, child);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
return true;
|
|
2053
|
+
}
|
|
2054
|
+
// defProc in implementation section → extract calls but don't create duplicate nodes
|
|
2055
|
+
if (nodeType === 'defProc') {
|
|
2056
|
+
this.extractPascalDefProc(node);
|
|
2057
|
+
return true;
|
|
2058
|
+
}
|
|
2059
|
+
// declProp → property node
|
|
2060
|
+
if (nodeType === 'declProp') {
|
|
2061
|
+
const nameNode = getChildByField(node, 'name');
|
|
2062
|
+
if (nameNode) {
|
|
2063
|
+
const name = getNodeText(nameNode, this.source);
|
|
2064
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
2065
|
+
this.createNode('property', name, node, { visibility });
|
|
2066
|
+
}
|
|
2067
|
+
return true;
|
|
2068
|
+
}
|
|
2069
|
+
// declField → field node
|
|
2070
|
+
if (nodeType === 'declField') {
|
|
2071
|
+
const nameNode = getChildByField(node, 'name');
|
|
2072
|
+
if (nameNode) {
|
|
2073
|
+
const name = getNodeText(nameNode, this.source);
|
|
2074
|
+
const visibility = this.extractor.getVisibility?.(node);
|
|
2075
|
+
this.createNode('field', name, node, { visibility });
|
|
2076
|
+
}
|
|
2077
|
+
return true;
|
|
2078
|
+
}
|
|
2079
|
+
// declSection → visit children (propagates visibility via getVisibility)
|
|
2080
|
+
if (nodeType === 'declSection') {
|
|
2081
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2082
|
+
const child = node.namedChild(i);
|
|
2083
|
+
if (child)
|
|
2084
|
+
this.visitNode(child);
|
|
2085
|
+
}
|
|
2086
|
+
return true;
|
|
2087
|
+
}
|
|
2088
|
+
// exprCall → extract function call reference
|
|
2089
|
+
if (nodeType === 'exprCall') {
|
|
2090
|
+
this.extractPascalCall(node);
|
|
2091
|
+
return true;
|
|
2092
|
+
}
|
|
2093
|
+
// interface/implementation sections → visit children
|
|
2094
|
+
if (nodeType === 'interface' || nodeType === 'implementation') {
|
|
2095
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2096
|
+
const child = node.namedChild(i);
|
|
2097
|
+
if (child)
|
|
2098
|
+
this.visitNode(child);
|
|
2099
|
+
}
|
|
2100
|
+
return true;
|
|
2101
|
+
}
|
|
2102
|
+
// block (begin..end) → visit for calls
|
|
2103
|
+
if (nodeType === 'block') {
|
|
2104
|
+
this.visitPascalBlock(node);
|
|
2105
|
+
return true;
|
|
2106
|
+
}
|
|
2107
|
+
return false;
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Extract a Pascal declType node (class, interface, enum, or type alias)
|
|
2111
|
+
*/
|
|
2112
|
+
extractPascalDeclType(node) {
|
|
2113
|
+
const nameNode = getChildByField(node, 'name');
|
|
2114
|
+
if (!nameNode)
|
|
2115
|
+
return;
|
|
2116
|
+
const name = getNodeText(nameNode, this.source);
|
|
2117
|
+
// Find the inner type declaration
|
|
2118
|
+
const declClass = node.namedChildren.find((c) => c.type === 'declClass');
|
|
2119
|
+
const declIntf = node.namedChildren.find((c) => c.type === 'declIntf');
|
|
2120
|
+
const typeChild = node.namedChildren.find((c) => c.type === 'type');
|
|
2121
|
+
if (declClass) {
|
|
2122
|
+
const classNode = this.createNode('class', name, node);
|
|
2123
|
+
// Extract inheritance from typeref children of declClass
|
|
2124
|
+
this.extractPascalInheritance(declClass, classNode.id);
|
|
2125
|
+
// Visit class body
|
|
2126
|
+
this.nodeStack.push(classNode.id);
|
|
2127
|
+
for (let i = 0; i < declClass.namedChildCount; i++) {
|
|
2128
|
+
const child = declClass.namedChild(i);
|
|
2129
|
+
if (child)
|
|
2130
|
+
this.visitNode(child);
|
|
2131
|
+
}
|
|
2132
|
+
this.nodeStack.pop();
|
|
2133
|
+
}
|
|
2134
|
+
else if (declIntf) {
|
|
2135
|
+
const ifaceNode = this.createNode('interface', name, node);
|
|
2136
|
+
// Visit interface members
|
|
2137
|
+
this.nodeStack.push(ifaceNode.id);
|
|
2138
|
+
for (let i = 0; i < declIntf.namedChildCount; i++) {
|
|
2139
|
+
const child = declIntf.namedChild(i);
|
|
2140
|
+
if (child)
|
|
2141
|
+
this.visitNode(child);
|
|
2142
|
+
}
|
|
2143
|
+
this.nodeStack.pop();
|
|
2144
|
+
}
|
|
2145
|
+
else if (typeChild) {
|
|
2146
|
+
// Check if it contains a declEnum
|
|
2147
|
+
const declEnum = typeChild.namedChildren.find((c) => c.type === 'declEnum');
|
|
2148
|
+
if (declEnum) {
|
|
2149
|
+
const enumNode = this.createNode('enum', name, node);
|
|
2150
|
+
// Extract enum members
|
|
2151
|
+
this.nodeStack.push(enumNode.id);
|
|
2152
|
+
for (let i = 0; i < declEnum.namedChildCount; i++) {
|
|
2153
|
+
const child = declEnum.namedChild(i);
|
|
2154
|
+
if (child?.type === 'declEnumValue') {
|
|
2155
|
+
const memberName = getChildByField(child, 'name');
|
|
2156
|
+
if (memberName) {
|
|
2157
|
+
this.createNode('enum_member', getNodeText(memberName, this.source), child);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
this.nodeStack.pop();
|
|
2162
|
+
}
|
|
2163
|
+
else {
|
|
2164
|
+
// Simple type alias: type TFoo = string / type TFoo = Integer
|
|
2165
|
+
this.createNode('type_alias', name, node);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
else {
|
|
2169
|
+
// Fallback: could be a forward declaration or simple alias
|
|
2170
|
+
this.createNode('type_alias', name, node);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Extract Pascal uses clause into individual import nodes
|
|
2175
|
+
*/
|
|
2176
|
+
extractPascalUses(node) {
|
|
2177
|
+
const importText = getNodeText(node, this.source).trim();
|
|
2178
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2179
|
+
const child = node.namedChild(i);
|
|
2180
|
+
if (child?.type === 'moduleName') {
|
|
2181
|
+
const unitName = getNodeText(child, this.source);
|
|
2182
|
+
this.createNode('import', unitName, child, {
|
|
2183
|
+
signature: importText,
|
|
2184
|
+
});
|
|
2185
|
+
// Create unresolved reference for resolution
|
|
2186
|
+
if (this.nodeStack.length > 0) {
|
|
2187
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
2188
|
+
if (parentId) {
|
|
2189
|
+
this.unresolvedReferences.push({
|
|
2190
|
+
fromNodeId: parentId,
|
|
2191
|
+
referenceName: unitName,
|
|
2192
|
+
referenceKind: 'imports',
|
|
2193
|
+
line: child.startPosition.row + 1,
|
|
2194
|
+
column: child.startPosition.column,
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Extract a Pascal constant declaration
|
|
2203
|
+
*/
|
|
2204
|
+
extractPascalConst(node) {
|
|
2205
|
+
const nameNode = getChildByField(node, 'name');
|
|
2206
|
+
if (!nameNode)
|
|
2207
|
+
return;
|
|
2208
|
+
const name = getNodeText(nameNode, this.source);
|
|
2209
|
+
const defaultValue = node.namedChildren.find((c) => c.type === 'defaultValue');
|
|
2210
|
+
const sig = defaultValue ? getNodeText(defaultValue, this.source) : undefined;
|
|
2211
|
+
this.createNode('constant', name, node, { signature: sig });
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Extract Pascal inheritance (extends/implements) from declClass typeref children
|
|
2215
|
+
*/
|
|
2216
|
+
extractPascalInheritance(declClass, classId) {
|
|
2217
|
+
const typerefs = declClass.namedChildren.filter((c) => c.type === 'typeref');
|
|
2218
|
+
for (let i = 0; i < typerefs.length; i++) {
|
|
2219
|
+
const ref = typerefs[i];
|
|
2220
|
+
const name = getNodeText(ref, this.source);
|
|
2221
|
+
this.unresolvedReferences.push({
|
|
2222
|
+
fromNodeId: classId,
|
|
2223
|
+
referenceName: name,
|
|
2224
|
+
referenceKind: i === 0 ? 'extends' : 'implements',
|
|
2225
|
+
line: ref.startPosition.row + 1,
|
|
2226
|
+
column: ref.startPosition.column,
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Extract calls and resolve method context from a Pascal defProc (implementation body).
|
|
2232
|
+
* Does not create a new node — the declaration was already captured from the interface section.
|
|
2233
|
+
*/
|
|
2234
|
+
extractPascalDefProc(node) {
|
|
2235
|
+
// Find the matching declaration node by name to use as call parent
|
|
2236
|
+
const declProc = node.namedChildren.find((c) => c.type === 'declProc');
|
|
2237
|
+
if (!declProc)
|
|
2238
|
+
return;
|
|
2239
|
+
const nameNode = getChildByField(declProc, 'name');
|
|
2240
|
+
if (!nameNode)
|
|
2241
|
+
return;
|
|
2242
|
+
const fullName = getNodeText(nameNode, this.source).trim();
|
|
2243
|
+
// fullName is like "TAuthService.Create"
|
|
2244
|
+
const shortName = fullName.includes('.') ? fullName.split('.').pop() : fullName;
|
|
2245
|
+
const fullNameKey = fullName.toLowerCase();
|
|
2246
|
+
const shortNameKey = shortName.toLowerCase();
|
|
2247
|
+
// Build method index on first use (O(n) once, then O(1) per lookup)
|
|
2248
|
+
if (!this.methodIndex) {
|
|
2249
|
+
this.methodIndex = new Map();
|
|
2250
|
+
for (const n of this.nodes) {
|
|
2251
|
+
if (n.kind === 'method' || n.kind === 'function') {
|
|
2252
|
+
const nameKey = n.name.toLowerCase();
|
|
2253
|
+
// Keep first seen short-name mapping to avoid silently overwriting earlier entries.
|
|
2254
|
+
if (!this.methodIndex.has(nameKey)) {
|
|
2255
|
+
this.methodIndex.set(nameKey, n.id);
|
|
2256
|
+
}
|
|
2257
|
+
// For Pascal methods, also index qualified forms (e.g. TAuthService.Create).
|
|
2258
|
+
if (n.kind === 'method') {
|
|
2259
|
+
const qualifiedParts = n.qualifiedName.split('::').slice(1); // drop file path
|
|
2260
|
+
if (qualifiedParts.length >= 2) {
|
|
2261
|
+
// Create suffix keys so both "Module.Class.Method" and "Class.Method" can resolve.
|
|
2262
|
+
for (let i = 0; i < qualifiedParts.length - 1; i++) {
|
|
2263
|
+
const scopedName = qualifiedParts.slice(i).join('.').toLowerCase();
|
|
2264
|
+
this.methodIndex.set(scopedName, n.id);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
const parentId = this.methodIndex.get(fullNameKey) ||
|
|
2272
|
+
this.methodIndex.get(shortNameKey) ||
|
|
2273
|
+
this.nodeStack[this.nodeStack.length - 1];
|
|
2274
|
+
if (!parentId)
|
|
2275
|
+
return;
|
|
2276
|
+
// Visit the block for calls
|
|
2277
|
+
const block = node.namedChildren.find((c) => c.type === 'block');
|
|
2278
|
+
if (block) {
|
|
2279
|
+
this.nodeStack.push(parentId);
|
|
2280
|
+
this.visitPascalBlock(block);
|
|
2281
|
+
this.nodeStack.pop();
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Extract function calls from a Pascal expression
|
|
2286
|
+
*/
|
|
2287
|
+
extractPascalCall(node) {
|
|
2288
|
+
if (this.nodeStack.length === 0)
|
|
2289
|
+
return;
|
|
2290
|
+
const callerId = this.nodeStack[this.nodeStack.length - 1];
|
|
2291
|
+
if (!callerId)
|
|
2292
|
+
return;
|
|
2293
|
+
// Get the callee name — first child is typically the identifier or exprDot
|
|
2294
|
+
const firstChild = node.namedChild(0);
|
|
2295
|
+
if (!firstChild)
|
|
2296
|
+
return;
|
|
2297
|
+
let calleeName = '';
|
|
2298
|
+
if (firstChild.type === 'exprDot') {
|
|
2299
|
+
// Qualified call: Obj.Method(...)
|
|
2300
|
+
const identifiers = firstChild.namedChildren.filter((c) => c.type === 'identifier');
|
|
2301
|
+
if (identifiers.length > 0) {
|
|
2302
|
+
calleeName = identifiers.map((id) => getNodeText(id, this.source)).join('.');
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
else if (firstChild.type === 'identifier') {
|
|
2306
|
+
calleeName = getNodeText(firstChild, this.source);
|
|
2307
|
+
}
|
|
2308
|
+
if (calleeName) {
|
|
2309
|
+
this.unresolvedReferences.push({
|
|
2310
|
+
fromNodeId: callerId,
|
|
2311
|
+
referenceName: calleeName,
|
|
2312
|
+
referenceKind: 'calls',
|
|
2313
|
+
line: node.startPosition.row + 1,
|
|
2314
|
+
column: node.startPosition.column,
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
// Also visit arguments for nested calls
|
|
2318
|
+
const args = node.namedChildren.find((c) => c.type === 'exprArgs');
|
|
2319
|
+
if (args) {
|
|
2320
|
+
this.visitPascalBlock(args);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
/**
|
|
2324
|
+
* Recursively visit a Pascal block/statement tree for call expressions
|
|
2325
|
+
*/
|
|
2326
|
+
visitPascalBlock(node) {
|
|
2327
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2328
|
+
const child = node.namedChild(i);
|
|
2329
|
+
if (!child)
|
|
2330
|
+
continue;
|
|
2331
|
+
if (child.type === 'exprCall') {
|
|
2332
|
+
this.extractPascalCall(child);
|
|
2333
|
+
}
|
|
2334
|
+
else if (child.type === 'exprDot') {
|
|
2335
|
+
// Check if exprDot contains an exprCall
|
|
2336
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
2337
|
+
const grandchild = child.namedChild(j);
|
|
2338
|
+
if (grandchild?.type === 'exprCall') {
|
|
2339
|
+
this.extractPascalCall(grandchild);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
else {
|
|
2344
|
+
this.visitPascalBlock(child);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
1916
2348
|
}
|
|
1917
2349
|
exports.TreeSitterExtractor = TreeSitterExtractor;
|
|
1918
2350
|
/**
|
|
@@ -2377,11 +2809,158 @@ class SvelteExtractor {
|
|
|
2377
2809
|
}
|
|
2378
2810
|
}
|
|
2379
2811
|
exports.SvelteExtractor = SvelteExtractor;
|
|
2812
|
+
/**
|
|
2813
|
+
* Custom extractor for Delphi DFM/FMX form files.
|
|
2814
|
+
*
|
|
2815
|
+
* DFM/FMX files describe the visual component hierarchy and event handler
|
|
2816
|
+
* bindings. They use a simple text format (object/end blocks) that we parse
|
|
2817
|
+
* with regex — no tree-sitter grammar exists for this format.
|
|
2818
|
+
*
|
|
2819
|
+
* Extracted information:
|
|
2820
|
+
* - Components as NodeKind `component`
|
|
2821
|
+
* - Nesting as EdgeKind `contains`
|
|
2822
|
+
* - Event handlers (OnClick = MethodName) as UnresolvedReference → EdgeKind `references`
|
|
2823
|
+
*/
|
|
2824
|
+
class DfmExtractor {
|
|
2825
|
+
filePath;
|
|
2826
|
+
source;
|
|
2827
|
+
nodes = [];
|
|
2828
|
+
edges = [];
|
|
2829
|
+
unresolvedReferences = [];
|
|
2830
|
+
errors = [];
|
|
2831
|
+
constructor(filePath, source) {
|
|
2832
|
+
this.filePath = filePath;
|
|
2833
|
+
this.source = source;
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* Extract components and event handler references from DFM/FMX source
|
|
2837
|
+
*/
|
|
2838
|
+
extract() {
|
|
2839
|
+
const startTime = Date.now();
|
|
2840
|
+
try {
|
|
2841
|
+
const fileNode = this.createFileNode();
|
|
2842
|
+
this.parseComponents(fileNode.id);
|
|
2843
|
+
}
|
|
2844
|
+
catch (error) {
|
|
2845
|
+
(0, sentry_1.captureException)(error, { operation: 'dfm-extraction', filePath: this.filePath });
|
|
2846
|
+
this.errors.push({
|
|
2847
|
+
message: `DFM extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2848
|
+
severity: 'error',
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
return {
|
|
2852
|
+
nodes: this.nodes,
|
|
2853
|
+
edges: this.edges,
|
|
2854
|
+
unresolvedReferences: this.unresolvedReferences,
|
|
2855
|
+
errors: this.errors,
|
|
2856
|
+
durationMs: Date.now() - startTime,
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
/** Create a file node for the DFM form file */
|
|
2860
|
+
createFileNode() {
|
|
2861
|
+
const lines = this.source.split('\n');
|
|
2862
|
+
const id = generateNodeId(this.filePath, 'file', this.filePath, 1);
|
|
2863
|
+
const fileNode = {
|
|
2864
|
+
id,
|
|
2865
|
+
kind: 'file',
|
|
2866
|
+
name: this.filePath.split('/').pop() || this.filePath,
|
|
2867
|
+
qualifiedName: this.filePath,
|
|
2868
|
+
filePath: this.filePath,
|
|
2869
|
+
language: 'pascal',
|
|
2870
|
+
startLine: 1,
|
|
2871
|
+
endLine: lines.length,
|
|
2872
|
+
startColumn: 0,
|
|
2873
|
+
endColumn: lines[lines.length - 1]?.length || 0,
|
|
2874
|
+
updatedAt: Date.now(),
|
|
2875
|
+
};
|
|
2876
|
+
this.nodes.push(fileNode);
|
|
2877
|
+
return fileNode;
|
|
2878
|
+
}
|
|
2879
|
+
/** Parse object/end blocks and extract components + event handlers */
|
|
2880
|
+
parseComponents(fileNodeId) {
|
|
2881
|
+
const lines = this.source.split('\n');
|
|
2882
|
+
const stack = [fileNodeId];
|
|
2883
|
+
const objectPattern = /^\s*(object|inherited|inline)\s+(\w+)\s*:\s*(\w+)/;
|
|
2884
|
+
const eventPattern = /^\s*(On\w+)\s*=\s*(\w+)\s*$/;
|
|
2885
|
+
const endPattern = /^\s*end\s*$/;
|
|
2886
|
+
const multiLineStart = /=\s*\(\s*$/;
|
|
2887
|
+
const multiLineItemStart = /=\s*<\s*$/;
|
|
2888
|
+
let inMultiLine = false;
|
|
2889
|
+
let multiLineEndChar = ')';
|
|
2890
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2891
|
+
const line = lines[i];
|
|
2892
|
+
const lineNum = i + 1;
|
|
2893
|
+
// Skip multi-line properties
|
|
2894
|
+
if (inMultiLine) {
|
|
2895
|
+
if (line.trimEnd().endsWith(multiLineEndChar))
|
|
2896
|
+
inMultiLine = false;
|
|
2897
|
+
continue;
|
|
2898
|
+
}
|
|
2899
|
+
if (multiLineStart.test(line)) {
|
|
2900
|
+
inMultiLine = true;
|
|
2901
|
+
multiLineEndChar = ')';
|
|
2902
|
+
continue;
|
|
2903
|
+
}
|
|
2904
|
+
if (multiLineItemStart.test(line)) {
|
|
2905
|
+
inMultiLine = true;
|
|
2906
|
+
multiLineEndChar = '>';
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
// Component declaration
|
|
2910
|
+
const objMatch = line.match(objectPattern);
|
|
2911
|
+
if (objMatch) {
|
|
2912
|
+
const [, , name, typeName] = objMatch;
|
|
2913
|
+
const nodeId = generateNodeId(this.filePath, 'component', name, lineNum);
|
|
2914
|
+
this.nodes.push({
|
|
2915
|
+
id: nodeId,
|
|
2916
|
+
kind: 'component',
|
|
2917
|
+
name: name,
|
|
2918
|
+
qualifiedName: `${this.filePath}#${name}`,
|
|
2919
|
+
filePath: this.filePath,
|
|
2920
|
+
language: 'pascal',
|
|
2921
|
+
startLine: lineNum,
|
|
2922
|
+
endLine: lineNum,
|
|
2923
|
+
startColumn: 0,
|
|
2924
|
+
endColumn: line.length,
|
|
2925
|
+
signature: typeName,
|
|
2926
|
+
updatedAt: Date.now(),
|
|
2927
|
+
});
|
|
2928
|
+
this.edges.push({
|
|
2929
|
+
source: stack[stack.length - 1],
|
|
2930
|
+
target: nodeId,
|
|
2931
|
+
kind: 'contains',
|
|
2932
|
+
});
|
|
2933
|
+
stack.push(nodeId);
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2936
|
+
// Event handler
|
|
2937
|
+
const eventMatch = line.match(eventPattern);
|
|
2938
|
+
if (eventMatch) {
|
|
2939
|
+
const [, , methodName] = eventMatch;
|
|
2940
|
+
this.unresolvedReferences.push({
|
|
2941
|
+
fromNodeId: stack[stack.length - 1],
|
|
2942
|
+
referenceName: methodName,
|
|
2943
|
+
referenceKind: 'references',
|
|
2944
|
+
line: lineNum,
|
|
2945
|
+
column: 0,
|
|
2946
|
+
});
|
|
2947
|
+
continue;
|
|
2948
|
+
}
|
|
2949
|
+
// Block end
|
|
2950
|
+
if (endPattern.test(line)) {
|
|
2951
|
+
if (stack.length > 1)
|
|
2952
|
+
stack.pop();
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
exports.DfmExtractor = DfmExtractor;
|
|
2380
2958
|
/**
|
|
2381
2959
|
* Extract nodes and edges from source code
|
|
2382
2960
|
*/
|
|
2383
2961
|
function extractFromSource(filePath, source, language) {
|
|
2384
2962
|
const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
|
|
2963
|
+
const fileExtension = path.extname(filePath).toLowerCase();
|
|
2385
2964
|
// Use custom extractor for Svelte
|
|
2386
2965
|
if (detectedLanguage === 'svelte') {
|
|
2387
2966
|
const extractor = new SvelteExtractor(filePath, source);
|
|
@@ -2392,6 +2971,12 @@ function extractFromSource(filePath, source, language) {
|
|
|
2392
2971
|
const extractor = new LiquidExtractor(filePath, source);
|
|
2393
2972
|
return extractor.extract();
|
|
2394
2973
|
}
|
|
2974
|
+
// Use custom extractor for DFM/FMX form files
|
|
2975
|
+
if (detectedLanguage === 'pascal' &&
|
|
2976
|
+
(fileExtension === '.dfm' || fileExtension === '.fmx')) {
|
|
2977
|
+
const extractor = new DfmExtractor(filePath, source);
|
|
2978
|
+
return extractor.extract();
|
|
2979
|
+
}
|
|
2395
2980
|
const extractor = new TreeSitterExtractor(filePath, source, detectedLanguage);
|
|
2396
2981
|
return extractor.extract();
|
|
2397
2982
|
}
|