Skip to content

Commit 7d6901a

Browse files
committed
Incluse resolver first shot
1 parent dd035b9 commit 7d6901a

File tree

6 files changed

+246
-0
lines changed

6 files changed

+246
-0
lines changed

src/languagePlugins/php/incluseResolver/index.test.ts

Whitespace-only changes.
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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 {
5+
PHP_INCLUDE_QUERY,
6+
PHP_USE_DETECTION_QUERY,
7+
PHP_USE_QUERY,
8+
} from "./queries.ts";
9+
import type Parser from "tree-sitter";
10+
import { dirname } from "@std/path";
11+
12+
export class PHPIncluseResolver {
13+
registree: PHPRegistree;
14+
imports: Map<string, PHPImports>;
15+
16+
constructor(registree: PHPRegistree) {
17+
this.registree = registree;
18+
this.imports = new Map();
19+
}
20+
21+
resolveImports(file: PHPFile) {
22+
if (this.imports.has(file.path)) {
23+
return this.imports.get(file.path)!;
24+
}
25+
const useImports = this.#resolveUseDirectives(file);
26+
const includeImports = this.#resolveIncludeDirectives(file);
27+
const imports: PHPImports = {
28+
resolved: new Map([
29+
...useImports.resolved,
30+
...includeImports.resolved,
31+
]),
32+
unresolved: {
33+
paths: [
34+
...useImports.unresolved.paths,
35+
...includeImports.unresolved.paths,
36+
],
37+
namespaces: [
38+
...useImports.unresolved.namespaces,
39+
...includeImports.unresolved.namespaces,
40+
],
41+
},
42+
};
43+
this.imports.set(file.path, imports);
44+
return imports;
45+
}
46+
47+
#resolveUseDirectives(file: PHPFile): PHPImports {
48+
const useDirectives = PHP_USE_DETECTION_QUERY.captures(file.rootNode);
49+
if (!useDirectives) {
50+
throw new Error(`Error when paring use directives for ${file.path}`);
51+
}
52+
const imports: PHPImports = {
53+
resolved: new Map(),
54+
unresolved: {
55+
paths: [],
56+
namespaces: [],
57+
},
58+
};
59+
for (const use of useDirectives) {
60+
const useClause = PHP_USE_QUERY.captures(use.node);
61+
if (!useClause) continue;
62+
let name: string | undefined = undefined;
63+
let node: PHPNode | undefined = undefined;
64+
for (const clause of useClause) {
65+
if (clause.name === "alias") {
66+
name = clause.node.text;
67+
} else {
68+
node = this.registree.tree.findNode(clause.node.text);
69+
if (node && !name) {
70+
name = node.name;
71+
} else if (!node && !name) {
72+
name = clause.node.text;
73+
}
74+
}
75+
}
76+
if (node && name) {
77+
if (node instanceof SymbolNode) {
78+
imports.resolved.set(name, node.symbols);
79+
} else {
80+
for (const [k, v] of node.children) {
81+
if (v instanceof SymbolNode) {
82+
imports.resolved.set(k, v.symbols);
83+
}
84+
}
85+
}
86+
} else if (name) {
87+
imports.unresolved.namespaces.push(name);
88+
}
89+
}
90+
return imports;
91+
}
92+
93+
#splitBinary(binary: Parser.SyntaxNode): Parser.SyntaxNode[] {
94+
const left = binary.childForFieldName("left")!;
95+
const right = binary.childForFieldName("right")!;
96+
const leftArr = [];
97+
const rightArr = [];
98+
if (left.type === "binary_expression") {
99+
leftArr.push(...this.#splitBinary(left));
100+
} else {
101+
leftArr.push(left);
102+
}
103+
if (right.type === "binary_expression") {
104+
rightArr.push(...this.#splitBinary(right));
105+
} else {
106+
rightArr.push(right);
107+
}
108+
return [...leftArr, ...rightArr];
109+
}
110+
111+
#resolveIncludeDirectives(file: PHPFile) {
112+
const includeDirectives = PHP_INCLUDE_QUERY.captures(file.rootNode);
113+
if (!includeDirectives) {
114+
throw new Error(`Error when parsing include directives for ${file.path}`);
115+
}
116+
const imports: PHPImports = {
117+
resolved: new Map(),
118+
unresolved: {
119+
paths: [],
120+
namespaces: [],
121+
},
122+
};
123+
for (const include of includeDirectives) {
124+
if (include.name === "includestr") {
125+
const path = include.node.text;
126+
const importedfile = this.registree.registry.getFile(path, file.path);
127+
if (importedfile) {
128+
for (const [k, v] of importedfile.symbols) {
129+
if (imports.resolved.has(k)) {
130+
imports.resolved.get(k)!.push(...v);
131+
} else {
132+
imports.resolved.set(k, v);
133+
}
134+
}
135+
} else {
136+
imports.unresolved.paths.push(path);
137+
}
138+
} else if (include.name === "includebin") {
139+
const filepath = this.#splitBinary(include.node)
140+
.map((n) => n.text === "__DIR__" ? dirname(file.path) : n.text)
141+
.filter((n) => n !== "")
142+
.map((n) => n.replace(/['"]/g, ""))
143+
.join("/");
144+
const importedfile = this.registree.registry.getFile(
145+
filepath,
146+
file.path,
147+
);
148+
if (importedfile) {
149+
for (const [k, v] of importedfile.symbols) {
150+
if (imports.resolved.has(k)) {
151+
imports.resolved.get(k)!.push(...v);
152+
} else {
153+
imports.resolved.set(k, v);
154+
}
155+
}
156+
} else {
157+
imports.unresolved.paths.push(filepath);
158+
}
159+
}
160+
}
161+
return imports;
162+
}
163+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
`,
9+
);
10+
11+
export const PHP_USE_QUERY = new Parser.Query(
12+
phpParser.getLanguage(),
13+
`
14+
(namespace_use_declaration
15+
(namespace_use_clause
16+
. (_) @ns (namespace_aliasing_clause (_) @alias)?
17+
))
18+
`,
19+
);
20+
21+
export const PHP_INCLUDE_QUERY = new Parser.Query(
22+
phpParser.getLanguage(),
23+
`
24+
(include_expression (string (string_content) @includestr))
25+
(include_once_expression (string (string_content) @includestr))
26+
(require_expression (string (string_content) @includestr))
27+
(require_once_expression (string (string_content) @includestr))
28+
29+
(include_expression (binary_expression) @includebin)
30+
(include_once_expression (binary_expression) @includebin)
31+
(require_expression (binary_expression) @includebin)
32+
(require_once_expression (binary_expression) @includebin)
33+
`,
34+
);
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: 32 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,21 @@ export class PHPTree extends NamespaceNode {
4243
}
4344
}
4445

46+
findNode(name: string): PHPNode | undefined {
47+
const parts = name.split("\\");
48+
let current: Map<string, PHPNode> = this.children;
49+
for (const part of parts) {
50+
if (part === "") {
51+
continue;
52+
}
53+
if (!current.has(part)) {
54+
return undefined;
55+
}
56+
current = current.get(part)!.children;
57+
}
58+
return current.get(parts[parts.length - 1]);
59+
}
60+
4561
addNamespaces(namespaces: ExportedNamespace[]) {
4662
for (const ns of namespaces) {
4763
const nsparts = ns.name.split("\\");
@@ -106,4 +122,20 @@ export class PHPRegistry {
106122
symbols,
107123
});
108124
}
125+
126+
getFile(path: string, origin: string): PHPFile | undefined {
127+
const filepaths = Array.from(this.files.keys());
128+
// 1. Check current file's directory
129+
const sourceDir = dirname(origin);
130+
const pathFromRelative = join(sourceDir, path).replace(/\\/g, "/");
131+
const corresponding1 = filepaths.find((f) => f === pathFromRelative);
132+
if (corresponding1) {
133+
return this.files.get(corresponding1);
134+
}
135+
// 2. Check from workspace root
136+
const corresponding2 = filepaths.find((f) => f === path);
137+
if (corresponding2) {
138+
return this.files.get(corresponding2);
139+
}
140+
}
109141
}
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+
use Fellas;
5+
use My\Namespace;
6+
7+
include 'learnphp.php';
8+
include __DIR__ . '/nested.php';

0 commit comments

Comments
 (0)