Skip to content

Commit acc0d4a

Browse files
committed
Recursive include resolution
1 parent c0e3cc3 commit acc0d4a

File tree

3 files changed

+56
-24
lines changed

3 files changed

+56
-24
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe("PHP Incluse resolver", () => {
1212

1313
test("resolves use directives", () => {
1414
const imports = resolver.resolveImports(registree.registry.files.get(USE)!);
15-
expect(imports.unresolved.namespaces.length).toBe(1);
15+
expect(imports.unresolved.namespaces.size).toBe(1);
1616
expect(imports.unresolved.namespaces).toContainEqual("I\\Do\\Not\\Exist");
1717
expect(imports.resolved.get("f")).toBeDefined(); // nested.php
1818
expect(imports.resolved.get("my_function")).toBeDefined(); // learnphp function
@@ -24,7 +24,8 @@ describe("PHP Incluse resolver", () => {
2424
const imports = resolver.resolveImports(
2525
registree.registry.files.get(INCLUDE)!,
2626
);
27-
expect(imports.unresolved.paths.length).toBe(1);
27+
console.log(Array.from(imports.unresolved.paths.values()));
28+
expect(imports.unresolved.paths.size).toBe(4);
2829
expect(imports.unresolved.paths).toContainEqual("unresolved.php");
2930
expect(imports.resolved.get("f")).toBeDefined(); // nested.php
3031
expect(imports.resolved.get("defined_in_used_file")).toBeDefined(); // use.php

src/languagePlugins/php/incluseResolver/index.ts

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@ export class PHPIncluseResolver {
2626
...includeImports.resolved,
2727
]),
2828
unresolved: {
29-
paths: [
30-
...useImports.unresolved.paths,
31-
...includeImports.unresolved.paths,
32-
],
33-
namespaces: [
34-
...useImports.unresolved.namespaces,
35-
...includeImports.unresolved.namespaces,
36-
],
29+
paths: useImports.unresolved.paths.union(
30+
includeImports.unresolved.paths,
31+
),
32+
namespaces: useImports.unresolved.namespaces.union(
33+
includeImports.unresolved.namespaces,
34+
),
3735
},
3836
};
3937
this.imports.set(file.path, imports);
@@ -48,8 +46,8 @@ export class PHPIncluseResolver {
4846
const imports: PHPImports = {
4947
resolved: new Map(),
5048
unresolved: {
51-
paths: [],
52-
namespaces: [],
49+
paths: new Set(),
50+
namespaces: new Set(),
5351
},
5452
};
5553
for (const use of useDirectives) {
@@ -78,7 +76,7 @@ export class PHPIncluseResolver {
7876
}
7977
}
8078
} else if (name) {
81-
imports.unresolved.namespaces.push(name);
79+
imports.unresolved.namespaces.add(name);
8280
}
8381
}
8482
return imports;
@@ -102,32 +100,50 @@ export class PHPIncluseResolver {
102100
return [...leftArr, ...rightArr];
103101
}
104102

105-
#resolveIncludeDirectives(file: PHPFile) {
103+
#resolveIncludeDirectives(file: PHPFile, visitedFiles = new Set<string>()) {
106104
const includeDirectives = PHP_INCLUDE_QUERY.captures(file.rootNode);
107105
if (!includeDirectives) {
108106
throw new Error(`Error when parsing include directives for ${file.path}`);
109107
}
110108
const imports: PHPImports = {
111109
resolved: new Map(),
112110
unresolved: {
113-
paths: [],
114-
namespaces: [],
111+
paths: new Set(),
112+
namespaces: new Set(),
115113
},
116114
};
115+
116+
// Add the current file to the visited set to prevent infinite recursion
117+
visitedFiles.add(file.path);
118+
117119
for (const include of includeDirectives) {
118120
if (include.name === "includestr") {
119121
const path = include.node.text;
120122
const importedfile = this.registree.registry.getFile(path, file.path);
121-
if (importedfile) {
123+
if (importedfile && !visitedFiles.has(importedfile.path)) {
122124
for (const [k, v] of importedfile.symbols) {
123125
if (imports.resolved.has(k)) {
124126
imports.resolved.get(k)!.push(...v);
125127
} else {
126128
imports.resolved.set(k, v);
127129
}
128130
}
129-
} else {
130-
imports.unresolved.paths.push(path);
131+
const nestedImports = this.#resolveIncludeDirectives(
132+
importedfile,
133+
visitedFiles,
134+
);
135+
for (const [k, v] of nestedImports.resolved) {
136+
if (imports.resolved.has(k)) {
137+
imports.resolved.get(k)!.push(...v);
138+
} else {
139+
imports.resolved.set(k, v);
140+
}
141+
}
142+
imports.unresolved.paths = imports.unresolved.paths.union(
143+
nestedImports.unresolved.paths,
144+
);
145+
} else if (!importedfile) {
146+
imports.unresolved.paths.add(path);
131147
}
132148
} else if (include.name === "includebin") {
133149
const fileparts = this.#splitBinary(include.node)
@@ -139,19 +155,34 @@ export class PHPIncluseResolver {
139155
filepath,
140156
file.path,
141157
);
142-
if (importedfile) {
158+
if (importedfile && !visitedFiles.has(importedfile.path)) {
143159
for (const [k, v] of importedfile.symbols) {
144160
if (imports.resolved.has(k)) {
145161
imports.resolved.get(k)!.push(...v);
146162
} else {
147163
imports.resolved.set(k, v);
148164
}
149165
}
150-
} else {
151-
imports.unresolved.paths.push(filepath);
166+
const nestedImports = this.#resolveIncludeDirectives(
167+
importedfile,
168+
visitedFiles,
169+
);
170+
for (const [k, v] of nestedImports.resolved) {
171+
if (imports.resolved.has(k)) {
172+
imports.resolved.get(k)!.push(...v);
173+
} else {
174+
imports.resolved.set(k, v);
175+
}
176+
}
177+
imports.unresolved.paths = imports.unresolved.paths.union(
178+
nestedImports.unresolved.paths,
179+
);
180+
} else if (!importedfile) {
181+
imports.unresolved.paths.add(filepath);
152182
}
153183
}
154184
}
185+
155186
return imports;
156187
}
157188
}

src/languagePlugins/php/incluseResolver/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ExportedSymbol } from "../exportResolver/types.ts";
33
export interface PHPImports {
44
resolved: Map<string, ExportedSymbol[]>;
55
unresolved: {
6-
paths: string[];
7-
namespaces: string[];
6+
paths: Set<string>;
7+
namespaces: Set<string>;
88
};
99
}

0 commit comments

Comments
 (0)