Skip to content

Commit e0afb07

Browse files
committed
add php parser
1 parent 9e58f78 commit e0afb07

12 files changed

+850
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules
2+
out/*

package-lock.json

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"author": "Mehedi Hassan <[email protected]>",
88
"license": "SEE LICENSE IN LICENSE",
99
"engines": {
10-
"vscode": "^1.15.0"
10+
"vscode": "^1.34.0"
1111
},
1212
"categories": [
1313
"Other"
@@ -33,7 +33,7 @@
3333
"onCommand:namespaceResolver.highlightNotUsed",
3434
"onCommand:namespaceResolver.generateNamespace"
3535
],
36-
"main": "./src/extension",
36+
"main": "./out/extension.js",
3737
"icon": "images/icon.png",
3838
"contributes": {
3939
"menus": {
@@ -208,13 +208,13 @@
208208
"bugs": {
209209
"url": "https://github.com/MehediDracula/PHP-Namespace-Resolver/issues"
210210
},
211-
"scripts": {
212-
"postinstall": "node ./node_modules/vscode/bin/install"
213-
},
214211
"devDependencies": {
215-
"vscode": "^1.0.0"
212+
"@types/node": "^8.10.25",
213+
"@types/vscode": "^1.34.0",
214+
"typescript": "^3.5.1"
216215
},
217216
"dependencies": {
218-
"node-natural-sort": "^0.8.6"
217+
"node-natural-sort": "^0.8.6",
218+
"php-parser": "^3.0.0-prerelease.8"
219219
}
220220
}

src/ClassInfo.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Position } from 'vscode';
2+
import { Location } from './Location';
3+
export class ClassInfo {
4+
public name: string;
5+
public location: Location;
6+
constructor(parsedClassObject: any) {
7+
this.name = parsedClassObject.name;
8+
this.location = parsedClassObject.loc;
9+
}
10+
get startPosition(): Position {
11+
return new Position(this.location.start.line - 1, this.location.start.column);
12+
}
13+
get endPosition(): Position {
14+
return new Position(this.location.end.line - 1, this.location.end.column);
15+
}
16+
get baseName(): string {
17+
return this.name.split('\\').pop();
18+
}
19+
public hasSameBaseName(classInfo: ClassInfo): boolean {
20+
return this.baseName === classInfo.baseName;
21+
}
22+
}

src/Location.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export type Location = {
2+
start: {
3+
line: number;
4+
column: number;
5+
offset: number;
6+
};
7+
end: {
8+
line: number;
9+
column: number;
10+
offset: number;
11+
};
12+
};

src/ParsedResult.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ClassInfo } from "./ClassInfo";
2+
export type ParsedResult = {
3+
useStatements: ClassInfo[];
4+
classesUsed: ClassInfo[];
5+
};

src/Parser.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import phpParser from 'php-parser';
2+
import { ParsedResult } from './ParsedResult';
3+
import { ClassInfo } from './ClassInfo';
4+
5+
6+
export class Parser {
7+
private text: string;
8+
private parser: any;
9+
public constructor(text: string) {
10+
this.text = text;
11+
12+
this.parser = new phpParser({
13+
parser: {
14+
extractDoc: false,
15+
php7: true
16+
},
17+
ast: {
18+
withPositions: true
19+
}
20+
});
21+
}
22+
23+
public parse(): ParsedResult {
24+
return this.getClasses();
25+
}
26+
27+
private getClasses(): ParsedResult {
28+
let parsedCode = this.parser.parseCode(this.text, '');
29+
30+
return {
31+
useStatements: this.getUseStatements(parsedCode),
32+
classesUsed: this.getClassesInBody(parsedCode)
33+
};
34+
}
35+
36+
private getClassesInBody(parsedCode: object): ClassInfo[] {
37+
let allClasses = [];
38+
this.getBodyElements(parsedCode).forEach(row => {
39+
if (this.isObject(row)) {
40+
allClasses.push(...this.getClassesForObject(row));
41+
}
42+
});
43+
44+
allClasses = this.filterOutFunctions(allClasses);
45+
46+
return allClasses.concat(this.getExtendedClasses(parsedCode));
47+
}
48+
49+
private getExtendedClasses(parsedCode: any): ClassInfo[] {
50+
51+
const classBody = this.getClass(parsedCode);
52+
if (!classBody) {
53+
return [];
54+
}
55+
const classes = [];
56+
57+
if ((classBody.extends)) {
58+
classes.push(new ClassInfo(classBody.extends));
59+
}
60+
61+
if (Array.isArray(classBody.implements)) {
62+
classes.push(...classBody.implements.map(row => {
63+
return new ClassInfo(row);
64+
}));
65+
}
66+
return classes;
67+
}
68+
69+
private filterOutFunctions(classes: ClassInfo[]): ClassInfo[] {
70+
return classes.filter(row => {
71+
const charAfterClass = this.text.substring(row.location.end.offset, row.location.end.offset + 1);
72+
if (charAfterClass === '(') {
73+
const charsBeforeClass = this.text.substring(row.location.start.offset - 4, row.location.start.offset);
74+
if (charsBeforeClass !== 'new ') {
75+
return false;
76+
}
77+
}
78+
return true;
79+
});
80+
}
81+
82+
private getClassesForObject(row: Record<string, any>): ClassInfo[] {
83+
const classes: ClassInfo[] = [];
84+
85+
Object.entries(row).forEach(([key, value]) => {
86+
if (key === 'kind' && value === 'classreference') {
87+
classes.push(new ClassInfo(row));
88+
} else if (Array.isArray(value)) {
89+
value.forEach(row => {
90+
if (this.isObject(row)) {
91+
classes.push(...this.getClassesForObject(row));
92+
}
93+
});
94+
} else if (this.isObject(value)) {
95+
classes.push(...this.getClassesForObject(value));
96+
}
97+
});
98+
return classes;
99+
}
100+
101+
private isObject(value: any) {
102+
if (value === null) {
103+
return false;
104+
}
105+
106+
return typeof value === 'object';
107+
}
108+
109+
private getElements(parsedCode: any): any[] {
110+
let children = parsedCode.children;
111+
112+
const nameSpaceObject = children.find(row => {
113+
return row.kind === 'namespace';
114+
});
115+
if (nameSpaceObject) {
116+
return nameSpaceObject.children;
117+
}
118+
return children;
119+
}
120+
121+
private getClass(parsedCode: object): any | null {
122+
const bodyType = this.getElements(parsedCode).find(row => {
123+
return row.kind === 'class';
124+
});
125+
126+
return bodyType;
127+
}
128+
129+
private getBodyElements(parsedCode: any): any[] {
130+
const classObject = this.getClass(parsedCode);
131+
132+
if (classObject) {
133+
return classObject.body;
134+
}
135+
136+
const returnType = parsedCode.children.find(row => {
137+
return row.kind === 'return';
138+
});
139+
140+
if (returnType) {
141+
return returnType.expr.items;
142+
}
143+
return [];
144+
}
145+
146+
private getUseStatements(parsedCode: object): ClassInfo[] {
147+
return this.getElements(parsedCode).flatMap(child => {
148+
if (child.kind === 'usegroup') {
149+
return child.items.map(item => {
150+
return new ClassInfo(item);
151+
});
152+
}
153+
return [];
154+
});
155+
}
156+
157+
}

0 commit comments

Comments
 (0)