@ant-yasa/uast-parser-php 0.2.10 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +0 -1
- package/dist/src/parser.js +147 -11
- package/dist/src/parser.js.map +1 -1
- package/package.json +1 -2
- package/src/index.ts +0 -26
- package/src/parser.ts +0 -1234
- package/tests/benchmark/base/advanced.php +0 -20
- package/tests/benchmark/base/advanced.php.json +0 -1269
- package/tests/benchmark/base/basic.php +0 -3
- package/tests/benchmark/base/basic.php.json +0 -292
- package/tests/benchmark/base/closure-class.php +0 -24
- package/tests/benchmark/base/closure-class.php.json +0 -1623
- package/tests/benchmark/base/control-flow.php +0 -37
- package/tests/benchmark/base/control-flow.php.json +0 -2010
- package/tests/benchmark/base/enum-trait-adapt.php +0 -31
- package/tests/benchmark/base/enum-trait-adapt.php.json +0 -965
- package/tests/benchmark/base/exprs.php +0 -4
- package/tests/benchmark/base/exprs.php.json +0 -811
- package/tests/benchmark/base/flow-extras.php +0 -12
- package/tests/benchmark/base/flow-extras.php.json +0 -897
- package/tests/benchmark/base/function.php +0 -7
- package/tests/benchmark/base/function.php.json +0 -536
- package/tests/benchmark/base/modern-php.php +0 -13
- package/tests/benchmark/base/modern-php.php.json +0 -508
- package/tests/benchmark/base/namespace.php +0 -5
- package/tests/benchmark/base/namespace.php.json +0 -307
- package/tests/benchmark/base/new-static.php +0 -3
- package/tests/benchmark/base/new-static.php.json +0 -399
- package/tests/benchmark/base/runtime.php +0 -13
- package/tests/benchmark/base/runtime.php.json +0 -1095
- package/tests/benchmark/base/strings-casts.php +0 -17
- package/tests/benchmark/base/strings-casts.php.json +0 -1357
- package/tests/benchmark/base/structures.php +0 -26
- package/tests/benchmark/base/structures.php.json +0 -1582
- package/tests/index.ts +0 -87
package/src/parser.ts
DELETED
|
@@ -1,1234 +0,0 @@
|
|
|
1
|
-
import * as TreeSitter from 'web-tree-sitter';
|
|
2
|
-
import * as UAST from '@ant-yasa/uast-spec';
|
|
3
|
-
import { version } from '../package.json';
|
|
4
|
-
|
|
5
|
-
type SyntaxNode = TreeSitter.Node;
|
|
6
|
-
|
|
7
|
-
export type ParseResult<Result> = Result;
|
|
8
|
-
|
|
9
|
-
let parserInstance: TreeSitter.Parser | null = null;
|
|
10
|
-
|
|
11
|
-
/** 初始化 tree-sitter parser,加载 PHP WASM */
|
|
12
|
-
export async function init(): Promise<void> {
|
|
13
|
-
await TreeSitter.Parser.init();
|
|
14
|
-
const Lang = await TreeSitter.Language.load(
|
|
15
|
-
require.resolve('tree-sitter-php/tree-sitter-php.wasm')
|
|
16
|
-
);
|
|
17
|
-
parserInstance = new TreeSitter.Parser();
|
|
18
|
-
parserInstance.setLanguage(Lang);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getParser(): TreeSitter.Parser {
|
|
22
|
-
if (!parserInstance) {
|
|
23
|
-
throw new Error('Parser not initialized. Call init() first.');
|
|
24
|
-
}
|
|
25
|
-
return parserInstance;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function toSourceLocation(node: SyntaxNode, sourcefile?: string) {
|
|
29
|
-
return {
|
|
30
|
-
start: {
|
|
31
|
-
line: node.startPosition.row + 1,
|
|
32
|
-
column: node.startPosition.column + 1,
|
|
33
|
-
},
|
|
34
|
-
end: {
|
|
35
|
-
line: node.endPosition.row + 1,
|
|
36
|
-
column: node.endPosition.column + 1,
|
|
37
|
-
},
|
|
38
|
-
sourcefile,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function appendNodeMeta(uastNode: any, tsNode: SyntaxNode | null | undefined, sourcefile?: string): any {
|
|
43
|
-
if (!uastNode || !UAST.isNode(uastNode)) {
|
|
44
|
-
return uastNode;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (tsNode) {
|
|
48
|
-
const loc = toSourceLocation(tsNode, sourcefile);
|
|
49
|
-
uastNode.loc = loc;
|
|
50
|
-
uastNode._meta.loc = loc;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return uastNode;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function visitList(nodes: SyntaxNode[], opts: Record<string, any>): Array<any> {
|
|
57
|
-
return nodes
|
|
58
|
-
.map((node) => visit(node, opts))
|
|
59
|
-
.filter((node) => node !== null && node !== undefined);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function flattenInstructions(nodes: Array<any>): Array<any> {
|
|
63
|
-
const instructions: Array<any> = [];
|
|
64
|
-
for (const node of nodes) {
|
|
65
|
-
if (!node) continue;
|
|
66
|
-
if (UAST.isSequence(node)) {
|
|
67
|
-
instructions.push(...node.expressions);
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
instructions.push(node);
|
|
71
|
-
}
|
|
72
|
-
return instructions;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function mapBinaryOperator(operator: string | undefined): string {
|
|
76
|
-
if (!operator) {
|
|
77
|
-
return '+';
|
|
78
|
-
}
|
|
79
|
-
const mapping: Record<string, string> = {
|
|
80
|
-
'.': '+',
|
|
81
|
-
'??': '||',
|
|
82
|
-
'<=>': '-',
|
|
83
|
-
'or': '||',
|
|
84
|
-
'and': '&&',
|
|
85
|
-
'xor': '^',
|
|
86
|
-
'<>': '!=',
|
|
87
|
-
};
|
|
88
|
-
// PHP 关键字大小写不敏感,统一转小写
|
|
89
|
-
const lower = operator.toLowerCase();
|
|
90
|
-
return mapping[lower] || lower;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function mapAssignOperator(operator: string): string {
|
|
94
|
-
const mapping: Record<string, string> = {
|
|
95
|
-
'.=': '+=',
|
|
96
|
-
'??=': '=',
|
|
97
|
-
'**=': '*=',
|
|
98
|
-
};
|
|
99
|
-
const validOperators = new Set(['=', '^=', '&=', '<<=', '>>=', '>>>=', '+=', '-=', '*=', '/=', '%=', '|=', '**=']);
|
|
100
|
-
if (validOperators.has(operator)) {
|
|
101
|
-
return operator;
|
|
102
|
-
}
|
|
103
|
-
return mapping[operator] || '=';
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** 去除字符串两端的引号 */
|
|
107
|
-
function stripQuotes(text: string): string {
|
|
108
|
-
if (text.length >= 2) {
|
|
109
|
-
const first = text[0];
|
|
110
|
-
const last = text[text.length - 1];
|
|
111
|
-
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
112
|
-
return text.slice(1, -1);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return text;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** 从 heredoc/nowdoc 中提取内容(去掉标签行和结束标签行) */
|
|
119
|
-
function stripHeredoc(text: string): string {
|
|
120
|
-
const lines = text.split('\n');
|
|
121
|
-
// 第一行: <<<TAG 或 <<<'TAG'
|
|
122
|
-
// 最后一行: TAG;
|
|
123
|
-
if (lines.length >= 2) {
|
|
124
|
-
return lines.slice(1, -1).join('\n');
|
|
125
|
-
}
|
|
126
|
-
return text;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** 从节点的匿名子节点中提取 operator 文本 */
|
|
130
|
-
function getOperator(node: SyntaxNode): string {
|
|
131
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
132
|
-
const child = node.child(i)!;
|
|
133
|
-
if (!child.isNamed && child.text !== '(' && child.text !== ')' && child.text !== ',' && child.text !== ';') {
|
|
134
|
-
return child.text;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return '=';
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** 处理函数参数(simple_parameter / variadic_parameter / property_promotion_parameter) */
|
|
141
|
-
function createParameter(paramNode: SyntaxNode, opts: Record<string, any>): any {
|
|
142
|
-
const sourcefile = opts?.sourcefile as string | undefined;
|
|
143
|
-
const nameNode = paramNode.childForFieldName('name');
|
|
144
|
-
const id = nameNode ? visit(nameNode, opts) as UAST.Identifier : UAST.identifier('_');
|
|
145
|
-
const defaultValueNode = paramNode.childForFieldName('default_value');
|
|
146
|
-
const init = defaultValueNode ? visit(defaultValueNode, opts) as UAST.Expression : null;
|
|
147
|
-
const varDecl = UAST.variableDeclaration(id, init, false, UAST.dynamicType());
|
|
148
|
-
varDecl._meta.variadic = paramNode.type === 'variadic_parameter';
|
|
149
|
-
// property_promotion_parameter 的 visibility
|
|
150
|
-
if (paramNode.type === 'property_promotion_parameter') {
|
|
151
|
-
for (const child of paramNode.namedChildren) {
|
|
152
|
-
if (child.type === 'visibility_modifier') {
|
|
153
|
-
varDecl._meta.visibility = child.text;
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return appendNodeMeta(varDecl, paramNode, sourcefile);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** 构建函数定义(function_definition / method_declaration / anonymous_function) */
|
|
162
|
-
function createFunctionLike(node: SyntaxNode, opts: Record<string, any>): any {
|
|
163
|
-
const sourcefile = opts?.sourcefile as string | undefined;
|
|
164
|
-
const nameNode = node.childForFieldName('name');
|
|
165
|
-
const id = nameNode ? visit(nameNode, opts) as UAST.Identifier : null;
|
|
166
|
-
const paramsNode = node.childForFieldName('parameters');
|
|
167
|
-
const parameters = paramsNode
|
|
168
|
-
? paramsNode.namedChildren.map((p) => createParameter(p, opts))
|
|
169
|
-
: [];
|
|
170
|
-
const bodyNode = node.childForFieldName('body');
|
|
171
|
-
const body = bodyNode
|
|
172
|
-
? visit(bodyNode, opts) as UAST.Instruction
|
|
173
|
-
: appendNodeMeta(UAST.scopedStatement([]), node, sourcefile);
|
|
174
|
-
const modifiers: string[] = [];
|
|
175
|
-
for (const child of node.namedChildren) {
|
|
176
|
-
if (child.type === 'visibility_modifier' || child.type === 'static_modifier'
|
|
177
|
-
|| child.type === 'abstract_modifier' || child.type === 'final_modifier'
|
|
178
|
-
|| child.type === 'readonly_modifier') {
|
|
179
|
-
modifiers.push(child.text);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const fdef = UAST.functionDefinition(id, parameters, UAST.dynamicType(), body, modifiers);
|
|
183
|
-
return appendNodeMeta(fdef, node, sourcefile);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** 构建类/接口/trait 定义 */
|
|
187
|
-
function createClassLike(node: SyntaxNode, opts: Record<string, any>, kind?: string): any {
|
|
188
|
-
const sourcefile = opts?.sourcefile as string | undefined;
|
|
189
|
-
const nameNode = node.childForFieldName('name');
|
|
190
|
-
const id = nameNode ? visit(nameNode, opts) as UAST.Identifier : null;
|
|
191
|
-
const bodyNode = node.childForFieldName('body');
|
|
192
|
-
const body = bodyNode
|
|
193
|
-
? flattenInstructions(visitList(bodyNode.namedChildren, opts))
|
|
194
|
-
: [];
|
|
195
|
-
// extends
|
|
196
|
-
const supers: Array<UAST.Expression> = [];
|
|
197
|
-
const baseClause = node.childForFieldName('base_clause');
|
|
198
|
-
if (baseClause) {
|
|
199
|
-
for (const child of baseClause.namedChildren) {
|
|
200
|
-
const superExpr = visit(child, opts);
|
|
201
|
-
if (superExpr) supers.push(superExpr);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const cdef = UAST.classDefinition(id, body, supers);
|
|
205
|
-
// implements
|
|
206
|
-
const implementsClause = node.childForFieldName('class_interface_clause');
|
|
207
|
-
if (implementsClause) {
|
|
208
|
-
cdef._meta.implements = visitList(implementsClause.namedChildren, opts);
|
|
209
|
-
}
|
|
210
|
-
if (kind) {
|
|
211
|
-
cdef._meta.kind = kind;
|
|
212
|
-
}
|
|
213
|
-
// modifiers
|
|
214
|
-
for (const child of node.namedChildren) {
|
|
215
|
-
if (child.type === 'abstract_modifier') cdef._meta.isAbstract = true;
|
|
216
|
-
if (child.type === 'final_modifier') cdef._meta.isFinal = true;
|
|
217
|
-
if (child.type === 'readonly_modifier') cdef._meta.isReadonly = true;
|
|
218
|
-
}
|
|
219
|
-
return appendNodeMeta(cdef, node, sourcefile);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function visit(node: SyntaxNode | null | undefined, opts: Record<string, any>): any {
|
|
223
|
-
if (!node) {
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const sourcefile = opts?.sourcefile as string | undefined;
|
|
228
|
-
|
|
229
|
-
switch (node.type) {
|
|
230
|
-
case 'program': {
|
|
231
|
-
const children = node.namedChildren.filter((child) => child.type !== 'php_tag');
|
|
232
|
-
const body = flattenInstructions(visitList(children, opts));
|
|
233
|
-
const compileUnit = UAST.compileUnit(body, 'php', null, sourcefile || '', version);
|
|
234
|
-
return appendNodeMeta(compileUnit, node, sourcefile);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
case 'expression_statement': {
|
|
238
|
-
const child = node.namedChildren[0];
|
|
239
|
-
if (!child) {
|
|
240
|
-
return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
241
|
-
}
|
|
242
|
-
const result = visit(child, opts);
|
|
243
|
-
// throw_expression 等节点返回的是语句而非表达式,直接返回
|
|
244
|
-
if (UAST.isNode(result) && result.type.endsWith('Statement')) {
|
|
245
|
-
return appendNodeMeta(result, node, sourcefile);
|
|
246
|
-
}
|
|
247
|
-
const exprStmt = UAST.expressionStatement(result as UAST.Expression);
|
|
248
|
-
return appendNodeMeta(exprStmt, node, sourcefile);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
case 'variable_name': {
|
|
252
|
-
// $variable -> 'variable'(variable_name 的 namedChild(0) 是 name 节点)
|
|
253
|
-
const nameNode = node.namedChildren[0];
|
|
254
|
-
const name = nameNode ? nameNode.text : node.text.replace(/^\$/, '');
|
|
255
|
-
return appendNodeMeta(UAST.identifier(name), node, sourcefile);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
case 'dynamic_variable_name': {
|
|
259
|
-
// $$var -> 变量变量,递归访问内部节点
|
|
260
|
-
const inner = node.namedChildren[0];
|
|
261
|
-
if (inner) {
|
|
262
|
-
return visit(inner, opts);
|
|
263
|
-
}
|
|
264
|
-
return appendNodeMeta(UAST.identifier(node.text.replace(/^\$+/, '')), node, sourcefile);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
case 'by_ref': {
|
|
268
|
-
// &$var -> 引用传递,直接访问内部变量
|
|
269
|
-
const inner = node.namedChildren[0];
|
|
270
|
-
if (inner) {
|
|
271
|
-
const result = visit(inner, opts);
|
|
272
|
-
if (UAST.isNode(result)) {
|
|
273
|
-
result._meta.byref = true;
|
|
274
|
-
}
|
|
275
|
-
return result;
|
|
276
|
-
}
|
|
277
|
-
return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
case 'name':
|
|
281
|
-
return appendNodeMeta(UAST.identifier(node.text), node, sourcefile);
|
|
282
|
-
|
|
283
|
-
case 'qualified_name':
|
|
284
|
-
return appendNodeMeta(UAST.identifier(node.text), node, sourcefile);
|
|
285
|
-
|
|
286
|
-
case 'namespace_name':
|
|
287
|
-
return appendNodeMeta(UAST.identifier(node.text), node, sourcefile);
|
|
288
|
-
|
|
289
|
-
case 'relative_scope':
|
|
290
|
-
// self / parent / static
|
|
291
|
-
return appendNodeMeta(UAST.identifier(node.text), node, sourcefile);
|
|
292
|
-
|
|
293
|
-
case 'integer':
|
|
294
|
-
return appendNodeMeta(UAST.literal(Number(node.text), 'number'), node, sourcefile);
|
|
295
|
-
|
|
296
|
-
case 'float':
|
|
297
|
-
return appendNodeMeta(UAST.literal(Number(node.text), 'number'), node, sourcefile);
|
|
298
|
-
|
|
299
|
-
case 'string':
|
|
300
|
-
return appendNodeMeta(UAST.literal(stripQuotes(node.text), 'string'), node, sourcefile);
|
|
301
|
-
|
|
302
|
-
case 'encapsed_string': {
|
|
303
|
-
// 含变量插值的双引号字符串,拆成 binary concat
|
|
304
|
-
const parts = node.namedChildren.map((child) => {
|
|
305
|
-
if (child.type === 'string_content') {
|
|
306
|
-
return appendNodeMeta(UAST.literal(child.text, 'string'), child, sourcefile);
|
|
307
|
-
}
|
|
308
|
-
return visit(child, opts);
|
|
309
|
-
});
|
|
310
|
-
if (parts.length === 0) {
|
|
311
|
-
return appendNodeMeta(UAST.literal('', 'string'), node, sourcefile);
|
|
312
|
-
}
|
|
313
|
-
let expr = parts[0];
|
|
314
|
-
for (let i = 1; i < parts.length; i++) {
|
|
315
|
-
expr = appendNodeMeta(UAST.binaryExpression('+' as any, expr, parts[i]), node, sourcefile);
|
|
316
|
-
}
|
|
317
|
-
if (expr && UAST.isNode(expr)) {
|
|
318
|
-
expr._meta.encapsed = true;
|
|
319
|
-
}
|
|
320
|
-
return appendNodeMeta(expr, node, sourcefile);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
case 'heredoc': {
|
|
324
|
-
const content = stripHeredoc(node.text);
|
|
325
|
-
const literal = UAST.literal(content, 'string');
|
|
326
|
-
literal._meta.heredoc = true;
|
|
327
|
-
return appendNodeMeta(literal, node, sourcefile);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
case 'nowdoc': {
|
|
331
|
-
const content = stripHeredoc(node.text);
|
|
332
|
-
const literal = UAST.literal(content, 'string');
|
|
333
|
-
literal._meta.nowdoc = true;
|
|
334
|
-
return appendNodeMeta(literal, node, sourcefile);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
case 'boolean':
|
|
338
|
-
return appendNodeMeta(
|
|
339
|
-
UAST.literal(node.text.toLowerCase() === 'true', 'boolean'),
|
|
340
|
-
node,
|
|
341
|
-
sourcefile
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
case 'null':
|
|
345
|
-
return appendNodeMeta(UAST.literal(null, 'null'), node, sourcefile);
|
|
346
|
-
|
|
347
|
-
case 'argument': {
|
|
348
|
-
// argument 节点包装了一个表达式 child
|
|
349
|
-
const child = node.namedChildren[0];
|
|
350
|
-
return child ? visit(child, opts) : appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
case 'parenthesized_expression': {
|
|
354
|
-
const child = node.namedChildren[0];
|
|
355
|
-
return child ? visit(child, opts) : appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
case 'text_interpolation':
|
|
359
|
-
// HTML 文本片段,跳过
|
|
360
|
-
return null;
|
|
361
|
-
|
|
362
|
-
// ─── 赋值 ───
|
|
363
|
-
|
|
364
|
-
case 'assignment_expression': {
|
|
365
|
-
const left = visit(node.childForFieldName('left'), opts) as UAST.LVal;
|
|
366
|
-
const right = visit(node.childForFieldName('right'), opts) as UAST.Expression;
|
|
367
|
-
const op = mapAssignOperator(getOperator(node));
|
|
368
|
-
const assign = UAST.assignmentExpression(left, right, op as any, false);
|
|
369
|
-
return appendNodeMeta(assign, node, sourcefile);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
case 'augmented_assignment_expression': {
|
|
373
|
-
const left = visit(node.childForFieldName('left'), opts) as UAST.LVal;
|
|
374
|
-
const right = visit(node.childForFieldName('right'), opts) as UAST.Expression;
|
|
375
|
-
const rawOp = getOperator(node);
|
|
376
|
-
const mappedOp = mapAssignOperator(rawOp);
|
|
377
|
-
const assign = UAST.assignmentExpression(left, right, mappedOp as any, false);
|
|
378
|
-
if (rawOp !== mappedOp) {
|
|
379
|
-
assign._meta.rawOperator = rawOp;
|
|
380
|
-
}
|
|
381
|
-
return appendNodeMeta(assign, node, sourcefile);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
case 'reference_assignment_expression': {
|
|
385
|
-
const left = visit(node.childForFieldName('left'), opts) as UAST.LVal;
|
|
386
|
-
const right = visit(node.childForFieldName('right'), opts) as UAST.Expression;
|
|
387
|
-
const assign = UAST.assignmentExpression(left, right, '=', true);
|
|
388
|
-
assign._meta.byref = true;
|
|
389
|
-
return appendNodeMeta(assign, node, sourcefile);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// ─── 二元 / 条件 / 一元 ───
|
|
393
|
-
|
|
394
|
-
case 'binary_expression': {
|
|
395
|
-
const left = visit(node.childForFieldName('left'), opts) as UAST.Expression;
|
|
396
|
-
const right = visit(node.childForFieldName('right'), opts) as UAST.Expression;
|
|
397
|
-
const op = getOperator(node);
|
|
398
|
-
const binary = UAST.binaryExpression(mapBinaryOperator(op) as any, left, right);
|
|
399
|
-
return appendNodeMeta(binary, node, sourcefile);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
case 'conditional_expression': {
|
|
403
|
-
const named = node.namedChildren;
|
|
404
|
-
if (named.length >= 3) {
|
|
405
|
-
// 完整三元:condition ? consequent : alternative
|
|
406
|
-
const cond = visit(named[0], opts) as UAST.Expression;
|
|
407
|
-
const consequent = visit(named[1], opts) as UAST.Expression;
|
|
408
|
-
const alternative = visit(named[2], opts) as UAST.Expression;
|
|
409
|
-
return appendNodeMeta(UAST.conditionalExpression(cond, consequent, alternative), node, sourcefile);
|
|
410
|
-
}
|
|
411
|
-
// PHP Elvis ?:(省略 consequent),只有 2 个 namedChildren
|
|
412
|
-
const cond = visit(named[0], opts) as UAST.Expression;
|
|
413
|
-
const alternative = visit(named[1], opts) as UAST.Expression;
|
|
414
|
-
// 复用 condition 作为 consequent
|
|
415
|
-
const condDup = visit(named[0], opts) as UAST.Expression;
|
|
416
|
-
return appendNodeMeta(UAST.conditionalExpression(cond, condDup, alternative), node, sourcefile);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
case 'unary_op_expression': {
|
|
420
|
-
const op = getOperator(node);
|
|
421
|
-
const operand = visit(node.namedChildren[0], opts) as UAST.Expression;
|
|
422
|
-
const unary = UAST.unaryExpression(op as any, operand, false);
|
|
423
|
-
return appendNodeMeta(unary, node, sourcefile);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
case 'update_expression': {
|
|
427
|
-
// 判断 prefix/postfix:第一个子节点是匿名(operator)则 prefix,否则 postfix
|
|
428
|
-
const firstChild = node.child(0)!;
|
|
429
|
-
const isPostfix = firstChild.isNamed;
|
|
430
|
-
const op = getOperator(node);
|
|
431
|
-
const operand = visit(node.namedChildren[0], opts) as UAST.Expression;
|
|
432
|
-
const unary = UAST.unaryExpression(op as any, operand, isPostfix);
|
|
433
|
-
return appendNodeMeta(unary, node, sourcefile);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// ─── 调用 ───
|
|
437
|
-
|
|
438
|
-
case 'function_call_expression': {
|
|
439
|
-
const callee = visit(node.childForFieldName('function'), opts) as UAST.Expression;
|
|
440
|
-
const argsNode = node.childForFieldName('arguments');
|
|
441
|
-
const args = argsNode ? visitList(argsNode.namedChildren, opts) as Array<UAST.Expression> : [];
|
|
442
|
-
const call = UAST.callExpression(callee, args);
|
|
443
|
-
return appendNodeMeta(call, node, sourcefile);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
case 'member_call_expression': {
|
|
447
|
-
const object = visit(node.childForFieldName('object'), opts) as UAST.Expression;
|
|
448
|
-
const name = visit(node.childForFieldName('name'), opts) as UAST.Expression;
|
|
449
|
-
const memberCallee = UAST.memberAccess(object, name, false);
|
|
450
|
-
appendNodeMeta(memberCallee, node, sourcefile);
|
|
451
|
-
const argsNode = node.childForFieldName('arguments');
|
|
452
|
-
const args = argsNode ? visitList(argsNode.namedChildren, opts) as Array<UAST.Expression> : [];
|
|
453
|
-
const call = UAST.callExpression(memberCallee, args);
|
|
454
|
-
return appendNodeMeta(call, node, sourcefile);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
case 'scoped_call_expression': {
|
|
458
|
-
const scope = visit(node.childForFieldName('scope'), opts) as UAST.Expression;
|
|
459
|
-
const name = visit(node.childForFieldName('name'), opts) as UAST.Expression;
|
|
460
|
-
const memberCallee = UAST.memberAccess(scope, name, false);
|
|
461
|
-
memberCallee._meta.isStatic = true;
|
|
462
|
-
appendNodeMeta(memberCallee, node, sourcefile);
|
|
463
|
-
const argsNode = node.childForFieldName('arguments');
|
|
464
|
-
const args = argsNode ? visitList(argsNode.namedChildren, opts) as Array<UAST.Expression> : [];
|
|
465
|
-
const call = UAST.callExpression(memberCallee, args);
|
|
466
|
-
return appendNodeMeta(call, node, sourcefile);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// ─── new / cast ───
|
|
470
|
-
|
|
471
|
-
case 'object_creation_expression': {
|
|
472
|
-
const className = visit(node.namedChildren[0], opts) as UAST.Expression;
|
|
473
|
-
const argsNode = node.childForFieldName('arguments');
|
|
474
|
-
const args = argsNode ? visitList(argsNode.namedChildren, opts) as Array<UAST.Expression> : [];
|
|
475
|
-
const expr = UAST.newExpression(className, args);
|
|
476
|
-
return appendNodeMeta(expr, node, sourcefile);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
case 'cast_expression': {
|
|
480
|
-
const typeNode = node.childForFieldName('type');
|
|
481
|
-
const castType = typeNode ? typeNode.text.replace(/[()]/g, '').trim() : 'mixed';
|
|
482
|
-
const value = visit(node.childForFieldName('value'), opts) as UAST.Expression;
|
|
483
|
-
const call = UAST.callExpression(UAST.identifier(castType), [value]);
|
|
484
|
-
call._meta.isCast = true;
|
|
485
|
-
return appendNodeMeta(call, node, sourcefile);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// ─── 成员访问 ───
|
|
489
|
-
|
|
490
|
-
case 'member_access_expression': {
|
|
491
|
-
const object = visit(node.childForFieldName('object'), opts) as UAST.Expression;
|
|
492
|
-
const name = visit(node.childForFieldName('name'), opts) as UAST.Expression;
|
|
493
|
-
const member = UAST.memberAccess(object, name, false);
|
|
494
|
-
return appendNodeMeta(member, node, sourcefile);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
case 'nullsafe_member_access_expression': {
|
|
498
|
-
const object = visit(node.childForFieldName('object'), opts) as UAST.Expression;
|
|
499
|
-
const name = visit(node.childForFieldName('name'), opts) as UAST.Expression;
|
|
500
|
-
const access = UAST.memberAccess(object, name, false);
|
|
501
|
-
appendNodeMeta(access, node, sourcefile);
|
|
502
|
-
access._meta.nullsafe = true;
|
|
503
|
-
const expr = UAST.conditionalExpression(object, access, UAST.literal(null, 'null'));
|
|
504
|
-
return appendNodeMeta(expr, node, sourcefile);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
case 'scoped_property_access_expression': {
|
|
508
|
-
const scope = visit(node.childForFieldName('scope'), opts) as UAST.Expression;
|
|
509
|
-
const name = visit(node.childForFieldName('name'), opts) as UAST.Expression;
|
|
510
|
-
const member = UAST.memberAccess(scope, name, false);
|
|
511
|
-
member._meta.isStatic = true;
|
|
512
|
-
return appendNodeMeta(member, node, sourcefile);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
case 'class_constant_access_expression': {
|
|
516
|
-
// class_constant_access_expression 没有字段名,按位置取 namedChildren
|
|
517
|
-
const scope = visit(node.namedChildren[0], opts) as UAST.Expression;
|
|
518
|
-
const name = visit(node.namedChildren[1], opts) as UAST.Expression;
|
|
519
|
-
const member = UAST.memberAccess(scope, name, false);
|
|
520
|
-
member._meta.isStatic = true;
|
|
521
|
-
return appendNodeMeta(member, node, sourcefile);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
case 'subscript_expression': {
|
|
525
|
-
const named = node.namedChildren;
|
|
526
|
-
const object = visit(named[0], opts) as UAST.Expression;
|
|
527
|
-
// $arr[] 数组追加时 index 为空,用 noop 占位
|
|
528
|
-
const index = named.length > 1 ? visit(named[1], opts) as UAST.Expression : UAST.noop() as any;
|
|
529
|
-
const member = UAST.memberAccess(object, index, true);
|
|
530
|
-
return appendNodeMeta(member, node, sourcefile);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// ─── 数组 / 列表 / match ───
|
|
534
|
-
|
|
535
|
-
case 'array_creation_expression': {
|
|
536
|
-
const properties = node.namedChildren.map((element) => {
|
|
537
|
-
const elementChildren = element.namedChildren;
|
|
538
|
-
if (elementChildren.length >= 2) {
|
|
539
|
-
// key => value
|
|
540
|
-
const key = visit(elementChildren[0], opts) as UAST.Expression;
|
|
541
|
-
const value = visit(elementChildren[1], opts) as UAST.Expression;
|
|
542
|
-
const prop = UAST.objectProperty(key, value);
|
|
543
|
-
return appendNodeMeta(prop, element, sourcefile);
|
|
544
|
-
}
|
|
545
|
-
// value only
|
|
546
|
-
const value = visit(elementChildren[0], opts) as UAST.Expression;
|
|
547
|
-
const prop = UAST.objectProperty(UAST.literal(null, 'null'), value);
|
|
548
|
-
return appendNodeMeta(prop, element, sourcefile);
|
|
549
|
-
});
|
|
550
|
-
const expr = UAST.objectExpression(properties);
|
|
551
|
-
expr._meta.isArray = true;
|
|
552
|
-
return appendNodeMeta(expr, node, sourcefile);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
case 'list_literal': {
|
|
556
|
-
const elements = node.namedChildren.map((child) => visit(child, opts) as UAST.Expression);
|
|
557
|
-
const tuple = UAST.tupleExpression(elements);
|
|
558
|
-
return appendNodeMeta(tuple, node, sourcefile);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
case 'match_expression': {
|
|
562
|
-
const condition = visit(node.childForFieldName('condition_list') || node.childForFieldName('condition'), opts) as UAST.Expression;
|
|
563
|
-
const bodyNode = node.childForFieldName('body');
|
|
564
|
-
const arms = bodyNode ? bodyNode.namedChildren.map((arm) => {
|
|
565
|
-
if (arm.type === 'match_default_expression') {
|
|
566
|
-
// default arm
|
|
567
|
-
const body = visit(arm.childForFieldName('return_expression') || arm.namedChildren[0], opts) as UAST.Expression;
|
|
568
|
-
const armObject = UAST.objectExpression([
|
|
569
|
-
appendNodeMeta(
|
|
570
|
-
UAST.objectProperty(UAST.identifier('conds'), UAST.tupleExpression([UAST.literal('default', 'string')])),
|
|
571
|
-
arm, sourcefile
|
|
572
|
-
),
|
|
573
|
-
appendNodeMeta(UAST.objectProperty(UAST.identifier('body'), body), arm, sourcefile),
|
|
574
|
-
]);
|
|
575
|
-
armObject._meta.default = true;
|
|
576
|
-
return appendNodeMeta(armObject, arm, sourcefile);
|
|
577
|
-
}
|
|
578
|
-
// match_conditional_expression
|
|
579
|
-
const condListNode = arm.childForFieldName('conditional_expressions');
|
|
580
|
-
const conds = condListNode
|
|
581
|
-
? visitList(condListNode.namedChildren, opts) as Array<UAST.Expression>
|
|
582
|
-
: visitList(arm.namedChildren.slice(0, -1), opts) as Array<UAST.Expression>;
|
|
583
|
-
const returnExpr = arm.childForFieldName('return_expression') || arm.namedChildren[arm.namedChildren.length - 1];
|
|
584
|
-
const body = visit(returnExpr, opts) as UAST.Expression;
|
|
585
|
-
const armObject = UAST.objectExpression([
|
|
586
|
-
appendNodeMeta(
|
|
587
|
-
UAST.objectProperty(UAST.identifier('conds'), UAST.tupleExpression(conds.length > 0 ? conds : [UAST.literal('default', 'string')])),
|
|
588
|
-
arm, sourcefile
|
|
589
|
-
),
|
|
590
|
-
appendNodeMeta(UAST.objectProperty(UAST.identifier('body'), body), arm, sourcefile),
|
|
591
|
-
]);
|
|
592
|
-
return appendNodeMeta(armObject, arm, sourcefile);
|
|
593
|
-
}) : [];
|
|
594
|
-
const matchArgs = [condition, UAST.tupleExpression(arms)];
|
|
595
|
-
const call = UAST.callExpression(UAST.identifier('match'), matchArgs);
|
|
596
|
-
call._meta.synthetic = true;
|
|
597
|
-
return appendNodeMeta(call, node, sourcefile);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// ─── import ───
|
|
601
|
-
|
|
602
|
-
case 'include_expression':
|
|
603
|
-
case 'include_once_expression':
|
|
604
|
-
case 'require_expression':
|
|
605
|
-
case 'require_once_expression': {
|
|
606
|
-
const targetNode = visit(node.namedChildren[0], opts);
|
|
607
|
-
let target: UAST.Literal;
|
|
608
|
-
if (UAST.isLiteral(targetNode)) {
|
|
609
|
-
target = targetNode;
|
|
610
|
-
} else {
|
|
611
|
-
// 动态 include,用空字符串占位
|
|
612
|
-
target = UAST.literal('', 'string');
|
|
613
|
-
target._meta.dynamicFrom = targetNode;
|
|
614
|
-
}
|
|
615
|
-
const importExpr = UAST.importExpression(target);
|
|
616
|
-
importExpr._meta.require = node.type.startsWith('require');
|
|
617
|
-
importExpr._meta.once = node.type.endsWith('once_expression');
|
|
618
|
-
return appendNodeMeta(importExpr, node, sourcefile);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// ─── 其他表达式 ───
|
|
622
|
-
|
|
623
|
-
case 'echo_statement': {
|
|
624
|
-
const args = visitList(node.namedChildren, opts) as Array<UAST.Expression>;
|
|
625
|
-
const echoCall = UAST.callExpression(UAST.identifier('echo'), args);
|
|
626
|
-
appendNodeMeta(echoCall, node, sourcefile);
|
|
627
|
-
const exprStmt = UAST.expressionStatement(echoCall);
|
|
628
|
-
return appendNodeMeta(exprStmt, node, sourcefile);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
case 'yield_expression': {
|
|
632
|
-
const valueNode = node.namedChildren[0] || null;
|
|
633
|
-
const expr = UAST.yieldExpression(valueNode ? visit(valueNode, opts) as UAST.Expression : null);
|
|
634
|
-
return appendNodeMeta(expr, node, sourcefile);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
case 'error_suppression_expression': {
|
|
638
|
-
const expr = visit(node.namedChildren[0], opts);
|
|
639
|
-
if (expr && UAST.isNode(expr)) {
|
|
640
|
-
expr._meta.silent = true;
|
|
641
|
-
}
|
|
642
|
-
return expr;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
case 'clone_expression': {
|
|
646
|
-
const expr = visit(node.namedChildren[0], opts) as UAST.Expression;
|
|
647
|
-
const call = UAST.callExpression(UAST.identifier('clone'), [expr]);
|
|
648
|
-
return appendNodeMeta(call, node, sourcefile);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
case 'print_intrinsic': {
|
|
652
|
-
const expr = visit(node.namedChildren[0], opts) as UAST.Expression;
|
|
653
|
-
const call = UAST.callExpression(UAST.identifier('print'), [expr]);
|
|
654
|
-
return appendNodeMeta(call, node, sourcefile);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
case 'exit_statement': {
|
|
658
|
-
const args = node.namedChildren.length > 0
|
|
659
|
-
? [visit(node.namedChildren[0], opts) as UAST.Expression]
|
|
660
|
-
: [];
|
|
661
|
-
const call = UAST.callExpression(UAST.identifier('exit'), args);
|
|
662
|
-
return appendNodeMeta(call, node, sourcefile);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
case 'sequence_expression': {
|
|
666
|
-
const items = visitList(node.namedChildren, opts);
|
|
667
|
-
const seq = UAST.sequence(items);
|
|
668
|
-
return appendNodeMeta(seq, node, sourcefile);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// ─── 语句 ───
|
|
672
|
-
|
|
673
|
-
case 'compound_statement': {
|
|
674
|
-
const body = flattenInstructions(visitList(node.namedChildren, opts));
|
|
675
|
-
const scoped = UAST.scopedStatement(body);
|
|
676
|
-
return appendNodeMeta(scoped, node, sourcefile);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
case 'if_statement': {
|
|
680
|
-
const condNode = node.childForFieldName('condition');
|
|
681
|
-
// parenthesized_expression → 取内层
|
|
682
|
-
const test = condNode && condNode.type === 'parenthesized_expression'
|
|
683
|
-
? visit(condNode.namedChildren[0], opts) as UAST.Expression
|
|
684
|
-
: visit(condNode, opts) as UAST.Expression;
|
|
685
|
-
const bodyNode = node.childForFieldName('body');
|
|
686
|
-
const consequent = bodyNode
|
|
687
|
-
? visit(bodyNode, opts) as UAST.Instruction
|
|
688
|
-
: UAST.scopedStatement([]);
|
|
689
|
-
// alternative: else_clause 或 else_if_clause
|
|
690
|
-
const altNode = node.childForFieldName('alternative');
|
|
691
|
-
let alternate: UAST.Instruction | null = null;
|
|
692
|
-
if (altNode) {
|
|
693
|
-
if (altNode.type === 'else_if_clause') {
|
|
694
|
-
// 递归构建嵌套 if
|
|
695
|
-
const elseIfCond = altNode.childForFieldName('condition');
|
|
696
|
-
const elseIfTest = elseIfCond && elseIfCond.type === 'parenthesized_expression'
|
|
697
|
-
? visit(elseIfCond.namedChildren[0], opts) as UAST.Expression
|
|
698
|
-
: visit(elseIfCond, opts) as UAST.Expression;
|
|
699
|
-
const elseIfBody = altNode.childForFieldName('body');
|
|
700
|
-
const elseIfConsequent = elseIfBody
|
|
701
|
-
? visit(elseIfBody, opts) as UAST.Instruction
|
|
702
|
-
: UAST.scopedStatement([]);
|
|
703
|
-
const elseIfAlt = altNode.childForFieldName('alternative');
|
|
704
|
-
const nestedAlt = elseIfAlt ? visit(elseIfAlt, opts) as UAST.Instruction : null;
|
|
705
|
-
alternate = UAST.ifStatement(elseIfTest, elseIfConsequent, nestedAlt);
|
|
706
|
-
appendNodeMeta(alternate, altNode, sourcefile);
|
|
707
|
-
} else if (altNode.type === 'else_clause') {
|
|
708
|
-
const elseBody = altNode.namedChildren[0];
|
|
709
|
-
alternate = elseBody ? visit(elseBody, opts) as UAST.Instruction : UAST.scopedStatement([]);
|
|
710
|
-
} else {
|
|
711
|
-
alternate = visit(altNode, opts) as UAST.Instruction;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
const ifStmt = UAST.ifStatement(test, consequent, alternate);
|
|
715
|
-
return appendNodeMeta(ifStmt, node, sourcefile);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
case 'else_if_clause': {
|
|
719
|
-
const condNode = node.childForFieldName('condition');
|
|
720
|
-
const test = condNode && condNode.type === 'parenthesized_expression'
|
|
721
|
-
? visit(condNode.namedChildren[0], opts) as UAST.Expression
|
|
722
|
-
: visit(condNode, opts) as UAST.Expression;
|
|
723
|
-
const bodyNode = node.childForFieldName('body');
|
|
724
|
-
const consequent = bodyNode
|
|
725
|
-
? visit(bodyNode, opts) as UAST.Instruction
|
|
726
|
-
: UAST.scopedStatement([]);
|
|
727
|
-
const altNode = node.childForFieldName('alternative');
|
|
728
|
-
const alternate = altNode ? visit(altNode, opts) as UAST.Instruction : null;
|
|
729
|
-
const ifStmt = UAST.ifStatement(test, consequent, alternate);
|
|
730
|
-
return appendNodeMeta(ifStmt, node, sourcefile);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
case 'else_clause': {
|
|
734
|
-
const child = node.namedChildren[0];
|
|
735
|
-
return child ? visit(child, opts) : appendNodeMeta(UAST.scopedStatement([]), node, sourcefile);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
case 'return_statement': {
|
|
739
|
-
const child = node.namedChildren[0] || null;
|
|
740
|
-
const ret = UAST.returnStatement(child ? visit(child, opts) as UAST.Expression : null);
|
|
741
|
-
return appendNodeMeta(ret, node, sourcefile);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
case 'break_statement': {
|
|
745
|
-
const stmt = UAST.breakStatement();
|
|
746
|
-
const levelNode = node.namedChildren[0];
|
|
747
|
-
if (levelNode && levelNode.type === 'integer') {
|
|
748
|
-
stmt._meta.level = Number(levelNode.text);
|
|
749
|
-
}
|
|
750
|
-
return appendNodeMeta(stmt, node, sourcefile);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
case 'continue_statement': {
|
|
754
|
-
const stmt = UAST.continueStatement();
|
|
755
|
-
const levelNode = node.namedChildren[0];
|
|
756
|
-
if (levelNode && levelNode.type === 'integer') {
|
|
757
|
-
stmt._meta.level = Number(levelNode.text);
|
|
758
|
-
}
|
|
759
|
-
return appendNodeMeta(stmt, node, sourcefile);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
case 'throw_expression': {
|
|
763
|
-
const expr = visit(node.namedChildren[0], opts) as UAST.Expression;
|
|
764
|
-
const stmt = UAST.throwStatement(expr);
|
|
765
|
-
return appendNodeMeta(stmt, node, sourcefile);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
case 'try_statement': {
|
|
769
|
-
const bodyNode = node.childForFieldName('body');
|
|
770
|
-
const body = bodyNode ? visit(bodyNode, opts) as UAST.Statement : UAST.scopedStatement([]) as any;
|
|
771
|
-
const catches: Array<UAST.CatchClause> = [];
|
|
772
|
-
let finalizer: UAST.Instruction | null = null;
|
|
773
|
-
for (const child of node.namedChildren) {
|
|
774
|
-
if (child.type === 'catch_clause') {
|
|
775
|
-
catches.push(visit(child, opts) as UAST.CatchClause);
|
|
776
|
-
} else if (child.type === 'finally_clause') {
|
|
777
|
-
const finallyBody = child.namedChildren[0];
|
|
778
|
-
finalizer = finallyBody ? visit(finallyBody, opts) as UAST.Instruction : UAST.scopedStatement([]);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
const tryStmt = UAST.tryStatement(body, catches.length > 0 ? catches : null, finalizer);
|
|
782
|
-
return appendNodeMeta(tryStmt, node, sourcefile);
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
case 'catch_clause': {
|
|
786
|
-
let paramId: UAST.Identifier = UAST.identifier('_');
|
|
787
|
-
const catchTypes: Array<any> = [];
|
|
788
|
-
let catchBody: UAST.Instruction = UAST.scopedStatement([]);
|
|
789
|
-
for (const child of node.namedChildren) {
|
|
790
|
-
if (child.type === 'type_list') {
|
|
791
|
-
for (const typeChild of child.namedChildren) {
|
|
792
|
-
catchTypes.push(visit(typeChild, opts));
|
|
793
|
-
}
|
|
794
|
-
} else if (child.type === 'named_type' || child.type === 'qualified_name' || child.type === 'name') {
|
|
795
|
-
catchTypes.push(visit(child, opts));
|
|
796
|
-
} else if (child.type === 'variable_name') {
|
|
797
|
-
paramId = visit(child, opts) as UAST.Identifier;
|
|
798
|
-
} else if (child.type === 'compound_statement') {
|
|
799
|
-
catchBody = visit(child, opts) as UAST.Instruction;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
const param = UAST.variableDeclaration(paramId, null, false, UAST.dynamicType());
|
|
803
|
-
param._meta.catchTypes = catchTypes;
|
|
804
|
-
appendNodeMeta(param, node, sourcefile);
|
|
805
|
-
const clause = UAST.catchClause([param], catchBody);
|
|
806
|
-
return appendNodeMeta(clause, node, sourcefile);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
case 'switch_statement': {
|
|
810
|
-
const condNode = node.childForFieldName('condition');
|
|
811
|
-
const discriminant = condNode && condNode.type === 'parenthesized_expression'
|
|
812
|
-
? visit(condNode.namedChildren[0], opts) as UAST.Expression
|
|
813
|
-
: visit(condNode, opts) as UAST.Expression;
|
|
814
|
-
const bodyNode = node.childForFieldName('body');
|
|
815
|
-
// 只取 case_statement / default_statement,过滤 comment 等
|
|
816
|
-
const caseNodes = bodyNode
|
|
817
|
-
? bodyNode.namedChildren.filter((c) => c.type === 'case_statement' || c.type === 'default_statement')
|
|
818
|
-
: [];
|
|
819
|
-
const cases = visitList(caseNodes, opts) as Array<UAST.CaseClause>;
|
|
820
|
-
const switchStmt = UAST.switchStatement(discriminant, cases);
|
|
821
|
-
return appendNodeMeta(switchStmt, node, sourcefile);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
case 'case_statement': {
|
|
825
|
-
const children = node.namedChildren;
|
|
826
|
-
const test = children.length > 0 ? visit(children[0], opts) as UAST.Expression : null;
|
|
827
|
-
const bodyStatements = children.length > 1
|
|
828
|
-
? flattenInstructions(visitList(children.slice(1), opts))
|
|
829
|
-
: [];
|
|
830
|
-
const body = UAST.scopedStatement(bodyStatements);
|
|
831
|
-
appendNodeMeta(body, node, sourcefile);
|
|
832
|
-
const clause = UAST.caseClause(test, body);
|
|
833
|
-
return appendNodeMeta(clause, node, sourcefile);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
case 'default_statement': {
|
|
837
|
-
const bodyStatements = flattenInstructions(visitList(node.namedChildren, opts));
|
|
838
|
-
const body = UAST.scopedStatement(bodyStatements);
|
|
839
|
-
appendNodeMeta(body, node, sourcefile);
|
|
840
|
-
const clause = UAST.caseClause(null, body);
|
|
841
|
-
return appendNodeMeta(clause, node, sourcefile);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
case 'for_statement': {
|
|
845
|
-
const children = node.namedChildren;
|
|
846
|
-
let init: UAST.Expression | null = null;
|
|
847
|
-
let test: UAST.Expression | null = null;
|
|
848
|
-
let update: UAST.Expression | null = null;
|
|
849
|
-
let forBody: UAST.Instruction = UAST.scopedStatement([]);
|
|
850
|
-
const bodyIdx = children.findIndex((c) => c.type === 'compound_statement' || c.type === 'colon_block');
|
|
851
|
-
if (bodyIdx >= 0) {
|
|
852
|
-
forBody = visit(children[bodyIdx], opts) as UAST.Instruction;
|
|
853
|
-
}
|
|
854
|
-
const exprs = children.filter((c) => c.type !== 'compound_statement' && c.type !== 'colon_block');
|
|
855
|
-
if (exprs.length >= 1) init = visit(exprs[0], opts) as UAST.Expression;
|
|
856
|
-
if (exprs.length >= 2) test = visit(exprs[1], opts) as UAST.Expression;
|
|
857
|
-
if (exprs.length >= 3) update = visit(exprs[2], opts) as UAST.Expression;
|
|
858
|
-
const forStmt = UAST.forStatement(init, test, update, forBody);
|
|
859
|
-
return appendNodeMeta(forStmt, node, sourcefile);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
case 'foreach_statement': {
|
|
863
|
-
const children = node.namedChildren;
|
|
864
|
-
let source: UAST.Expression = UAST.noop() as any;
|
|
865
|
-
let key: UAST.VariableDeclaration | null = null;
|
|
866
|
-
let value: UAST.VariableDeclaration | null = null;
|
|
867
|
-
let foreachBody: UAST.Instruction = UAST.scopedStatement([]);
|
|
868
|
-
const bodyIdx = children.findIndex((c) => c.type === 'compound_statement' || c.type === 'colon_block');
|
|
869
|
-
if (bodyIdx >= 0) {
|
|
870
|
-
foreachBody = visit(children[bodyIdx], opts) as UAST.Instruction;
|
|
871
|
-
}
|
|
872
|
-
const nonBody = children.filter((c) => c.type !== 'compound_statement' && c.type !== 'colon_block');
|
|
873
|
-
if (nonBody.length >= 1) {
|
|
874
|
-
source = visit(nonBody[0], opts) as UAST.Expression;
|
|
875
|
-
}
|
|
876
|
-
if (nonBody.length >= 2) {
|
|
877
|
-
const second = nonBody[1];
|
|
878
|
-
if (second.type === 'pair') {
|
|
879
|
-
const pairChildren = second.namedChildren;
|
|
880
|
-
if (pairChildren.length >= 2) {
|
|
881
|
-
const keyExpr = visit(pairChildren[0], opts) as UAST.LVal;
|
|
882
|
-
key = UAST.variableDeclaration(keyExpr, null, false, UAST.dynamicType());
|
|
883
|
-
appendNodeMeta(key, pairChildren[0], sourcefile);
|
|
884
|
-
const valExpr = visit(pairChildren[1], opts) as UAST.LVal;
|
|
885
|
-
value = UAST.variableDeclaration(valExpr, null, false, UAST.dynamicType());
|
|
886
|
-
appendNodeMeta(value, pairChildren[1], sourcefile);
|
|
887
|
-
}
|
|
888
|
-
} else {
|
|
889
|
-
const valExpr = visit(second, opts) as UAST.LVal;
|
|
890
|
-
value = UAST.variableDeclaration(valExpr, null, false, UAST.dynamicType());
|
|
891
|
-
appendNodeMeta(value, second, sourcefile);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
const rangeStmt = UAST.rangeStatement(key, value, source, foreachBody);
|
|
895
|
-
return appendNodeMeta(rangeStmt, node, sourcefile);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
case 'while_statement': {
|
|
899
|
-
const condNode = node.childForFieldName('condition');
|
|
900
|
-
const test = condNode && condNode.type === 'parenthesized_expression'
|
|
901
|
-
? visit(condNode.namedChildren[0], opts) as UAST.Expression
|
|
902
|
-
: visit(condNode, opts) as UAST.Expression;
|
|
903
|
-
const bodyNode = node.childForFieldName('body');
|
|
904
|
-
const whileBody = bodyNode ? visit(bodyNode, opts) as UAST.Instruction : UAST.scopedStatement([]);
|
|
905
|
-
const stmt = UAST.whileStatement(test, whileBody, false);
|
|
906
|
-
return appendNodeMeta(stmt, node, sourcefile);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
case 'do_statement': {
|
|
910
|
-
const bodyNode = node.childForFieldName('body');
|
|
911
|
-
const doBody = bodyNode ? visit(bodyNode, opts) as UAST.Instruction : UAST.scopedStatement([]);
|
|
912
|
-
const condNode = node.childForFieldName('condition');
|
|
913
|
-
const test = condNode && condNode.type === 'parenthesized_expression'
|
|
914
|
-
? visit(condNode.namedChildren[0], opts) as UAST.Expression
|
|
915
|
-
: visit(condNode, opts) as UAST.Expression;
|
|
916
|
-
const stmt = UAST.whileStatement(test, doBody, true);
|
|
917
|
-
return appendNodeMeta(stmt, node, sourcefile);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
case 'empty_statement':
|
|
921
|
-
return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
922
|
-
|
|
923
|
-
case 'goto_statement': {
|
|
924
|
-
const noop = UAST.noop();
|
|
925
|
-
noop._meta.goto = node.namedChildren[0] ? node.namedChildren[0].text : null;
|
|
926
|
-
return appendNodeMeta(noop, node, sourcefile);
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
case 'named_label_statement': {
|
|
930
|
-
const noop = UAST.noop();
|
|
931
|
-
noop._meta.label = node.namedChildren[0] ? node.namedChildren[0].text : null;
|
|
932
|
-
return appendNodeMeta(noop, node, sourcefile);
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
case 'unset_statement': {
|
|
936
|
-
const args = visitList(node.namedChildren, opts) as Array<UAST.Expression>;
|
|
937
|
-
const call = UAST.callExpression(UAST.identifier('unset'), args);
|
|
938
|
-
appendNodeMeta(call, node, sourcefile);
|
|
939
|
-
const exprStmt = UAST.expressionStatement(call);
|
|
940
|
-
return appendNodeMeta(exprStmt, node, sourcefile);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// ─── 声明 ───
|
|
944
|
-
|
|
945
|
-
case 'function_definition':
|
|
946
|
-
case 'method_declaration':
|
|
947
|
-
return createFunctionLike(node, opts);
|
|
948
|
-
|
|
949
|
-
case 'anonymous_function': {
|
|
950
|
-
const fdef = createFunctionLike(node, opts);
|
|
951
|
-
for (const child of node.namedChildren) {
|
|
952
|
-
if (child.type === 'anonymous_function_use_clause') {
|
|
953
|
-
fdef._meta.uses = visitList(child.namedChildren, opts);
|
|
954
|
-
break;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
return fdef;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
case 'arrow_function': {
|
|
961
|
-
const paramsNode = node.childForFieldName('parameters');
|
|
962
|
-
const parameters = paramsNode
|
|
963
|
-
? paramsNode.namedChildren.map((p) => createParameter(p, opts))
|
|
964
|
-
: [];
|
|
965
|
-
const bodyNode = node.childForFieldName('body');
|
|
966
|
-
let body: UAST.Instruction;
|
|
967
|
-
if (bodyNode) {
|
|
968
|
-
const bodyExpr = visit(bodyNode, opts);
|
|
969
|
-
if (bodyExpr && UAST.isNode(bodyExpr) && !UAST.isExpr(bodyExpr)) {
|
|
970
|
-
body = bodyExpr as UAST.Instruction;
|
|
971
|
-
} else {
|
|
972
|
-
const ret = UAST.returnStatement(bodyExpr as UAST.Expression);
|
|
973
|
-
appendNodeMeta(ret, bodyNode, sourcefile);
|
|
974
|
-
body = UAST.scopedStatement([ret]);
|
|
975
|
-
appendNodeMeta(body, bodyNode, sourcefile);
|
|
976
|
-
}
|
|
977
|
-
} else {
|
|
978
|
-
body = UAST.scopedStatement([]);
|
|
979
|
-
}
|
|
980
|
-
const fdef = UAST.functionDefinition(null, parameters, UAST.dynamicType(), body, []);
|
|
981
|
-
return appendNodeMeta(fdef, node, sourcefile);
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
case 'simple_parameter':
|
|
985
|
-
case 'variadic_parameter':
|
|
986
|
-
case 'property_promotion_parameter':
|
|
987
|
-
return createParameter(node, opts);
|
|
988
|
-
|
|
989
|
-
case 'class_declaration':
|
|
990
|
-
return createClassLike(node, opts);
|
|
991
|
-
|
|
992
|
-
case 'interface_declaration':
|
|
993
|
-
return createClassLike(node, opts, 'interface');
|
|
994
|
-
|
|
995
|
-
case 'trait_declaration':
|
|
996
|
-
return createClassLike(node, opts, 'trait');
|
|
997
|
-
|
|
998
|
-
case 'enum_declaration': {
|
|
999
|
-
const nameNode = node.childForFieldName('name');
|
|
1000
|
-
const id = nameNode ? visit(nameNode, opts) as UAST.Identifier : null;
|
|
1001
|
-
const bodyNode = node.childForFieldName('body');
|
|
1002
|
-
const body = bodyNode
|
|
1003
|
-
? flattenInstructions(visitList(bodyNode.namedChildren, opts))
|
|
1004
|
-
: [];
|
|
1005
|
-
const cdef = UAST.classDefinition(id, body, []);
|
|
1006
|
-
cdef._meta.kind = 'enum';
|
|
1007
|
-
return appendNodeMeta(cdef, node, sourcefile);
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
case 'enum_case': {
|
|
1011
|
-
const nameNode = node.childForFieldName('name');
|
|
1012
|
-
const id = nameNode ? visit(nameNode, opts) as UAST.Identifier : UAST.identifier('_');
|
|
1013
|
-
let init: UAST.Expression | null = null;
|
|
1014
|
-
for (const child of node.namedChildren) {
|
|
1015
|
-
if (child !== nameNode && child.type !== 'name') {
|
|
1016
|
-
init = visit(child, opts) as UAST.Expression;
|
|
1017
|
-
break;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
const decl = UAST.variableDeclaration(id, init, false, UAST.dynamicType());
|
|
1021
|
-
decl._meta.enumCase = true;
|
|
1022
|
-
return appendNodeMeta(decl, node, sourcefile);
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
case 'property_declaration': {
|
|
1026
|
-
const declarations: Array<any> = [];
|
|
1027
|
-
let visibility: string | null = null;
|
|
1028
|
-
let isStatic = false;
|
|
1029
|
-
let isReadonly = false;
|
|
1030
|
-
for (const child of node.namedChildren) {
|
|
1031
|
-
if (child.type === 'visibility_modifier') {
|
|
1032
|
-
visibility = child.text;
|
|
1033
|
-
} else if (child.type === 'static_modifier') {
|
|
1034
|
-
isStatic = true;
|
|
1035
|
-
} else if (child.type === 'readonly_modifier') {
|
|
1036
|
-
isReadonly = true;
|
|
1037
|
-
} else if (child.type === 'property_element') {
|
|
1038
|
-
const propName = child.childForFieldName('name') || child.namedChildren[0];
|
|
1039
|
-
const propValue = child.namedChildren.length > 1 ? child.namedChildren[1] : null;
|
|
1040
|
-
const id = propName ? visit(propName, opts) as UAST.LVal : UAST.identifier('_');
|
|
1041
|
-
const init = propValue ? visit(propValue, opts) as UAST.Expression : null;
|
|
1042
|
-
const decl = UAST.variableDeclaration(id, init, false, UAST.dynamicType());
|
|
1043
|
-
decl._meta.visibility = visibility;
|
|
1044
|
-
decl._meta.isStatic = isStatic;
|
|
1045
|
-
decl._meta.readonly = isReadonly;
|
|
1046
|
-
declarations.push(appendNodeMeta(decl, child, sourcefile));
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
if (declarations.length === 1) return declarations[0];
|
|
1050
|
-
if (declarations.length === 0) return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
1051
|
-
return appendNodeMeta(UAST.sequence(declarations), node, sourcefile);
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
case 'const_declaration': {
|
|
1055
|
-
const declarations: Array<any> = [];
|
|
1056
|
-
for (const child of node.namedChildren) {
|
|
1057
|
-
if (child.type === 'const_element') {
|
|
1058
|
-
const nameChild = child.childForFieldName('name') || child.namedChildren[0];
|
|
1059
|
-
const valueChild = child.childForFieldName('value') || child.namedChildren[1];
|
|
1060
|
-
const id = nameChild ? visit(nameChild, opts) as UAST.LVal : UAST.identifier('_');
|
|
1061
|
-
const init = valueChild ? visit(valueChild, opts) as UAST.Expression : null;
|
|
1062
|
-
const decl = UAST.variableDeclaration(id, init, false, UAST.dynamicType());
|
|
1063
|
-
decl._meta.constant = true;
|
|
1064
|
-
declarations.push(appendNodeMeta(decl, child, sourcefile));
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
if (declarations.length === 1) return declarations[0];
|
|
1068
|
-
if (declarations.length === 0) return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
1069
|
-
return appendNodeMeta(UAST.sequence(declarations), node, sourcefile);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
case 'use_declaration': {
|
|
1073
|
-
// trait use 语句
|
|
1074
|
-
const traitNames = visitList(node.namedChildren, opts) as Array<UAST.Expression>;
|
|
1075
|
-
const call = UAST.callExpression(UAST.identifier('trait_use'), traitNames);
|
|
1076
|
-
call._meta.synthetic = true;
|
|
1077
|
-
appendNodeMeta(call, node, sourcefile);
|
|
1078
|
-
const exprStmt = UAST.expressionStatement(call);
|
|
1079
|
-
return appendNodeMeta(exprStmt, node, sourcefile);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
case 'namespace_definition': {
|
|
1083
|
-
const nameNode = node.childForFieldName('name');
|
|
1084
|
-
const bodyNode = node.childForFieldName('body');
|
|
1085
|
-
const instructions: Array<UAST.Instruction> = [];
|
|
1086
|
-
if (nameNode) {
|
|
1087
|
-
const pkg = UAST.packageDeclaration(UAST.identifier(nameNode.text));
|
|
1088
|
-
instructions.push(appendNodeMeta(pkg, nameNode, sourcefile));
|
|
1089
|
-
}
|
|
1090
|
-
if (bodyNode) {
|
|
1091
|
-
instructions.push(...flattenInstructions(visitList(bodyNode.namedChildren, opts)));
|
|
1092
|
-
}
|
|
1093
|
-
if (instructions.length === 1) return instructions[0];
|
|
1094
|
-
if (instructions.length === 0) return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
1095
|
-
return appendNodeMeta(UAST.sequence(instructions), node, sourcefile);
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
case 'namespace_use_declaration': {
|
|
1099
|
-
const imports: Array<any> = [];
|
|
1100
|
-
for (const child of node.namedChildren) {
|
|
1101
|
-
if (child.type === 'namespace_use_clause') {
|
|
1102
|
-
const nameChild = child.namedChildren[0];
|
|
1103
|
-
const from = UAST.literal(nameChild ? nameChild.text : '', 'string');
|
|
1104
|
-
const aliasChild = child.namedChildren.length > 1 ? child.namedChildren[1] : null;
|
|
1105
|
-
const alias = aliasChild ? visit(aliasChild, opts) as UAST.Identifier : undefined;
|
|
1106
|
-
const importExpr = UAST.importExpression(from, alias);
|
|
1107
|
-
imports.push(appendNodeMeta(importExpr, child, sourcefile));
|
|
1108
|
-
} else if (child.type === 'namespace_use_group') {
|
|
1109
|
-
for (const groupChild of child.namedChildren) {
|
|
1110
|
-
if (groupChild.type === 'namespace_use_group_clause') {
|
|
1111
|
-
const gcName = groupChild.namedChildren[0];
|
|
1112
|
-
const from = UAST.literal(gcName ? gcName.text : '', 'string');
|
|
1113
|
-
const gcAlias = groupChild.namedChildren.length > 1 ? groupChild.namedChildren[1] : null;
|
|
1114
|
-
const alias = gcAlias ? visit(gcAlias, opts) as UAST.Identifier : undefined;
|
|
1115
|
-
const importExpr = UAST.importExpression(from, alias);
|
|
1116
|
-
imports.push(appendNodeMeta(importExpr, groupChild, sourcefile));
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
if (imports.length === 1) {
|
|
1122
|
-
return UAST.expressionStatement(imports[0]);
|
|
1123
|
-
}
|
|
1124
|
-
if (imports.length === 0) return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
1125
|
-
return appendNodeMeta(UAST.sequence(imports.map((i: any) => UAST.expressionStatement(i))), node, sourcefile);
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
case 'global_declaration': {
|
|
1129
|
-
const declarations = node.namedChildren.map((child) => {
|
|
1130
|
-
const id = visit(child, opts) as UAST.LVal;
|
|
1131
|
-
const decl = UAST.variableDeclaration(id, null, false, UAST.dynamicType());
|
|
1132
|
-
decl._meta.storage = 'global';
|
|
1133
|
-
return appendNodeMeta(decl, child, sourcefile);
|
|
1134
|
-
});
|
|
1135
|
-
if (declarations.length === 1) return declarations[0];
|
|
1136
|
-
if (declarations.length === 0) return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
1137
|
-
return appendNodeMeta(UAST.sequence(declarations), node, sourcefile);
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
case 'function_static_declaration': {
|
|
1141
|
-
const declarations: Array<any> = [];
|
|
1142
|
-
for (const child of node.namedChildren) {
|
|
1143
|
-
if (child.type === 'static_variable_declaration') {
|
|
1144
|
-
const varName = child.namedChildren[0];
|
|
1145
|
-
const varInit = child.namedChildren.length > 1 ? child.namedChildren[1] : null;
|
|
1146
|
-
const id = varName ? visit(varName, opts) as UAST.LVal : UAST.identifier('_');
|
|
1147
|
-
const init = varInit ? visit(varInit, opts) as UAST.Expression : null;
|
|
1148
|
-
const decl = UAST.variableDeclaration(id, init, false, UAST.dynamicType());
|
|
1149
|
-
decl._meta.storage = 'static';
|
|
1150
|
-
declarations.push(appendNodeMeta(decl, child, sourcefile));
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
if (declarations.length === 1) return declarations[0];
|
|
1154
|
-
if (declarations.length === 0) return appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
1155
|
-
return appendNodeMeta(UAST.sequence(declarations), node, sourcefile);
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
case 'declare_statement': {
|
|
1159
|
-
const directives: Array<{ key: any; value: any }> = [];
|
|
1160
|
-
let declareBody: UAST.Instruction | null = null;
|
|
1161
|
-
for (const child of node.namedChildren) {
|
|
1162
|
-
if (child.type === 'declare_directive') {
|
|
1163
|
-
const key = child.namedChildren[0] ? visit(child.namedChildren[0], opts) : null;
|
|
1164
|
-
const value = child.namedChildren[1] ? visit(child.namedChildren[1], opts) : null;
|
|
1165
|
-
directives.push({ key, value });
|
|
1166
|
-
} else if (child.type === 'compound_statement' || child.type === 'colon_block') {
|
|
1167
|
-
declareBody = visit(child, opts) as UAST.Instruction;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
const result = declareBody || appendNodeMeta(UAST.noop(), node, sourcefile);
|
|
1171
|
-
if (result && UAST.isNode(result)) {
|
|
1172
|
-
result._meta.declare = { directives };
|
|
1173
|
-
}
|
|
1174
|
-
return appendNodeMeta(result, node, sourcefile);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
// ─── 辅助节点类型 ───
|
|
1178
|
-
|
|
1179
|
-
case 'named_type': {
|
|
1180
|
-
const child = node.namedChildren[0];
|
|
1181
|
-
return child ? visit(child, opts) : appendNodeMeta(UAST.identifier(node.text), node, sourcefile);
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
case 'colon_block': {
|
|
1185
|
-
const body = flattenInstructions(visitList(node.namedChildren, opts));
|
|
1186
|
-
return appendNodeMeta(UAST.scopedStatement(body), node, sourcefile);
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
default: {
|
|
1190
|
-
const noop = UAST.noop();
|
|
1191
|
-
return appendNodeMeta(noop, node, sourcefile);
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
function sanitize(node: any) {
|
|
1197
|
-
if (!node) {
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
if (Array.isArray(node)) {
|
|
1202
|
-
node.forEach((item: any) => sanitize(item));
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
if (!node.type) {
|
|
1207
|
-
return;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
if (node.loc) {
|
|
1211
|
-
Object.keys(node.loc).forEach((key: string) => {
|
|
1212
|
-
if (!['start', 'end', 'sourcefile'].includes(key)) {
|
|
1213
|
-
delete node.loc[key];
|
|
1214
|
-
}
|
|
1215
|
-
});
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
Object.keys(node).forEach((key: string) => sanitize(node[key]));
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
export function parse(content: string, opts: Record<string, any> = {}): ParseResult<UAST.Node> {
|
|
1222
|
-
const parser = getParser();
|
|
1223
|
-
const tree = parser.parse(content);
|
|
1224
|
-
if (!tree) {
|
|
1225
|
-
throw new Error('Failed to parse PHP code');
|
|
1226
|
-
}
|
|
1227
|
-
try {
|
|
1228
|
-
const node = visit(tree.rootNode, opts);
|
|
1229
|
-
sanitize(node);
|
|
1230
|
-
return node;
|
|
1231
|
-
} finally {
|
|
1232
|
-
tree.delete();
|
|
1233
|
-
}
|
|
1234
|
-
}
|