From 878320b78483b782e2d9eac0c83488608f1109b2 Mon Sep 17 00:00:00 2001 From: LeBoufty Date: Fri, 25 Jul 2025 11:21:34 +0200 Subject: [PATCH] Invocation resolver --- .../php/invocationResolver/index.test.ts | 26 +++++++ .../php/invocationResolver/index.ts | 73 +++++++++++++++++++ .../php/invocationResolver/queries.ts | 10 +++ .../php/invocationResolver/types.ts | 6 ++ .../php/testFiles/constants.ts | 1 + .../php/testFiles/phpFiles/invocations.php | 12 +++ 6 files changed, 128 insertions(+) create mode 100644 src/languagePlugins/php/invocationResolver/index.test.ts create mode 100644 src/languagePlugins/php/invocationResolver/index.ts create mode 100644 src/languagePlugins/php/invocationResolver/queries.ts create mode 100644 src/languagePlugins/php/invocationResolver/types.ts create mode 100644 src/languagePlugins/php/testFiles/phpFiles/invocations.php diff --git a/src/languagePlugins/php/invocationResolver/index.test.ts b/src/languagePlugins/php/invocationResolver/index.test.ts new file mode 100644 index 0000000..0d3570b --- /dev/null +++ b/src/languagePlugins/php/invocationResolver/index.test.ts @@ -0,0 +1,26 @@ +import { describe, test } from "@std/testing/bdd"; +import { expect } from "@std/expect"; +import { PHPInvocationResolver } from "./index.ts"; +import { PHPIncluseResolver } from "../incluseResolver/index.ts"; +import { PHPRegistree } from "../registree/index.ts"; +import { getPHPFilesMap } from "../testFiles/index.ts"; +import { INVOCATIONS } from "../testFiles/constants.ts"; + +describe("PHP Invocation resolver", () => { + const files = getPHPFilesMap(); + const registree = new PHPRegistree(files); + const incluseres = new PHPIncluseResolver(registree); + const resolver = new PHPInvocationResolver(incluseres); + + test("resolves invocations", () => { + const invocations = resolver.getInvocationsForFile(INVOCATIONS); + expect(invocations.unresolved.size >= 1).toBe(true); + expect(invocations.unresolved).toContainEqual("doesnotexistlmao"); + const resolved = Array.from(invocations.resolved.keys()); + expect(resolved).toContainEqual("array"); + expect(resolved).toContainEqual("my_function"); + expect(resolved).toContainEqual("a"); + expect(resolved).toContainEqual("MyClass"); + expect(resolved).toContainEqual("All\\My\\Fellas\\f"); + }); +}); diff --git a/src/languagePlugins/php/invocationResolver/index.ts b/src/languagePlugins/php/invocationResolver/index.ts new file mode 100644 index 0000000..b335cd3 --- /dev/null +++ b/src/languagePlugins/php/invocationResolver/index.ts @@ -0,0 +1,73 @@ +import type { Invocations } from "./types.ts"; +import { PHP_INVOCATION_QUERY } from "./queries.ts"; +import type { PHPIncluseResolver } from "../incluseResolver/index.ts"; +import type Parser from "tree-sitter"; +import type { ExportedSymbol } from "../exportResolver/types.ts"; +import { SymbolNode } from "../registree/types.ts"; + +export class PHPInvocationResolver { + incluseResolver: PHPIncluseResolver; + + constructor(incluseResolver: PHPIncluseResolver) { + this.incluseResolver = incluseResolver; + } + + getInvocationsForNode( + node: Parser.SyntaxNode, + filepath: string, + symbolname: string | undefined = undefined, + ): Invocations { + const currentfile = this.incluseResolver.registree.registry.files.get( + filepath, + )!; + const availableSymbols = this.incluseResolver + .resolveImports(currentfile)?.resolved; + const localSymbols = currentfile.symbols; + const unresolved = new Set(); + const resolved = new Map(); + const captures = PHP_INVOCATION_QUERY.captures(node); + for (const capture of captures) { + const name = capture.node.text; + // if the symbol name is the same as the one we are looking at, skip it + if (symbolname && name === symbolname) { + continue; + } + if (availableSymbols && availableSymbols.has(name)) { + const availableSymbol = availableSymbols.get(name); + if (!availableSymbol) { + unresolved.add(name); + continue; + } + resolved.set(name, availableSymbol); + } else if (localSymbols && localSymbols.has(name)) { + const localSymbol = localSymbols.get(name); + if (!localSymbol) { + unresolved.add(name); + continue; + } + resolved.set(name, localSymbol); + } else if (capture.name === "qualified") { + const qualSymbol = this.incluseResolver.registree.tree.findNode(name); + if (qualSymbol && qualSymbol instanceof SymbolNode) { + resolved.set(name, qualSymbol.symbols); + } else { + unresolved.add(name); + } + } else { + unresolved.add(name); + } + } + return { + resolved, + unresolved, + }; + } + + getInvocationsForFile(filepath: string): Invocations { + const file = this.incluseResolver.registree.registry.files.get(filepath); + if (!file) { + throw new Error(`File not found: ${filepath}`); + } + return this.getInvocationsForNode(file.rootNode, file.path); + } +} diff --git a/src/languagePlugins/php/invocationResolver/queries.ts b/src/languagePlugins/php/invocationResolver/queries.ts new file mode 100644 index 0000000..36e6e29 --- /dev/null +++ b/src/languagePlugins/php/invocationResolver/queries.ts @@ -0,0 +1,10 @@ +import Parser from "tree-sitter"; +import { phpParser } from "../../../helpers/treeSitter/parsers.ts"; + +export const PHP_INVOCATION_QUERY = new Parser.Query( + phpParser.getLanguage(), + ` + (name) @name + (qualified_name) @qualified + `, +); diff --git a/src/languagePlugins/php/invocationResolver/types.ts b/src/languagePlugins/php/invocationResolver/types.ts new file mode 100644 index 0000000..37ff822 --- /dev/null +++ b/src/languagePlugins/php/invocationResolver/types.ts @@ -0,0 +1,6 @@ +import type { ExportedSymbol } from "../exportResolver/types.ts"; + +export interface Invocations { + resolved: Map; + unresolved: Set; +} diff --git a/src/languagePlugins/php/testFiles/constants.ts b/src/languagePlugins/php/testFiles/constants.ts index 7690e1b..496eade 100644 --- a/src/languagePlugins/php/testFiles/constants.ts +++ b/src/languagePlugins/php/testFiles/constants.ts @@ -5,3 +5,4 @@ export const LEARN_PHP = join(phpFilesFolder, "learnphp.php"); export const NESTED = join(phpFilesFolder, "nested.php"); export const INCLUDE = join(phpFilesFolder, "include.php"); export const USE = join(phpFilesFolder, "use.php"); +export const INVOCATIONS = join(phpFilesFolder, "invocations.php"); diff --git a/src/languagePlugins/php/testFiles/phpFiles/invocations.php b/src/languagePlugins/php/testFiles/phpFiles/invocations.php new file mode 100644 index 0000000..8bd55d7 --- /dev/null +++ b/src/languagePlugins/php/testFiles/phpFiles/invocations.php @@ -0,0 +1,12 @@ +