Skip to content

Commit c0e3cc3

Browse files
authored
Feature/php incluse resolver (#168)
* Incluse resolver first shot * Update pom.xml * Update lint_test_compile.yml * Incluse resolver completed
1 parent dd035b9 commit c0e3cc3

File tree

10 files changed

+290
-3
lines changed

10 files changed

+290
-3
lines changed

.github/workflows/lint_test_compile.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
run: deno lint
2525

2626
- name: Deno fmt (check)
27-
run: deno fmt --check --ignore=examples/java/websocket/**/*.html
27+
run: deno fmt --check --ignore=examples/**
2828

2929
tests:
3030
runs-on: ubuntu-latest

examples/java/websocket/pom.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<project
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
6+
>
37
<modelVersion>4.0.0</modelVersion>
48
<artifactId>websocket</artifactId>
59
<parent>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, test } from "@std/testing/bdd";
2+
import { expect } from "@std/expect";
3+
import { getPHPFilesMap } from "../testFiles/index.ts";
4+
import { PHPRegistree } from "../registree/index.ts";
5+
import { PHPIncluseResolver } from "./index.ts";
6+
import { INCLUDE, USE } from "../testFiles/constants.ts";
7+
8+
describe("PHP Incluse resolver", () => {
9+
const files = getPHPFilesMap();
10+
const registree = new PHPRegistree(files);
11+
const resolver = new PHPIncluseResolver(registree);
12+
13+
test("resolves use directives", () => {
14+
const imports = resolver.resolveImports(registree.registry.files.get(USE)!);
15+
expect(imports.unresolved.namespaces.length).toBe(1);
16+
expect(imports.unresolved.namespaces).toContainEqual("I\\Do\\Not\\Exist");
17+
expect(imports.resolved.get("f")).toBeDefined(); // nested.php
18+
expect(imports.resolved.get("my_function")).toBeDefined(); // learnphp function
19+
expect(imports.resolved.get("MyClass")).toBeDefined(); // learnphp class
20+
expect(imports.resolved.get("wheels")).toBeDefined(); // leanrphp variable
21+
});
22+
23+
test("resolves include directives", () => {
24+
const imports = resolver.resolveImports(
25+
registree.registry.files.get(INCLUDE)!,
26+
);
27+
expect(imports.unresolved.paths.length).toBe(1);
28+
expect(imports.unresolved.paths).toContainEqual("unresolved.php");
29+
expect(imports.resolved.get("f")).toBeDefined(); // nested.php
30+
expect(imports.resolved.get("defined_in_used_file")).toBeDefined(); // use.php
31+
expect(imports.resolved.get("my_function")).toBeDefined(); // learnphp function
32+
expect(imports.resolved.get("MyClass")).toBeDefined(); // learnphp class
33+
expect(imports.resolved.get("wheels")).toBeDefined(); // leanrphp variable
34+
});
35+
});
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import type { PHPRegistree } from "../registree/index.ts";
2+
import { type PHPFile, type PHPNode, SymbolNode } from "../registree/types.ts";
3+
import type { PHPImports } from "./types.ts";
4+
import { PHP_INCLUDE_QUERY, PHP_USE_DETECTION_QUERY } from "./queries.ts";
5+
import type Parser from "tree-sitter";
6+
import { dirname, join } from "@std/path";
7+
8+
export class PHPIncluseResolver {
9+
registree: PHPRegistree;
10+
imports: Map<string, PHPImports>;
11+
12+
constructor(registree: PHPRegistree) {
13+
this.registree = registree;
14+
this.imports = new Map();
15+
}
16+
17+
resolveImports(file: PHPFile) {
18+
if (this.imports.has(file.path)) {
19+
return this.imports.get(file.path)!;
20+
}
21+
const useImports = this.#resolveUseDirectives(file);
22+
const includeImports = this.#resolveIncludeDirectives(file);
23+
const imports: PHPImports = {
24+
resolved: new Map([
25+
...useImports.resolved,
26+
...includeImports.resolved,
27+
]),
28+
unresolved: {
29+
paths: [
30+
...useImports.unresolved.paths,
31+
...includeImports.unresolved.paths,
32+
],
33+
namespaces: [
34+
...useImports.unresolved.namespaces,
35+
...includeImports.unresolved.namespaces,
36+
],
37+
},
38+
};
39+
this.imports.set(file.path, imports);
40+
return imports;
41+
}
42+
43+
#resolveUseDirectives(file: PHPFile): PHPImports {
44+
const useDirectives = PHP_USE_DETECTION_QUERY.captures(file.rootNode);
45+
if (!useDirectives) {
46+
throw new Error(`Error when paring use directives for ${file.path}`);
47+
}
48+
const imports: PHPImports = {
49+
resolved: new Map(),
50+
unresolved: {
51+
paths: [],
52+
namespaces: [],
53+
},
54+
};
55+
for (const use of useDirectives) {
56+
let name: string | undefined = undefined;
57+
let node: PHPNode | undefined = undefined;
58+
for (const clause of use.node.namedChildren) {
59+
if (clause.type === "namespace_aliasing_clause") {
60+
name = clause.text;
61+
} else {
62+
node = this.registree.tree.findNode(clause.text);
63+
if (node && !name) {
64+
name = node.name;
65+
} else if (!node && !name) {
66+
name = clause.text;
67+
}
68+
}
69+
}
70+
if (node && name) {
71+
if (node instanceof SymbolNode) {
72+
imports.resolved.set(name, node.symbols);
73+
} else {
74+
for (const [k, v] of node.children) {
75+
if (v instanceof SymbolNode) {
76+
imports.resolved.set(k, v.symbols);
77+
}
78+
}
79+
}
80+
} else if (name) {
81+
imports.unresolved.namespaces.push(name);
82+
}
83+
}
84+
return imports;
85+
}
86+
87+
#splitBinary(binary: Parser.SyntaxNode): Parser.SyntaxNode[] {
88+
const left = binary.childForFieldName("left")!;
89+
const right = binary.childForFieldName("right")!;
90+
const leftArr = [];
91+
const rightArr = [];
92+
if (left.type === "binary_expression") {
93+
leftArr.push(...this.#splitBinary(left));
94+
} else {
95+
leftArr.push(left);
96+
}
97+
if (right.type === "binary_expression") {
98+
rightArr.push(...this.#splitBinary(right));
99+
} else {
100+
rightArr.push(right);
101+
}
102+
return [...leftArr, ...rightArr];
103+
}
104+
105+
#resolveIncludeDirectives(file: PHPFile) {
106+
const includeDirectives = PHP_INCLUDE_QUERY.captures(file.rootNode);
107+
if (!includeDirectives) {
108+
throw new Error(`Error when parsing include directives for ${file.path}`);
109+
}
110+
const imports: PHPImports = {
111+
resolved: new Map(),
112+
unresolved: {
113+
paths: [],
114+
namespaces: [],
115+
},
116+
};
117+
for (const include of includeDirectives) {
118+
if (include.name === "includestr") {
119+
const path = include.node.text;
120+
const importedfile = this.registree.registry.getFile(path, file.path);
121+
if (importedfile) {
122+
for (const [k, v] of importedfile.symbols) {
123+
if (imports.resolved.has(k)) {
124+
imports.resolved.get(k)!.push(...v);
125+
} else {
126+
imports.resolved.set(k, v);
127+
}
128+
}
129+
} else {
130+
imports.unresolved.paths.push(path);
131+
}
132+
} else if (include.name === "includebin") {
133+
const fileparts = this.#splitBinary(include.node)
134+
.map((n) => n.text === "__DIR__" ? dirname(file.path) : n.text)
135+
.filter((n) => n !== "")
136+
.map((n) => n.replace(/['"]/g, ""));
137+
const filepath = join(fileparts[0], ...fileparts.slice(1));
138+
const importedfile = this.registree.registry.getFile(
139+
filepath,
140+
file.path,
141+
);
142+
if (importedfile) {
143+
for (const [k, v] of importedfile.symbols) {
144+
if (imports.resolved.has(k)) {
145+
imports.resolved.get(k)!.push(...v);
146+
} else {
147+
imports.resolved.set(k, v);
148+
}
149+
}
150+
} else {
151+
imports.unresolved.paths.push(filepath);
152+
}
153+
}
154+
}
155+
return imports;
156+
}
157+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Parser from "tree-sitter";
2+
import { phpParser } from "../../../helpers/treeSitter/parsers.ts";
3+
4+
export const PHP_USE_DETECTION_QUERY = new Parser.Query(
5+
phpParser.getLanguage(),
6+
`
7+
(namespace_use_declaration
8+
(namespace_use_clause) @use
9+
)
10+
`,
11+
);
12+
13+
export const PHP_INCLUDE_QUERY = new Parser.Query(
14+
phpParser.getLanguage(),
15+
`
16+
(include_expression (string (string_content) @includestr))
17+
(include_once_expression (string (string_content) @includestr))
18+
(require_expression (string (string_content) @includestr))
19+
(require_once_expression (string (string_content) @includestr))
20+
21+
(include_expression (binary_expression) @includebin)
22+
(include_once_expression (binary_expression) @includebin)
23+
(require_expression (binary_expression) @includebin)
24+
(require_once_expression (binary_expression) @includebin)
25+
`,
26+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { ExportedSymbol } from "../exportResolver/types.ts";
2+
3+
export interface PHPImports {
4+
resolved: Map<string, ExportedSymbol[]>;
5+
unresolved: {
6+
paths: string[];
7+
namespaces: string[];
8+
};
9+
}

src/languagePlugins/php/registree/types.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
ExportedSymbol,
55
} from "../exportResolver/types.ts";
66
import { PHPExportResolver } from "../exportResolver/index.ts";
7+
import { dirname, join } from "@std/path";
78

89
export interface PHPNode {
910
name: string;
@@ -42,6 +43,29 @@ export class PHPTree extends NamespaceNode {
4243
}
4344
}
4445

46+
findNode(name: string): PHPNode | undefined {
47+
if (name === "") {
48+
return this;
49+
} else {
50+
const packageparts = name.split("\\").reverse();
51+
let part = packageparts.pop()!;
52+
if (part === "") {
53+
part = packageparts.pop()!;
54+
}
55+
let current = this.children.get(part);
56+
if (!current) {
57+
return undefined;
58+
}
59+
while (packageparts.length > 0) {
60+
if (!current) {
61+
return undefined;
62+
}
63+
current = current.children.get(packageparts.pop()!);
64+
}
65+
return current;
66+
}
67+
}
68+
4569
addNamespaces(namespaces: ExportedNamespace[]) {
4670
for (const ns of namespaces) {
4771
const nsparts = ns.name.split("\\");
@@ -106,4 +130,20 @@ export class PHPRegistry {
106130
symbols,
107131
});
108132
}
133+
134+
getFile(path: string, origin: string): PHPFile | undefined {
135+
const filepaths = Array.from(this.files.keys());
136+
// 1. Check current file's directory
137+
const sourceDir = dirname(origin);
138+
const pathFromRelative = join(sourceDir, path).replace(/\\/g, "/");
139+
const corresponding1 = filepaths.find((f) => f === pathFromRelative);
140+
if (corresponding1) {
141+
return this.files.get(corresponding1);
142+
}
143+
// 2. Check from workspace root
144+
const corresponding2 = filepaths.find((f) => f === path);
145+
if (corresponding2) {
146+
return this.files.get(corresponding2);
147+
}
148+
}
109149
}

src/languagePlugins/php/testFiles/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ import { join } from "@std/path";
33

44
export const LEARN_PHP = join(phpFilesFolder, "learnphp.php");
55
export const NESTED = join(phpFilesFolder, "nested.php");
6+
export const INCLUDE = join(phpFilesFolder, "include.php");
7+
export const USE = join(phpFilesFolder, "use.php");
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
include 'learnphp.php';
4+
include __DIR__ . '/nested.php';
5+
include __DIR__ . '/../phpFiles/use.php';
6+
include 'unresolved.php';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
namespace All\My;
3+
4+
$defined_in_used_file = 0;
5+
6+
use All\My\Fellas;
7+
use My\Namespace;
8+
use I\Do\Not\Exist;

0 commit comments

Comments
 (0)