forked from colbymchenry/codegraph
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathphp.ts
More file actions
105 lines (100 loc) · 4.17 KB
/
php.ts
File metadata and controls
105 lines (100 loc) · 4.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import type { Node as SyntaxNode } from 'web-tree-sitter';
import { getNodeText } from '../tree-sitter-helpers';
import type { LanguageExtractor } from '../tree-sitter-types';
export const phpExtractor: LanguageExtractor = {
functionTypes: ['function_definition'],
classTypes: ['class_declaration', 'trait_declaration'],
methodTypes: ['method_declaration'],
interfaceTypes: ['interface_declaration'],
structTypes: [],
enumTypes: ['enum_declaration'],
enumMemberTypes: ['enum_case'],
typeAliasTypes: [],
importTypes: ['namespace_use_declaration'],
callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
variableTypes: ['const_declaration'],
fieldTypes: ['property_declaration'],
nameField: 'name',
bodyField: 'body',
paramsField: 'parameters',
returnField: 'return_type',
classifyClassNode: (node) => {
return node.type === 'trait_declaration' ? 'trait' : 'class';
},
getVisibility: (node) => {
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i);
if (child?.type === 'visibility_modifier') {
const text = child.text;
if (text === 'public') return 'public';
if (text === 'private') return 'private';
if (text === 'protected') return 'protected';
}
}
return 'public'; // PHP defaults to public
},
isStatic: (node) => {
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i);
if (child?.type === 'static_modifier') return true;
}
return false;
},
visitNode: (node, ctx) => {
// Handle class constants: const_declaration inside classes
// These are skipped by the main visitor because variableTypes check excludes class-like contexts
if (node.type === 'const_declaration') {
const constElements = node.namedChildren.filter((c: SyntaxNode) => c.type === 'const_element');
for (const elem of constElements) {
const nameNode = elem.namedChildren.find((c: SyntaxNode) => c.type === 'name');
if (!nameNode) continue;
const name = getNodeText(nameNode, ctx.source);
ctx.createNode('constant', name, elem, {});
}
return true; // handled
}
// Handle trait usage: use TraitName, OtherTrait; inside classes
// Creates unresolved references that will be resolved to 'implements' edges
if (node.type === 'use_declaration') {
const names = node.namedChildren.filter((c: SyntaxNode) => c.type === 'name' || c.type === 'qualified_name');
const parentId = ctx.nodeStack.length > 0 ? ctx.nodeStack[ctx.nodeStack.length - 1] : undefined;
if (parentId) {
for (const nameNode of names) {
const traitName = getNodeText(nameNode, ctx.source);
ctx.addUnresolvedReference({
fromNodeId: parentId,
referenceName: traitName,
referenceKind: 'implements',
filePath: ctx.filePath,
line: node.startPosition.row + 1,
column: node.startPosition.column,
});
}
}
return true; // handled
}
return false;
},
extractImport: (node, source) => {
const importText = source.substring(node.startIndex, node.endIndex).trim();
// Check for grouped imports: use X\{A, B} - return null for core fallback
const namespacePrefix = node.namedChildren.find((c: SyntaxNode) => c.type === 'namespace_name');
const useGroup = node.namedChildren.find((c: SyntaxNode) => c.type === 'namespace_use_group');
if (namespacePrefix && useGroup) {
return null; // Grouped imports create multiple nodes - let core handle
}
// Single import - find namespace_use_clause
const useClause = node.namedChildren.find((c: SyntaxNode) => c.type === 'namespace_use_clause');
if (useClause) {
const qualifiedName = useClause.namedChildren.find((c: SyntaxNode) => c.type === 'qualified_name');
if (qualifiedName) {
return { moduleName: getNodeText(qualifiedName, source), signature: importText };
}
const name = useClause.namedChildren.find((c: SyntaxNode) => c.type === 'name');
if (name) {
return { moduleName: getNodeText(name, source), signature: importText };
}
}
return null;
},
};