Skip to content

Commit 9d638fd

Browse files
committed
feat: index all namespaces
1 parent 09aff8b commit 9d638fd

File tree

4 files changed

+96
-76
lines changed

4 files changed

+96
-76
lines changed
Lines changed: 20 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,42 @@
11
import { Uri } from 'vscode';
22
import PhpNamespace from 'common/PhpNamespace';
3-
import FileSystem from 'util/FileSystem';
43
import { AbstractIndexData } from 'indexer/AbstractIndexData';
5-
import { AutoloadNamespaceData } from './types';
64
import { Memoize } from 'typescript-memoize';
75
import AutoloadNamespaceIndexer from './AutoloadNamespaceIndexer';
6+
import { Namespace } from './types';
87

9-
export class AutoloadNamespaceIndexData extends AbstractIndexData<AutoloadNamespaceData> {
8+
export class AutoloadNamespaceIndexData extends AbstractIndexData<Namespace[]> {
109
private static readonly SPECIAL_CLASSNAMES = ['Proxy', 'Factory'];
1110

1211
@Memoize({
1312
tags: [AutoloadNamespaceIndexer.KEY],
14-
hashFunction: (namespace: PhpNamespace) => namespace.toString(),
1513
})
16-
public async findClassByNamespace(namespace: PhpNamespace): Promise<Uri | undefined> {
17-
const parts = namespace.getParts();
18-
19-
for (let i = parts.length; i >= 0; i--) {
20-
const namespace = PhpNamespace.fromParts(parts.slice(0, i)).toString();
21-
22-
const directories = this.getDirectoriesByNamespace(namespace);
23-
24-
if (directories.length === 0) {
25-
continue;
26-
}
27-
28-
let className = parts.pop() as string;
29-
30-
if (AutoloadNamespaceIndexData.SPECIAL_CLASSNAMES.includes(className)) {
31-
className = parts.pop() as string;
32-
}
33-
34-
const classNamespace = PhpNamespace.fromParts(parts.slice(i)).append(className);
35-
36-
const directory = await this.findNamespaceDirectory(classNamespace, directories);
37-
38-
if (!directory) {
39-
continue;
40-
}
14+
public getNamespaces(): Namespace[] {
15+
return Array.from(this.data.values()).flat();
16+
}
4117

42-
const classPath = classNamespace.toString().replace(/\\/g, '/');
43-
const fileUri = Uri.joinPath(directory, `${classPath}.php`);
18+
@Memoize({
19+
tags: [AutoloadNamespaceIndexer.KEY],
20+
hashFunction: (namespace: PhpNamespace) => namespace.toString(),
21+
})
22+
public async findUriByNamespace(phpNamespace: PhpNamespace): Promise<Uri | undefined> {
23+
const namespaces = this.getNamespaces();
4424

45-
return fileUri;
25+
if (AutoloadNamespaceIndexData.SPECIAL_CLASSNAMES.includes(phpNamespace.getTail())) {
26+
phpNamespace.pop();
4627
}
4728

48-
return undefined;
49-
}
29+
const namespace = namespaces.find(n => n.fqn === phpNamespace.toString());
5030

51-
private getDirectoriesByNamespace(namespace: string): string[] {
52-
const namespaceData = this.getValues().filter(data => data[namespace] !== undefined);
53-
54-
if (!namespaceData) {
55-
return [];
31+
if (!namespace) {
32+
return undefined;
5633
}
5734

58-
return namespaceData.flatMap(data => data[namespace] ?? []);
35+
return Uri.file(namespace.path);
5936
}
6037

61-
private async findNamespaceDirectory(
62-
namespace: PhpNamespace,
63-
directories: string[]
64-
): Promise<Uri | undefined> {
65-
for (const directory of directories) {
66-
const directoryUri = Uri.file(directory);
67-
const classPath = namespace.toString().replace(/\\/g, '/');
68-
const fileUri = Uri.joinPath(directoryUri, `${classPath}.php`);
69-
const exists = await FileSystem.fileExists(fileUri);
70-
71-
if (exists) {
72-
return directoryUri;
73-
}
74-
}
75-
76-
return undefined;
38+
public findNamespacesByPrefix(prefix: string): Namespace[] {
39+
const namespaces = this.getNamespaces();
40+
return namespaces.filter(namespace => namespace.fqn.startsWith(prefix));
7741
}
7842
}
Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { RelativePattern, Uri } from 'vscode';
22
import { Indexer } from 'indexer/Indexer';
33
import FileSystem from 'util/FileSystem';
4-
import { AutoloadNamespaceData } from './types';
4+
import { Namespace } from './types';
55

6-
export default class AutoloadNamespaceIndexer extends Indexer<AutoloadNamespaceData> {
6+
export default class AutoloadNamespaceIndexer extends Indexer<Namespace[]> {
77
public static readonly KEY = 'autoloadNamespace';
88

99
public getId(): string {
@@ -18,7 +18,7 @@ export default class AutoloadNamespaceIndexer extends Indexer<AutoloadNamespaceD
1818
return new RelativePattern(uri, '**/composer.json');
1919
}
2020

21-
public async indexFile(uri: Uri): Promise<AutoloadNamespaceData | undefined> {
21+
public async indexFile(uri: Uri): Promise<Namespace[] | undefined> {
2222
const content = await FileSystem.readFile(uri);
2323
const composer = JSON.parse(content);
2424

@@ -27,34 +27,75 @@ export default class AutoloadNamespaceIndexer extends Indexer<AutoloadNamespaceD
2727
}
2828

2929
const baseDir = Uri.joinPath(uri, '..');
30-
const data: AutoloadNamespaceData = {};
30+
const data: Namespace[] = [];
3131

3232
// Handle PSR-4 autoloading
3333
if (composer.autoload['psr-4']) {
34-
for (const [namespace, paths] of Object.entries(composer.autoload['psr-4'])) {
35-
const directories = Array.isArray(paths) ? paths : [paths];
34+
const namespaces = await this.indexNamespaces(composer.autoload['psr-4'], baseDir);
3635

37-
data[this.normalizeNamespace(namespace)] = directories.map(
38-
(dir: string) => Uri.joinPath(baseDir, dir.replace(/^\.\//, '')).fsPath
39-
);
40-
}
36+
data.push(...namespaces);
4137
}
4238

43-
// Handle PSR-0 autoloading
39+
// // Handle PSR-0 autoloading
4440
if (composer.autoload['psr-0']) {
45-
for (const [namespace, paths] of Object.entries(composer.autoload['psr-0'])) {
46-
const directories = Array.isArray(paths) ? paths : [paths];
41+
const namespaces = await this.indexNamespaces(composer.autoload['psr-0'], baseDir);
4742

48-
data[this.normalizeNamespace(namespace)] = directories.map(
49-
(dir: string) => Uri.joinPath(baseDir, dir.replace(/^\.\//, '')).fsPath
50-
);
51-
}
43+
data.push(...namespaces);
5244
}
5345

5446
return data;
5547
}
5648

49+
private async indexNamespaces(
50+
autoLoadData: Record<string, string[]>,
51+
baseDir: Uri
52+
): Promise<Namespace[]> {
53+
const promises: Promise<Namespace[]>[] = [];
54+
55+
for (const [namespace, paths] of Object.entries(autoLoadData)) {
56+
const directories = Array.isArray(paths) ? paths : [paths];
57+
58+
for (const directory of directories) {
59+
promises.push(this.expandNamespaces(namespace, baseDir, directory));
60+
}
61+
}
62+
63+
const namespaces = await Promise.all(promises);
64+
return namespaces.flat();
65+
}
66+
67+
private async expandNamespaces(
68+
baseNamespace: string,
69+
baseDirectory: Uri,
70+
relativeBaseDirectory: string
71+
): Promise<Namespace[]> {
72+
const baseDirectoryUri = Uri.joinPath(baseDirectory, relativeBaseDirectory.replace(/\\$/, ''));
73+
const files = await FileSystem.readDirectoryRecursive(baseDirectoryUri);
74+
75+
return files
76+
.filter(file => file.endsWith('.php'))
77+
.filter(file => {
78+
const parts = file.split('/');
79+
const filename = parts[parts.length - 1];
80+
return filename.charAt(0) === filename.charAt(0).toUpperCase();
81+
})
82+
.map(file => {
83+
const namespace = file.replace('.php', '');
84+
85+
const fqn = this.normalizeNamespace(
86+
`${this.normalizeNamespace(baseNamespace)}\\${namespace.replace(/\//g, '\\')}`
87+
);
88+
89+
return {
90+
fqn,
91+
prefix: baseNamespace,
92+
baseDirectory: baseDirectoryUri.fsPath,
93+
path: Uri.joinPath(baseDirectoryUri, file).fsPath,
94+
};
95+
});
96+
}
97+
5798
private normalizeNamespace(namespace: string): string {
58-
return namespace.replace(/\\$/, '');
99+
return namespace.replace(/\\$/, '').replace(/^\\/, '');
59100
}
60101
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
export type AutoloadNamespaceData = Record<string, string[]>;
1+
export interface Namespace {
2+
fqn: string;
3+
prefix: string;
4+
baseDirectory: string;
5+
path: string;
6+
}

src/util/FileSystem.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Uri, workspace } from 'vscode';
1+
import { FileType, RelativePattern, Uri, workspace } from 'vscode';
22
import * as path from 'path';
33
import ExtensionState from 'common/ExtensionState';
44

@@ -20,6 +20,16 @@ export default class FileSystem {
2020
return content.toString();
2121
}
2222

23+
public static async readDirectory(uri: Uri): Promise<string[]> {
24+
const files = await workspace.fs.readDirectory(uri);
25+
return files.map(([name]) => name);
26+
}
27+
28+
public static async readDirectoryRecursive(uri: Uri): Promise<string[]> {
29+
const files = await workspace.findFiles(new RelativePattern(uri, '**/*.php'));
30+
return files.map(file => path.relative(uri.fsPath, file.fsPath));
31+
}
32+
2333
public static async writeFile(uri: Uri, content: string): Promise<void> {
2434
await workspace.fs.writeFile(uri, Buffer.from(content));
2535
}

0 commit comments

Comments
 (0)