1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of PDepend. |
4
|
|
|
* |
5
|
|
|
* PHP Version 5 |
6
|
|
|
* |
7
|
|
|
* Copyright (c) 2008-2017 Manuel Pichler <[email protected]>. |
8
|
|
|
* All rights reserved. |
9
|
|
|
* |
10
|
|
|
* Redistribution and use in source and binary forms, with or without |
11
|
|
|
* modification, are permitted provided that the following conditions |
12
|
|
|
* are met: |
13
|
|
|
* |
14
|
|
|
* * Redistributions of source code must retain the above copyright |
15
|
|
|
* notice, this list of conditions and the following disclaimer. |
16
|
|
|
* |
17
|
|
|
* * Redistributions in binary form must reproduce the above copyright |
18
|
|
|
* notice, this list of conditions and the following disclaimer in |
19
|
|
|
* the documentation and/or other materials provided with the |
20
|
|
|
* distribution. |
21
|
|
|
* |
22
|
|
|
* * Neither the name of Manuel Pichler nor the names of his |
23
|
|
|
* contributors may be used to endorse or promote products derived |
24
|
|
|
* from this software without specific prior written permission. |
25
|
|
|
* |
26
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
27
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
28
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
29
|
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
30
|
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
31
|
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
32
|
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
33
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
34
|
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
35
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
36
|
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
37
|
|
|
* POSSIBILITY OF SUCH DAMAGE. |
38
|
|
|
* |
39
|
|
|
* @copyright 2008-2017 Manuel Pichler. All rights reserved. |
40
|
|
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License |
41
|
|
|
* |
42
|
|
|
* @since 2.3 |
43
|
|
|
*/ |
44
|
|
|
|
45
|
|
|
namespace PDepend\Source\Language\PHP; |
46
|
|
|
|
47
|
|
|
use PDepend\Source\AST\ASTArguments; |
48
|
|
|
use PDepend\Source\AST\ASTConstant; |
49
|
|
|
use PDepend\Source\AST\ASTNamedArgument; |
50
|
|
|
use PDepend\Source\AST\ASTNode; |
51
|
|
|
use PDepend\Source\AST\ASTValue; |
52
|
|
|
use PDepend\Source\Parser\UnexpectedTokenException; |
53
|
|
|
use PDepend\Source\Tokenizer\FullTokenizer; |
54
|
|
|
use PDepend\Source\Tokenizer\Tokenizer; |
55
|
|
|
use PDepend\Source\Tokenizer\Tokens; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Concrete parser implementation that supports features up to PHP version 5.6. |
59
|
|
|
* |
60
|
|
|
* @copyright 2008-2017 Manuel Pichler. All rights reserved. |
61
|
|
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License |
62
|
|
|
* |
63
|
|
|
* @since 2.3 |
64
|
|
|
*/ |
65
|
|
|
abstract class PHPParserVersion56 extends PHPParserVersion55 |
66
|
|
|
{ |
67
|
|
|
/** |
68
|
|
|
* Parses additional static values that are valid in the supported php version. |
69
|
|
|
* |
70
|
|
|
* @throws UnexpectedTokenException |
71
|
|
|
* |
72
|
|
|
* @return ASTValue|null |
73
|
|
|
*/ |
74
|
|
|
protected function parseStaticValueVersionSpecific(ASTValue $value) |
75
|
|
|
{ |
76
|
|
|
$expressions = array(); |
77
|
|
|
|
78
|
|
|
while (($tokenType = $this->tokenizer->peek()) != Tokenizer::T_EOF) { |
79
|
|
|
switch ($tokenType) { |
80
|
|
|
case Tokens::T_COMMA: |
81
|
|
|
case Tokens::T_CLOSE_TAG: |
82
|
|
|
case Tokens::T_COLON: |
83
|
|
|
case Tokens::T_DOUBLE_ARROW: |
84
|
|
|
case Tokens::T_END_HEREDOC: |
85
|
|
|
case Tokens::T_PARENTHESIS_CLOSE: |
86
|
|
|
case Tokens::T_SEMICOLON: |
87
|
|
|
case Tokens::T_SQUARED_BRACKET_CLOSE: |
88
|
|
|
break 2; |
89
|
|
|
case Tokens::T_SELF: |
90
|
|
|
case Tokens::T_STRING: |
91
|
|
|
case Tokens::T_PARENT: |
92
|
|
|
case Tokens::T_STATIC: |
93
|
|
|
case Tokens::T_DOLLAR: |
94
|
|
|
case Tokens::T_VARIABLE: |
95
|
|
|
case Tokens::T_BACKSLASH: |
96
|
|
|
case Tokens::T_NAMESPACE: |
97
|
|
|
$expressions[] = $this->parseVariableOrConstantOrPrimaryPrefix(); |
98
|
|
|
break; |
99
|
|
|
case ($this->isArrayStartDelimiter()): |
100
|
|
|
$expressions[] = $this->doParseArray(true); |
101
|
|
|
break; |
102
|
|
|
case Tokens::T_NULL: |
103
|
|
|
case Tokens::T_TRUE: |
104
|
|
|
case Tokens::T_FALSE: |
105
|
|
|
case Tokens::T_LNUMBER: |
106
|
|
|
case Tokens::T_DNUMBER: |
107
|
|
|
case Tokens::T_BACKTICK: |
108
|
|
|
case Tokens::T_DOUBLE_QUOTE: |
109
|
|
|
case Tokens::T_CONSTANT_ENCAPSED_STRING: |
110
|
|
|
$expressions[] = $this->parseLiteralOrString(); |
111
|
|
|
break; |
112
|
|
|
case Tokens::T_QUESTION_MARK: |
113
|
|
|
$expressions[] = $this->parseConditionalExpression(); |
114
|
|
|
break; |
115
|
|
|
case Tokens::T_BOOLEAN_AND: |
116
|
|
|
$expressions[] = $this->parseBooleanAndExpression(); |
117
|
|
|
break; |
118
|
|
|
case Tokens::T_BOOLEAN_OR: |
119
|
|
|
$expressions[] = $this->parseBooleanOrExpression(); |
120
|
|
|
break; |
121
|
|
|
case Tokens::T_LOGICAL_AND: |
122
|
|
|
$expressions[] = $this->parseLogicalAndExpression(); |
123
|
|
|
break; |
124
|
|
|
case Tokens::T_LOGICAL_OR: |
125
|
|
|
$expressions[] = $this->parseLogicalOrExpression(); |
126
|
|
|
break; |
127
|
|
|
case Tokens::T_LOGICAL_XOR: |
128
|
|
|
$expressions[] = $this->parseLogicalXorExpression(); |
129
|
|
|
break; |
130
|
|
|
case Tokens::T_PARENTHESIS_OPEN: |
131
|
|
|
$expressions[] = $this->parseParenthesisExpressionOrPrimaryPrefix(); |
132
|
|
|
break; |
133
|
|
|
case Tokens::T_START_HEREDOC: |
134
|
|
|
$expressions[] = $this->parseHeredoc(); |
135
|
|
|
break; |
136
|
|
|
case Tokens::T_SL: |
137
|
|
|
$expressions[] = $this->parseShiftLeftExpression(); |
138
|
|
|
break; |
139
|
|
|
case Tokens::T_SR: |
140
|
|
|
$expressions[] = $this->parseShiftRightExpression(); |
141
|
|
|
break; |
142
|
|
|
case Tokens::T_ELLIPSIS: |
143
|
|
|
$this->checkEllipsisInExpressionSupport(); |
144
|
|
|
// no break |
145
|
|
|
case Tokens::T_STRING_VARNAME: // TODO: Implement this |
146
|
|
|
case Tokens::T_PLUS: // TODO: Make this a arithmetic expression |
147
|
|
|
case Tokens::T_MINUS: |
148
|
|
|
case Tokens::T_MUL: |
149
|
|
|
case Tokens::T_DIV: |
150
|
|
|
case Tokens::T_MOD: |
151
|
|
|
case Tokens::T_POW: |
152
|
|
|
case Tokens::T_IS_EQUAL: // TODO: Implement compare expressions |
153
|
|
|
case Tokens::T_IS_NOT_EQUAL: |
154
|
|
|
case Tokens::T_IS_IDENTICAL: |
155
|
|
|
case Tokens::T_IS_NOT_IDENTICAL: |
156
|
|
|
case Tokens::T_BITWISE_OR: |
157
|
|
|
case Tokens::T_BITWISE_AND: |
158
|
|
|
case Tokens::T_BITWISE_NOT: |
159
|
|
|
case Tokens::T_BITWISE_XOR: |
160
|
|
|
case Tokens::T_IS_GREATER_OR_EQUAL: |
161
|
|
|
case Tokens::T_IS_SMALLER_OR_EQUAL: |
162
|
|
|
case Tokens::T_ANGLE_BRACKET_OPEN: |
163
|
|
|
case Tokens::T_ANGLE_BRACKET_CLOSE: |
164
|
|
|
case Tokens::T_EMPTY: |
165
|
|
|
case Tokens::T_CONCAT: |
166
|
|
|
$token = $this->consumeToken($tokenType); |
167
|
|
|
|
168
|
|
|
$expr = $this->builder->buildAstExpression($token->image); |
169
|
|
|
$expr->configureLinesAndColumns( |
170
|
|
|
$token->startLine, |
171
|
|
|
$token->endLine, |
172
|
|
|
$token->startColumn, |
173
|
|
|
$token->endColumn |
174
|
|
|
); |
175
|
|
|
|
176
|
|
|
$expressions[] = $expr; |
177
|
|
|
break; |
178
|
|
|
case Tokens::T_EQUAL: |
179
|
|
|
case Tokens::T_OR_EQUAL: |
180
|
|
|
case Tokens::T_SL_EQUAL: |
181
|
|
|
case Tokens::T_SR_EQUAL: |
182
|
|
|
case Tokens::T_AND_EQUAL: |
183
|
|
|
case Tokens::T_DIV_EQUAL: |
184
|
|
|
case Tokens::T_MOD_EQUAL: |
185
|
|
|
case Tokens::T_MUL_EQUAL: |
186
|
|
|
case Tokens::T_XOR_EQUAL: |
187
|
|
|
case Tokens::T_PLUS_EQUAL: |
188
|
|
|
case Tokens::T_MINUS_EQUAL: |
189
|
|
|
case Tokens::T_CONCAT_EQUAL: |
190
|
|
|
case Tokens::T_COALESCE_EQUAL: |
191
|
|
|
$expressions[] = $this->parseAssignmentExpression( |
192
|
|
|
array_pop($expressions) |
193
|
|
|
); |
194
|
|
|
break; |
195
|
|
|
case Tokens::T_DIR: |
196
|
|
|
case Tokens::T_FILE: |
197
|
|
|
case Tokens::T_LINE: |
198
|
|
|
case Tokens::T_NS_C: |
199
|
|
|
case Tokens::T_FUNC_C: |
200
|
|
|
case Tokens::T_CLASS_C: |
201
|
|
|
case Tokens::T_METHOD_C: |
202
|
|
|
$expressions[] = $this->parseConstant(); |
203
|
|
|
break; |
204
|
|
|
// TODO: Handle comments here |
205
|
|
|
case Tokens::T_COMMENT: |
206
|
|
|
case Tokens::T_DOC_COMMENT: |
207
|
|
|
$this->consumeToken($tokenType); |
208
|
|
|
break; |
209
|
|
|
case Tokens::T_AT: |
210
|
|
|
case Tokens::T_EXCLAMATION_MARK: |
211
|
|
|
$token = $this->consumeToken($tokenType); |
212
|
|
|
|
213
|
|
|
$expr = $this->builder->buildAstUnaryExpression($token->image); |
214
|
|
|
$expr->configureLinesAndColumns( |
215
|
|
|
$token->startLine, |
216
|
|
|
$token->endLine, |
217
|
|
|
$token->startColumn, |
218
|
|
|
$token->endColumn |
219
|
|
|
); |
220
|
|
|
|
221
|
|
|
$expressions[] = $expr; |
222
|
|
|
break; |
223
|
|
|
default: |
224
|
|
|
throw $this->getUnexpectedNextTokenException(); |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
$expressions = $this->reduce($expressions); |
229
|
|
|
|
230
|
|
|
$count = count($expressions); |
231
|
|
|
if ($count == 0) { |
232
|
|
|
return null; |
233
|
|
|
} elseif ($count == 1) { |
234
|
|
|
// @todo ASTValue must be a valid node. |
235
|
|
|
$value->setValue($expressions[0]); |
236
|
|
|
|
237
|
|
|
return $value; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
$expr = $this->builder->buildAstExpression(); |
241
|
|
|
foreach ($expressions as $node) { |
242
|
|
|
$expr->addChild($node); |
243
|
|
|
} |
244
|
|
|
$expr->configureLinesAndColumns( |
245
|
|
|
$expressions[0]->getStartLine(), |
246
|
|
|
$expressions[$count - 1]->getEndLine(), |
247
|
|
|
$expressions[0]->getStartColumn(), |
248
|
|
|
$expressions[$count - 1]->getEndColumn() |
249
|
|
|
); |
250
|
|
|
|
251
|
|
|
// @todo ASTValue must be a valid node. |
252
|
|
|
$value->setValue($expr); |
253
|
|
|
|
254
|
|
|
return $value; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Parses use declarations that are valid in the supported php version. |
259
|
|
|
* |
260
|
|
|
* @return void |
261
|
|
|
*/ |
262
|
|
|
protected function parseUseDeclarations() |
263
|
|
|
{ |
264
|
|
|
// Consume use keyword |
265
|
|
|
$this->consumeToken(Tokens::T_USE); |
266
|
|
|
$this->consumeComments(); |
267
|
|
|
|
268
|
|
|
// Consume const and function tokens |
269
|
|
|
$nextToken = $this->tokenizer->peek(); |
270
|
|
|
switch ($nextToken) { |
271
|
|
|
case Tokens::T_CONST: |
272
|
|
|
case Tokens::T_FUNCTION: |
273
|
|
|
$this->consumeToken($nextToken); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
// Parse all use declarations |
277
|
|
|
$this->parseUseDeclaration(); |
278
|
|
|
$this->consumeComments(); |
279
|
|
|
|
280
|
|
|
// Consume closing semicolon |
281
|
|
|
$this->consumeToken(Tokens::T_SEMICOLON); |
282
|
|
|
|
283
|
|
|
// Reset any previous state |
284
|
|
|
$this->reset(); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* This method will be called when the base parser cannot handle an expression |
289
|
|
|
* in the base version. In this method you can implement version specific |
290
|
|
|
* expressions. |
291
|
|
|
* |
292
|
|
|
* @throws UnexpectedTokenException |
293
|
|
|
* |
294
|
|
|
* @return ASTNode |
295
|
|
|
* |
296
|
|
|
* @since 2.2 |
297
|
|
|
*/ |
298
|
|
|
protected function parseOptionalExpressionForVersion() |
299
|
|
|
{ |
300
|
|
|
if ($expression = $this->parseExpressionVersion56()) { |
301
|
|
|
return $expression; |
302
|
|
|
} |
303
|
|
|
return parent::parseOptionalExpressionForVersion(); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* In this method we implement parsing of PHP 5.6 specific expressions. |
308
|
|
|
* |
309
|
|
|
* @return ASTNode|null |
310
|
|
|
* |
311
|
|
|
* @since 2.3 |
312
|
|
|
*/ |
313
|
|
|
protected function parseExpressionVersion56() |
314
|
|
|
{ |
315
|
|
|
$this->consumeComments(); |
316
|
|
|
$nextTokenType = $this->tokenizer->peek(); |
317
|
|
|
|
318
|
|
|
switch ($nextTokenType) { |
319
|
|
|
case Tokens::T_POW: |
320
|
|
|
$token = $this->consumeToken($nextTokenType); |
321
|
|
|
|
322
|
|
|
$expr = $this->builder->buildAstExpression($token->image); |
323
|
|
|
$expr->configureLinesAndColumns( |
324
|
|
|
$token->startLine, |
325
|
|
|
$token->endLine, |
326
|
|
|
$token->startColumn, |
327
|
|
|
$token->endColumn |
328
|
|
|
); |
329
|
|
|
|
330
|
|
|
return $expr; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
return null; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* @return ASTConstant|ASTNamedArgument |
338
|
|
|
*/ |
339
|
|
|
protected function parseConstantArgument(ASTConstant $constant, ASTArguments $arguments) |
|
|
|
|
340
|
|
|
{ |
341
|
|
|
return $constant; |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* @return ASTArguments |
346
|
|
|
*/ |
347
|
|
|
protected function parseArgumentList(ASTArguments $arguments) |
348
|
|
|
{ |
349
|
|
|
while (true) { |
350
|
|
|
$this->consumeComments(); |
351
|
|
|
|
352
|
|
|
if (Tokens::T_ELLIPSIS === $this->tokenizer->peek()) { |
353
|
|
|
$this->consumeToken(Tokens::T_ELLIPSIS); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
$expr = $this->parseArgumentExpression(); |
357
|
|
|
|
358
|
|
|
if ($expr instanceof ASTConstant) { |
359
|
|
|
$expr = $this->parseConstantArgument($expr, $arguments); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
if (!$expr || !$this->addChildToList($arguments, $expr)) { |
363
|
|
|
break; |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
return $arguments; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* @return ASTNode|null |
372
|
|
|
*/ |
373
|
|
|
protected function parseArgumentExpression() |
374
|
|
|
{ |
375
|
|
|
return $this->parseOptionalExpression(); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Parses the value of a php constant. By default this can be only static |
380
|
|
|
* values that were allowed in the oldest supported PHP version. |
381
|
|
|
* |
382
|
|
|
* @return ASTValue |
383
|
|
|
*/ |
384
|
|
|
protected function parseConstantDeclaratorValue() |
385
|
|
|
{ |
386
|
|
|
if ($this->isFollowedByStaticValueOrStaticArray()) { |
387
|
|
|
return $this->parseVariableDefaultValue(); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
// Else it would be provided as ASTLiteral or expressions object. |
391
|
|
|
$value = new ASTValue(); |
392
|
|
|
$value->setValue($this->parseOptionalExpression()); |
393
|
|
|
|
394
|
|
|
return $value; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Determines if the following expression can be stored as a static value. |
399
|
|
|
* |
400
|
|
|
* @return bool |
401
|
|
|
*/ |
402
|
|
|
protected function isFollowedByStaticValueOrStaticArray() |
403
|
|
|
{ |
404
|
|
|
// If we can't anticipate, we should assume it can be a dynamic value |
405
|
|
|
if (!($this->tokenizer instanceof FullTokenizer)) { |
406
|
|
|
return false; |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
for ($i = 0; $type = $this->tokenizer->peekAt($i); $i++) { |
410
|
|
|
switch ($type) { |
411
|
|
|
case Tokens::T_COMMENT: |
412
|
|
|
case Tokens::T_DOC_COMMENT: |
413
|
|
|
case Tokens::T_ARRAY: |
414
|
|
|
case Tokens::T_SQUARED_BRACKET_OPEN: |
415
|
|
|
case Tokens::T_SQUARED_BRACKET_CLOSE: |
416
|
|
|
case Tokens::T_PARENTHESIS_OPEN: |
417
|
|
|
case Tokens::T_PARENTHESIS_CLOSE: |
418
|
|
|
case Tokens::T_COMMA: |
419
|
|
|
case Tokens::T_DOUBLE_ARROW: |
420
|
|
|
case Tokens::T_NULL: |
421
|
|
|
case Tokens::T_TRUE: |
422
|
|
|
case Tokens::T_FALSE: |
423
|
|
|
case Tokens::T_LNUMBER: |
424
|
|
|
case Tokens::T_DNUMBER: |
425
|
|
|
case Tokens::T_STRING: |
426
|
|
|
case Tokens::T_EQUAL: |
427
|
|
|
case Tokens::T_START_HEREDOC: |
428
|
|
|
case Tokens::T_END_HEREDOC: |
429
|
|
|
case Tokens::T_ENCAPSED_AND_WHITESPACE: |
430
|
|
|
break; |
431
|
|
|
|
432
|
|
|
case Tokens::T_SEMICOLON: |
433
|
|
|
case Tokenizer::T_EOF: |
434
|
|
|
return true; |
435
|
|
|
|
436
|
|
|
default: |
437
|
|
|
return false; |
438
|
|
|
} |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
return false; |
442
|
|
|
} |
443
|
|
|
} |
444
|
|
|
|
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.