| Total Complexity | 111 |
| Total Lines | 728 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like TokensAnalyzer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TokensAnalyzer, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 31 | final class TokensAnalyzer |
||
| 32 | { |
||
| 33 | /** |
||
| 34 | * Tokens collection instance. |
||
| 35 | * |
||
| 36 | * @var Tokens |
||
| 37 | */ |
||
| 38 | private $tokens; |
||
| 39 | |||
| 40 | /** |
||
| 41 | * @var ?GotoLabelAnalyzer |
||
| 42 | */ |
||
| 43 | private $gotoLabelAnalyzer; |
||
| 44 | |||
| 45 | public function __construct(Tokens $tokens) |
||
| 46 | { |
||
| 47 | $this->tokens = $tokens; |
||
| 48 | } |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Get indexes of methods and properties in classy code (classes, interfaces and traits). |
||
| 52 | * |
||
| 53 | * @return array[] |
||
| 54 | */ |
||
| 55 | public function getClassyElements(): array |
||
| 56 | { |
||
| 57 | $elements = []; |
||
| 58 | |||
| 59 | for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) { |
||
| 60 | if ($this->tokens[$index]->isClassy()) { |
||
| 61 | [$index, $newElements] = $this->findClassyElements($index, $index); |
||
| 62 | $elements += $newElements; |
||
| 63 | } |
||
| 64 | } |
||
| 65 | |||
| 66 | ksort($elements); |
||
| 67 | |||
| 68 | return $elements; |
||
| 69 | } |
||
| 70 | |||
| 71 | /** |
||
| 72 | * Get indexes of namespace uses. |
||
| 73 | * |
||
| 74 | * @param bool $perNamespace Return namespace uses per namespace |
||
| 75 | * |
||
| 76 | * @return int[]|int[][] |
||
| 77 | */ |
||
| 78 | public function getImportUseIndexes(bool $perNamespace = false): array |
||
| 79 | { |
||
| 80 | $tokens = $this->tokens; |
||
| 81 | |||
| 82 | $uses = []; |
||
| 83 | $namespaceIndex = 0; |
||
| 84 | |||
| 85 | for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) { |
||
| 86 | $token = $tokens[$index]; |
||
| 87 | |||
| 88 | if ($token->isGivenKind(T_NAMESPACE)) { |
||
| 89 | $nextTokenIndex = $tokens->getNextTokenOfKind($index, [';', '{']); |
||
| 90 | $nextToken = $tokens[$nextTokenIndex]; |
||
| 91 | |||
| 92 | if ($nextToken->equals('{')) { |
||
| 93 | $index = $nextTokenIndex; |
||
| 94 | } |
||
| 95 | |||
| 96 | if ($perNamespace) { |
||
| 97 | ++$namespaceIndex; |
||
| 98 | } |
||
| 99 | |||
| 100 | continue; |
||
| 101 | } |
||
| 102 | |||
| 103 | if ($token->isGivenKind(T_USE)) { |
||
| 104 | $uses[$namespaceIndex][] = $index; |
||
| 105 | } |
||
| 106 | } |
||
| 107 | |||
| 108 | if (!$perNamespace && isset($uses[$namespaceIndex])) { |
||
| 109 | return $uses[$namespaceIndex]; |
||
| 110 | } |
||
| 111 | |||
| 112 | return $uses; |
||
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Check if there is an array at given index. |
||
| 117 | */ |
||
| 118 | public function isArray(int $index): bool |
||
| 119 | { |
||
| 120 | return $this->tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]); |
||
| 121 | } |
||
| 122 | |||
| 123 | /** |
||
| 124 | * Check if the array at index is multiline. |
||
| 125 | * |
||
| 126 | * This only checks the root-level of the array. |
||
| 127 | */ |
||
| 128 | public function isArrayMultiLine(int $index): bool |
||
| 129 | { |
||
| 130 | if (!$this->isArray($index)) { |
||
| 131 | throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index)); |
||
| 132 | } |
||
| 133 | |||
| 134 | $tokens = $this->tokens; |
||
| 135 | |||
| 136 | // Skip only when its an array, for short arrays we need the brace for correct |
||
| 137 | // level counting |
||
| 138 | if ($tokens[$index]->isGivenKind(T_ARRAY)) { |
||
| 139 | $index = $tokens->getNextMeaningfulToken($index); |
||
| 140 | } |
||
| 141 | |||
| 142 | return $this->isBlockMultiline($tokens, $index); |
||
|
|
|||
| 143 | } |
||
| 144 | |||
| 145 | public function isBlockMultiline(Tokens $tokens, int $index): bool |
||
| 146 | { |
||
| 147 | $blockType = Tokens::detectBlockType($tokens[$index]); |
||
| 148 | |||
| 149 | if (null === $blockType || !$blockType['isStart']) { |
||
| 150 | throw new \InvalidArgumentException(sprintf('Not an block start at given index %d.', $index)); |
||
| 151 | } |
||
| 152 | |||
| 153 | $endIndex = $tokens->findBlockEnd($blockType['type'], $index); |
||
| 154 | |||
| 155 | for (++$index; $index < $endIndex; ++$index) { |
||
| 156 | $token = $tokens[$index]; |
||
| 157 | $blockType = Tokens::detectBlockType($token); |
||
| 158 | |||
| 159 | if ($blockType && $blockType['isStart']) { |
||
| 160 | $index = $tokens->findBlockEnd($blockType['type'], $index); |
||
| 161 | |||
| 162 | continue; |
||
| 163 | } |
||
| 164 | |||
| 165 | if ( |
||
| 166 | $token->isWhitespace() |
||
| 167 | && !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC) |
||
| 168 | && false !== strpos($token->getContent(), "\n") |
||
| 169 | ) { |
||
| 170 | return true; |
||
| 171 | } |
||
| 172 | } |
||
| 173 | |||
| 174 | return false; |
||
| 175 | } |
||
| 176 | |||
| 177 | /** |
||
| 178 | * Returns the attributes of the method under the given index. |
||
| 179 | * |
||
| 180 | * The array has the following items: |
||
| 181 | * 'visibility' int|null T_PRIVATE, T_PROTECTED or T_PUBLIC |
||
| 182 | * 'static' bool |
||
| 183 | * 'abstract' bool |
||
| 184 | * 'final' bool |
||
| 185 | * |
||
| 186 | * @param int $index Token index of the method (T_FUNCTION) |
||
| 187 | */ |
||
| 188 | public function getMethodAttributes(int $index): array |
||
| 189 | { |
||
| 190 | $tokens = $this->tokens; |
||
| 191 | $token = $tokens[$index]; |
||
| 192 | |||
| 193 | if (!$token->isGivenKind(T_FUNCTION)) { |
||
| 194 | throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got "%s".', $index, $token->getName())); |
||
| 195 | } |
||
| 196 | |||
| 197 | $attributes = [ |
||
| 198 | 'visibility' => null, |
||
| 199 | 'static' => false, |
||
| 200 | 'abstract' => false, |
||
| 201 | 'final' => false, |
||
| 202 | ]; |
||
| 203 | |||
| 204 | for ($i = $index; $i >= 0; --$i) { |
||
| 205 | $tokenIndex = $tokens->getPrevMeaningfulToken($i); |
||
| 206 | |||
| 207 | $i = $tokenIndex; |
||
| 208 | $token = $tokens[$tokenIndex]; |
||
| 209 | |||
| 210 | if ($token->isGivenKind(T_STATIC)) { |
||
| 211 | $attributes['static'] = true; |
||
| 212 | |||
| 213 | continue; |
||
| 214 | } |
||
| 215 | |||
| 216 | if ($token->isGivenKind(T_FINAL)) { |
||
| 217 | $attributes['final'] = true; |
||
| 218 | |||
| 219 | continue; |
||
| 220 | } |
||
| 221 | |||
| 222 | if ($token->isGivenKind(T_ABSTRACT)) { |
||
| 223 | $attributes['abstract'] = true; |
||
| 224 | |||
| 225 | continue; |
||
| 226 | } |
||
| 227 | |||
| 228 | // visibility |
||
| 229 | |||
| 230 | if ($token->isGivenKind(T_PRIVATE)) { |
||
| 231 | $attributes['visibility'] = T_PRIVATE; |
||
| 232 | |||
| 233 | continue; |
||
| 234 | } |
||
| 235 | |||
| 236 | if ($token->isGivenKind(T_PROTECTED)) { |
||
| 237 | $attributes['visibility'] = T_PROTECTED; |
||
| 238 | |||
| 239 | continue; |
||
| 240 | } |
||
| 241 | |||
| 242 | if ($token->isGivenKind(T_PUBLIC)) { |
||
| 243 | $attributes['visibility'] = T_PUBLIC; |
||
| 244 | |||
| 245 | continue; |
||
| 246 | } |
||
| 247 | |||
| 248 | // found a meaningful token that is not part of |
||
| 249 | // the function signature; stop looking |
||
| 250 | break; |
||
| 251 | } |
||
| 252 | |||
| 253 | return $attributes; |
||
| 254 | } |
||
| 255 | |||
| 256 | /** |
||
| 257 | * Check if there is an anonymous class under given index. |
||
| 258 | */ |
||
| 259 | public function isAnonymousClass(int $index): bool |
||
| 260 | { |
||
| 261 | if (!$this->tokens[$index]->isClassy()) { |
||
| 262 | throw new \LogicException(sprintf('No classy token at given index %d.', $index)); |
||
| 263 | } |
||
| 264 | |||
| 265 | if (!$this->tokens[$index]->isGivenKind(T_CLASS)) { |
||
| 266 | return false; |
||
| 267 | } |
||
| 268 | |||
| 269 | return $this->tokens[$this->tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NEW); |
||
| 270 | } |
||
| 271 | |||
| 272 | /** |
||
| 273 | * Check if the function under given index is a lambda. |
||
| 274 | */ |
||
| 275 | public function isLambda(int $index): bool |
||
| 276 | { |
||
| 277 | if ( |
||
| 278 | !$this->tokens[$index]->isGivenKind(T_FUNCTION) |
||
| 279 | && (\PHP_VERSION_ID < 70400 || !$this->tokens[$index]->isGivenKind(T_FN)) |
||
| 280 | ) { |
||
| 281 | throw new \LogicException(sprintf('No T_FUNCTION or T_FN at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); |
||
| 282 | } |
||
| 283 | |||
| 284 | $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($index); |
||
| 285 | $startParenthesisToken = $this->tokens[$startParenthesisIndex]; |
||
| 286 | |||
| 287 | // skip & for `function & () {}` syntax |
||
| 288 | if ($startParenthesisToken->isGivenKind(CT::T_RETURN_REF)) { |
||
| 289 | $startParenthesisIndex = $this->tokens->getNextMeaningfulToken($startParenthesisIndex); |
||
| 290 | $startParenthesisToken = $this->tokens[$startParenthesisIndex]; |
||
| 291 | } |
||
| 292 | |||
| 293 | return $startParenthesisToken->equals('('); |
||
| 294 | } |
||
| 295 | |||
| 296 | /** |
||
| 297 | * Check if the T_STRING under given index is a constant invocation. |
||
| 298 | */ |
||
| 299 | public function isConstantInvocation(int $index): bool |
||
| 300 | { |
||
| 301 | if (!$this->tokens[$index]->isGivenKind(T_STRING)) { |
||
| 302 | throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $this->tokens[$index]->getName())); |
||
| 303 | } |
||
| 304 | |||
| 305 | $nextIndex = $this->tokens->getNextMeaningfulToken($index); |
||
| 306 | |||
| 307 | if ( |
||
| 308 | $this->tokens[$nextIndex]->equalsAny(['(', '{']) |
||
| 309 | || $this->tokens[$nextIndex]->isGivenKind([T_AS, T_DOUBLE_COLON, T_ELLIPSIS, T_NS_SEPARATOR, CT::T_RETURN_REF, CT::T_TYPE_ALTERNATION, T_VARIABLE]) |
||
| 310 | ) { |
||
| 311 | return false; |
||
| 312 | } |
||
| 313 | |||
| 314 | $prevIndex = $this->tokens->getPrevMeaningfulToken($index); |
||
| 315 | |||
| 316 | if ($this->tokens[$prevIndex]->isGivenKind([T_AS, T_CLASS, T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_GOTO, CT::T_GROUP_IMPORT_BRACE_OPEN, T_INTERFACE, T_TRAIT, CT::T_TYPE_COLON]) || $this->tokens[$prevIndex]->isObjectOperator()) { |
||
| 317 | return false; |
||
| 318 | } |
||
| 319 | |||
| 320 | while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) { |
||
| 321 | $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); |
||
| 322 | } |
||
| 323 | |||
| 324 | if ($this->tokens[$prevIndex]->isGivenKind([CT::T_CONST_IMPORT, T_EXTENDS, CT::T_FUNCTION_IMPORT, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_NAMESPACE, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_COLON, T_USE, CT::T_USE_TRAIT])) { |
||
| 325 | return false; |
||
| 326 | } |
||
| 327 | |||
| 328 | // `FOO & $bar` could be: |
||
| 329 | // - function reference parameter: function baz(Foo & $bar) {} |
||
| 330 | // - bit operator: $x = FOO & $bar; |
||
| 331 | if ($this->tokens[$nextIndex]->equals('&') && $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(T_VARIABLE)) { |
||
| 332 | $checkIndex = $this->tokens->getPrevTokenOfKind($prevIndex, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]); |
||
| 333 | |||
| 334 | if ($this->tokens[$checkIndex]->isGivenKind(T_FUNCTION)) { |
||
| 335 | return false; |
||
| 336 | } |
||
| 337 | } |
||
| 338 | |||
| 339 | // check for `extends`/`implements`/`use` list |
||
| 340 | if ($this->tokens[$prevIndex]->equals(',')) { |
||
| 341 | $checkIndex = $prevIndex; |
||
| 342 | while ($this->tokens[$checkIndex]->equalsAny([',', [T_AS], [CT::T_NAMESPACE_OPERATOR], [T_NS_SEPARATOR], [T_STRING]])) { |
||
| 343 | $checkIndex = $this->tokens->getPrevMeaningfulToken($checkIndex); |
||
| 344 | } |
||
| 345 | |||
| 346 | if ($this->tokens[$checkIndex]->isGivenKind([T_EXTENDS, CT::T_GROUP_IMPORT_BRACE_OPEN, T_IMPLEMENTS, T_USE, CT::T_USE_TRAIT])) { |
||
| 347 | return false; |
||
| 348 | } |
||
| 349 | } |
||
| 350 | |||
| 351 | // check for array in double quoted string: `"..$foo[bar].."` |
||
| 352 | if ($this->tokens[$prevIndex]->equals('[') && $this->tokens[$nextIndex]->equals(']')) { |
||
| 353 | $checkToken = $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]; |
||
| 354 | |||
| 355 | if ($checkToken->equals('"') || $checkToken->isGivenKind([T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES, T_ENCAPSED_AND_WHITESPACE, T_VARIABLE])) { |
||
| 356 | return false; |
||
| 357 | } |
||
| 358 | } |
||
| 359 | |||
| 360 | // check for attribute: `#[Foo]` |
||
| 361 | if (AttributeAnalyzer::isAttribute($this->tokens, $index)) { |
||
| 362 | return false; |
||
| 363 | } |
||
| 364 | |||
| 365 | // check for goto label |
||
| 366 | if ($this->tokens[$nextIndex]->equals(':')) { |
||
| 367 | if (null === $this->gotoLabelAnalyzer) { |
||
| 368 | $this->gotoLabelAnalyzer = new GotoLabelAnalyzer(); |
||
| 369 | } |
||
| 370 | |||
| 371 | if ($this->gotoLabelAnalyzer->belongsToGoToLabel($this->tokens, $nextIndex)) { |
||
| 372 | return false; |
||
| 373 | } |
||
| 374 | } |
||
| 375 | |||
| 376 | // check for non-capturing catches |
||
| 377 | while ($this->tokens[$prevIndex]->isGivenKind([CT::T_TYPE_ALTERNATION, T_STRING])) { |
||
| 378 | $prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); |
||
| 379 | } |
||
| 380 | |||
| 381 | if ($this->tokens[$prevIndex]->equals('(')) { |
||
| 382 | $prevPrevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex); |
||
| 383 | if ($this->tokens[$prevPrevIndex]->isGivenKind(T_CATCH)) { |
||
| 384 | return false; |
||
| 385 | } |
||
| 386 | } |
||
| 387 | |||
| 388 | return true; |
||
| 389 | } |
||
| 390 | |||
| 391 | /** |
||
| 392 | * Checks if there is an unary successor operator under given index. |
||
| 393 | */ |
||
| 394 | public function isUnarySuccessorOperator(int $index): bool |
||
| 395 | { |
||
| 396 | static $allowedPrevToken = [ |
||
| 397 | ']', |
||
| 398 | [T_STRING], |
||
| 399 | [T_VARIABLE], |
||
| 400 | [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], |
||
| 401 | [CT::T_DYNAMIC_PROP_BRACE_CLOSE], |
||
| 402 | [CT::T_DYNAMIC_VAR_BRACE_CLOSE], |
||
| 403 | ]; |
||
| 404 | |||
| 405 | $tokens = $this->tokens; |
||
| 406 | $token = $tokens[$index]; |
||
| 407 | |||
| 408 | if (!$token->isGivenKind([T_INC, T_DEC])) { |
||
| 409 | return false; |
||
| 410 | } |
||
| 411 | |||
| 412 | $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; |
||
| 413 | |||
| 414 | return $prevToken->equalsAny($allowedPrevToken); |
||
| 415 | } |
||
| 416 | |||
| 417 | /** |
||
| 418 | * Checks if there is an unary predecessor operator under given index. |
||
| 419 | */ |
||
| 420 | public function isUnaryPredecessorOperator(int $index): bool |
||
| 421 | { |
||
| 422 | static $potentialSuccessorOperator = [T_INC, T_DEC]; |
||
| 423 | |||
| 424 | static $potentialBinaryOperator = ['+', '-', '&', [CT::T_RETURN_REF]]; |
||
| 425 | |||
| 426 | static $otherOperators; |
||
| 427 | if (null === $otherOperators) { |
||
| 428 | $otherOperators = ['!', '~', '@', [T_ELLIPSIS]]; |
||
| 429 | } |
||
| 430 | |||
| 431 | static $disallowedPrevTokens; |
||
| 432 | if (null === $disallowedPrevTokens) { |
||
| 433 | $disallowedPrevTokens = [ |
||
| 434 | ']', |
||
| 435 | '}', |
||
| 436 | ')', |
||
| 437 | '"', |
||
| 438 | '`', |
||
| 439 | [CT::T_ARRAY_SQUARE_BRACE_CLOSE], |
||
| 440 | [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE], |
||
| 441 | [CT::T_DYNAMIC_PROP_BRACE_CLOSE], |
||
| 442 | [CT::T_DYNAMIC_VAR_BRACE_CLOSE], |
||
| 443 | [T_CLASS_C], |
||
| 444 | [T_CONSTANT_ENCAPSED_STRING], |
||
| 445 | [T_DEC], |
||
| 446 | [T_DIR], |
||
| 447 | [T_DNUMBER], |
||
| 448 | [T_FILE], |
||
| 449 | [T_FUNC_C], |
||
| 450 | [T_INC], |
||
| 451 | [T_LINE], |
||
| 452 | [T_LNUMBER], |
||
| 453 | [T_METHOD_C], |
||
| 454 | [T_NS_C], |
||
| 455 | [T_STRING], |
||
| 456 | [T_TRAIT_C], |
||
| 457 | [T_VARIABLE], |
||
| 458 | ]; |
||
| 459 | } |
||
| 460 | |||
| 461 | $tokens = $this->tokens; |
||
| 462 | $token = $tokens[$index]; |
||
| 463 | |||
| 464 | if ($token->isGivenKind($potentialSuccessorOperator)) { |
||
| 465 | return !$this->isUnarySuccessorOperator($index); |
||
| 466 | } |
||
| 467 | |||
| 468 | if ($token->equalsAny($otherOperators)) { |
||
| 469 | return true; |
||
| 470 | } |
||
| 471 | |||
| 472 | if (!$token->equalsAny($potentialBinaryOperator)) { |
||
| 473 | return false; |
||
| 474 | } |
||
| 475 | |||
| 476 | $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)]; |
||
| 477 | |||
| 478 | if (!$prevToken->equalsAny($disallowedPrevTokens)) { |
||
| 479 | return true; |
||
| 480 | } |
||
| 481 | |||
| 482 | if (!$token->equals('&') || !$prevToken->isGivenKind(T_STRING)) { |
||
| 483 | return false; |
||
| 484 | } |
||
| 485 | |||
| 486 | static $searchTokens = [ |
||
| 487 | ';', |
||
| 488 | '{', |
||
| 489 | '}', |
||
| 490 | [T_FUNCTION], |
||
| 491 | [T_OPEN_TAG], |
||
| 492 | [T_OPEN_TAG_WITH_ECHO], |
||
| 493 | ]; |
||
| 494 | $prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)]; |
||
| 495 | |||
| 496 | return $prevToken->isGivenKind(T_FUNCTION); |
||
| 497 | } |
||
| 498 | |||
| 499 | /** |
||
| 500 | * Checks if there is a binary operator under given index. |
||
| 501 | */ |
||
| 502 | public function isBinaryOperator(int $index): bool |
||
| 503 | { |
||
| 504 | static $nonArrayOperators = [ |
||
| 505 | '=' => true, |
||
| 506 | '*' => true, |
||
| 507 | '/' => true, |
||
| 508 | '%' => true, |
||
| 509 | '<' => true, |
||
| 510 | '>' => true, |
||
| 511 | '|' => true, |
||
| 512 | '^' => true, |
||
| 513 | '.' => true, |
||
| 514 | ]; |
||
| 515 | |||
| 516 | static $potentialUnaryNonArrayOperators = [ |
||
| 517 | '+' => true, |
||
| 518 | '-' => true, |
||
| 519 | '&' => true, |
||
| 520 | ]; |
||
| 521 | |||
| 522 | static $arrayOperators; |
||
| 523 | if (null === $arrayOperators) { |
||
| 524 | $arrayOperators = [ |
||
| 525 | T_AND_EQUAL => true, // &= |
||
| 526 | T_BOOLEAN_AND => true, // && |
||
| 527 | T_BOOLEAN_OR => true, // || |
||
| 528 | T_CONCAT_EQUAL => true, // .= |
||
| 529 | T_DIV_EQUAL => true, // /= |
||
| 530 | T_DOUBLE_ARROW => true, // => |
||
| 531 | T_IS_EQUAL => true, // == |
||
| 532 | T_IS_GREATER_OR_EQUAL => true, // >= |
||
| 533 | T_IS_IDENTICAL => true, // === |
||
| 534 | T_IS_NOT_EQUAL => true, // !=, <> |
||
| 535 | T_IS_NOT_IDENTICAL => true, // !== |
||
| 536 | T_IS_SMALLER_OR_EQUAL => true, // <= |
||
| 537 | T_LOGICAL_AND => true, // and |
||
| 538 | T_LOGICAL_OR => true, // or |
||
| 539 | T_LOGICAL_XOR => true, // xor |
||
| 540 | T_MINUS_EQUAL => true, // -= |
||
| 541 | T_MOD_EQUAL => true, // %= |
||
| 542 | T_MUL_EQUAL => true, // *= |
||
| 543 | T_OR_EQUAL => true, // |= |
||
| 544 | T_PLUS_EQUAL => true, // += |
||
| 545 | T_POW => true, // ** |
||
| 546 | T_POW_EQUAL => true, // **= |
||
| 547 | T_SL => true, // << |
||
| 548 | T_SL_EQUAL => true, // <<= |
||
| 549 | T_SR => true, // >> |
||
| 550 | T_SR_EQUAL => true, // >>= |
||
| 551 | T_XOR_EQUAL => true, // ^= |
||
| 552 | T_SPACESHIP => true, // <=> |
||
| 553 | T_COALESCE => true, // ?? |
||
| 554 | ]; |
||
| 555 | |||
| 556 | // @TODO: drop condition when PHP 7.4+ is required |
||
| 557 | if (\defined('T_COALESCE_EQUAL')) { |
||
| 558 | $arrayOperators[T_COALESCE_EQUAL] = true; // ??= |
||
| 559 | } |
||
| 560 | } |
||
| 561 | |||
| 562 | $tokens = $this->tokens; |
||
| 563 | $token = $tokens[$index]; |
||
| 564 | |||
| 565 | if ($token->isArray()) { |
||
| 566 | return isset($arrayOperators[$token->getId()]); |
||
| 567 | } |
||
| 568 | |||
| 569 | if (isset($nonArrayOperators[$token->getContent()])) { |
||
| 570 | return true; |
||
| 571 | } |
||
| 572 | |||
| 573 | if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) { |
||
| 574 | return !$this->isUnaryPredecessorOperator($index); |
||
| 575 | } |
||
| 576 | |||
| 577 | return false; |
||
| 578 | } |
||
| 579 | |||
| 580 | /** |
||
| 581 | * Check if `T_WHILE` token at given index is `do { ... } while ();` syntax |
||
| 582 | * and not `while () { ...}`. |
||
| 583 | */ |
||
| 584 | public function isWhilePartOfDoWhile(int $index): bool |
||
| 585 | { |
||
| 586 | $tokens = $this->tokens; |
||
| 587 | $token = $tokens[$index]; |
||
| 588 | |||
| 589 | if (!$token->isGivenKind(T_WHILE)) { |
||
| 590 | throw new \LogicException(sprintf('No T_WHILE at given index %d, got "%s".', $index, $token->getName())); |
||
| 591 | } |
||
| 592 | |||
| 593 | $endIndex = $tokens->getPrevMeaningfulToken($index); |
||
| 594 | if (!$tokens[$endIndex]->equals('}')) { |
||
| 595 | return false; |
||
| 596 | } |
||
| 597 | |||
| 598 | $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex); |
||
| 599 | $beforeStartIndex = $tokens->getPrevMeaningfulToken($startIndex); |
||
| 600 | |||
| 601 | return $tokens[$beforeStartIndex]->isGivenKind(T_DO); |
||
| 602 | } |
||
| 603 | |||
| 604 | public function isSuperGlobal(int $index): bool |
||
| 625 | } |
||
| 626 | |||
| 627 | /** |
||
| 628 | * Find classy elements. |
||
| 629 | * |
||
| 630 | * Searches in tokens from the classy (start) index till the end (index) of the classy. |
||
| 631 | * Returns an array; first value is the index until the method has analysed (int), second the found classy elements (array). |
||
| 632 | * |
||
| 633 | * @param int $classIndex classy index |
||
| 634 | */ |
||
| 635 | private function findClassyElements(int $classIndex, int $index): array |
||
| 759 | } |
||
| 760 | } |
||
| 761 |