feat: Add PHP inheritance extraction and improve method call handling

Addresses PHP's base_clause syntax for class inheritance (extends) and implements clause for interface implementation. Adds trait_declaration support and separates property_declaration into fieldTypes. Improves PHP method call extraction by handling member_call_expression and scoped_call_expression with proper receiver name processing, including $ prefix stripping and self/this/parent/static receiver filtering.
This commit is contained in:
Colby McHenry
2026-04-06 22:10:23 -05:00
parent 2d14503258
commit e848e6f22f
3 changed files with 49 additions and 8 deletions
+3 -2
View File
@@ -4,7 +4,7 @@ import type { LanguageExtractor } from '../tree-sitter-types';
export const phpExtractor: LanguageExtractor = {
functionTypes: ['function_definition'],
classTypes: ['class_declaration'],
classTypes: ['class_declaration', 'trait_declaration'],
methodTypes: ['method_declaration'],
interfaceTypes: ['interface_declaration'],
structTypes: [],
@@ -13,7 +13,8 @@ export const phpExtractor: LanguageExtractor = {
typeAliasTypes: [],
importTypes: ['namespace_use_declaration'],
callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
variableTypes: ['property_declaration', 'const_declaration'],
variableTypes: ['const_declaration'],
fieldTypes: ['property_declaration'],
nameField: 'name',
bodyField: 'body',
paramsField: 'parameters',
+15 -6
View File
@@ -1151,17 +1151,25 @@ export class TreeSitterExtractor {
let calleeName = '';
// Java/Kotlin method_invocation has 'object' + 'name' fields instead of 'function'
// PHP member_call_expression has 'object' + 'name', scoped_call_expression has 'scope' + 'name'
const nameField = getChildByField(node, 'name');
const objectField = getChildByField(node, 'object');
const objectField = getChildByField(node, 'object') || getChildByField(node, 'scope');
if (nameField && objectField && node.type === 'method_invocation') {
// Java-style method call: receiver.method()
if (nameField && objectField && (node.type === 'method_invocation' || node.type === 'member_call_expression' || node.type === 'scoped_call_expression')) {
// Method call with explicit receiver: receiver.method() / $receiver->method() / ClassName::method()
const methodName = getNodeText(nameField, this.source);
const receiverName = getNodeText(objectField, this.source);
let receiverName = getNodeText(objectField, this.source);
// Strip PHP $ prefix from variable names
receiverName = receiverName.replace(/^\$/, '');
if (methodName) {
// Emit receiver.method form for qualified resolution
calleeName = `${receiverName}.${methodName}`;
// Skip self/this/parent/static receivers — they don't aid resolution
const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super', 'parent', 'static']);
if (SKIP_RECEIVERS.has(receiverName)) {
calleeName = methodName;
} else {
calleeName = `${receiverName}.${methodName}`;
}
}
} else {
const func = getChildByField(node, 'function') || node.namedChild(0);
@@ -1245,6 +1253,7 @@ export class TreeSitterExtractor {
if (
child.type === 'extends_clause' ||
child.type === 'superclass' ||
child.type === 'base_clause' || // PHP class extends
child.type === 'extends_interfaces' // Java interface extends
) {
// Extract parent class/interface names