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:
@@ -815,6 +815,37 @@ class UserController
|
|||||||
expect(classNode).toBeDefined();
|
expect(classNode).toBeDefined();
|
||||||
expect(classNode?.name).toBe('UserController');
|
expect(classNode?.name).toBe('UserController');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should extract class inheritance (extends) and interface implementation', () => {
|
||||||
|
const code = `<?php
|
||||||
|
|
||||||
|
class ChildController extends BaseController implements Serializable, JsonSerializable
|
||||||
|
{
|
||||||
|
public function serialize(): string
|
||||||
|
{
|
||||||
|
return json_encode($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const result = extractFromSource('ChildController.php', code);
|
||||||
|
|
||||||
|
const classNode = result.nodes.find((n) => n.kind === 'class');
|
||||||
|
expect(classNode).toBeDefined();
|
||||||
|
expect(classNode?.name).toBe('ChildController');
|
||||||
|
|
||||||
|
const extendsRef = result.unresolvedReferences.find(
|
||||||
|
(r) => r.referenceKind === 'extends'
|
||||||
|
);
|
||||||
|
expect(extendsRef).toBeDefined();
|
||||||
|
expect(extendsRef?.referenceName).toBe('BaseController');
|
||||||
|
|
||||||
|
const implementsRefs = result.unresolvedReferences.filter(
|
||||||
|
(r) => r.referenceKind === 'implements'
|
||||||
|
);
|
||||||
|
expect(implementsRefs.length).toBe(2);
|
||||||
|
expect(implementsRefs.map((r) => r.referenceName)).toContain('Serializable');
|
||||||
|
expect(implementsRefs.map((r) => r.referenceName)).toContain('JsonSerializable');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Swift Extraction', () => {
|
describe('Swift Extraction', () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { LanguageExtractor } from '../tree-sitter-types';
|
|||||||
|
|
||||||
export const phpExtractor: LanguageExtractor = {
|
export const phpExtractor: LanguageExtractor = {
|
||||||
functionTypes: ['function_definition'],
|
functionTypes: ['function_definition'],
|
||||||
classTypes: ['class_declaration'],
|
classTypes: ['class_declaration', 'trait_declaration'],
|
||||||
methodTypes: ['method_declaration'],
|
methodTypes: ['method_declaration'],
|
||||||
interfaceTypes: ['interface_declaration'],
|
interfaceTypes: ['interface_declaration'],
|
||||||
structTypes: [],
|
structTypes: [],
|
||||||
@@ -13,7 +13,8 @@ export const phpExtractor: LanguageExtractor = {
|
|||||||
typeAliasTypes: [],
|
typeAliasTypes: [],
|
||||||
importTypes: ['namespace_use_declaration'],
|
importTypes: ['namespace_use_declaration'],
|
||||||
callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
|
callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
|
||||||
variableTypes: ['property_declaration', 'const_declaration'],
|
variableTypes: ['const_declaration'],
|
||||||
|
fieldTypes: ['property_declaration'],
|
||||||
nameField: 'name',
|
nameField: 'name',
|
||||||
bodyField: 'body',
|
bodyField: 'body',
|
||||||
paramsField: 'parameters',
|
paramsField: 'parameters',
|
||||||
|
|||||||
@@ -1151,18 +1151,26 @@ export class TreeSitterExtractor {
|
|||||||
let calleeName = '';
|
let calleeName = '';
|
||||||
|
|
||||||
// Java/Kotlin method_invocation has 'object' + 'name' fields instead of 'function'
|
// 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 nameField = getChildByField(node, 'name');
|
||||||
const objectField = getChildByField(node, 'object');
|
const objectField = getChildByField(node, 'object') || getChildByField(node, 'scope');
|
||||||
|
|
||||||
if (nameField && objectField && node.type === 'method_invocation') {
|
if (nameField && objectField && (node.type === 'method_invocation' || node.type === 'member_call_expression' || node.type === 'scoped_call_expression')) {
|
||||||
// Java-style method call: receiver.method()
|
// Method call with explicit receiver: receiver.method() / $receiver->method() / ClassName::method()
|
||||||
const methodName = getNodeText(nameField, this.source);
|
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) {
|
if (methodName) {
|
||||||
// Emit receiver.method form for qualified resolution
|
// 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}`;
|
calleeName = `${receiverName}.${methodName}`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const func = getChildByField(node, 'function') || node.namedChild(0);
|
const func = getChildByField(node, 'function') || node.namedChild(0);
|
||||||
|
|
||||||
@@ -1245,6 +1253,7 @@ export class TreeSitterExtractor {
|
|||||||
if (
|
if (
|
||||||
child.type === 'extends_clause' ||
|
child.type === 'extends_clause' ||
|
||||||
child.type === 'superclass' ||
|
child.type === 'superclass' ||
|
||||||
|
child.type === 'base_clause' || // PHP class extends
|
||||||
child.type === 'extends_interfaces' // Java interface extends
|
child.type === 'extends_interfaces' // Java interface extends
|
||||||
) {
|
) {
|
||||||
// Extract parent class/interface names
|
// Extract parent class/interface names
|
||||||
|
|||||||
Reference in New Issue
Block a user