Missing dependancies

This commit is contained in:
tokslaw7
2023-06-21 12:19:22 +00:00
parent fbf3f180c2
commit 421f25c80d
2356 changed files with 342670 additions and 4743 deletions
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
use PhpCsFixer\Utils;
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
abstract class AbstractTransformer implements TransformerInterface
{
/**
* {@inheritdoc}
*/
public function getName(): string
{
$nameParts = explode('\\', static::class);
$name = substr(end($nameParts), 0, -\strlen('Transformer'));
return Utils::camelCaseToUnderscore($name);
}
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
return 0;
}
/**
* {@inheritdoc}
*/
abstract public function getCustomTokens(): array;
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
abstract class AbstractTypeTransformer extends AbstractTransformer
{
/**
* @param array{0: int, 1?: string}|string $originalToken
*/
protected function doProcess(Tokens $tokens, int $index, $originalToken): void
{
if (!$tokens[$index]->equals($originalToken)) {
return;
}
$prevIndex = $this->getPreviousTokenCandidate($tokens, $index);
/** @var Token $prevToken */
$prevToken = $tokens[$prevIndex];
if ($prevToken->isGivenKind([
CT::T_TYPE_COLON, // `:` is part of a function return type `foo(): X|Y`
CT::T_TYPE_ALTERNATION, // `|` is part of a union (chain) `X|Y`
CT::T_TYPE_INTERSECTION,
T_STATIC, T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, // `var X|Y $a;`, `private X|Y $a` or `public static X|Y $a`
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, // promoted properties
])) {
$this->replaceToken($tokens, $index);
return;
}
if (\defined('T_READONLY') && $prevToken->isGivenKind(T_READONLY)) { // @TODO: drop condition when PHP 8.1+ is required
$this->replaceToken($tokens, $index);
return;
}
if (!$prevToken->equalsAny(['(', ','])) {
return;
}
$prevPrevTokenIndex = $tokens->getPrevMeaningfulToken($prevIndex);
if ($tokens[$prevPrevTokenIndex]->isGivenKind(T_CATCH)) {
$this->replaceToken($tokens, $index);
return;
}
$functionKinds = [[T_FUNCTION], [T_FN]];
$functionIndex = $tokens->getPrevTokenOfKind($prevIndex, $functionKinds);
if (null === $functionIndex) {
return;
}
$braceOpenIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']);
$braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex);
if ($braceCloseIndex < $index) {
return;
}
$this->replaceToken($tokens, $index);
}
abstract protected function replaceToken(Tokens $tokens, int $index): void;
private function getPreviousTokenCandidate(Tokens $tokens, int $index): int
{
$candidateIndex = $tokens->getTokenNotOfKindsSibling($index, -1, [T_CALLABLE, T_NS_SEPARATOR, T_STRING, CT::T_ARRAY_TYPEHINT, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT]);
return $tokens[$candidateIndex]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)
? $this->getPreviousTokenCandidate($tokens, $tokens->getPrevTokenOfKind($index, [[T_ATTRIBUTE]]))
: $candidateIndex
;
}
}
@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*
* @TODO 4.0 remove this analyzer and move this logic into a transformer
*/
final class AlternativeSyntaxAnalyzer
{
private const ALTERNATIVE_SYNTAX_BLOCK_EDGES = [
T_IF => [T_ENDIF, T_ELSE, T_ELSEIF],
T_ELSE => [T_ENDIF],
T_ELSEIF => [T_ENDIF, T_ELSE, T_ELSEIF],
T_FOR => [T_ENDFOR],
T_FOREACH => [T_ENDFOREACH],
T_WHILE => [T_ENDWHILE],
T_SWITCH => [T_ENDSWITCH],
];
public function belongsToAlternativeSyntax(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->equals(':')) {
return false;
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$prevIndex]->isGivenKind(T_ELSE)) {
return true;
}
if (!$tokens[$prevIndex]->equals(')')) {
return false;
}
$openParenthesisIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex);
$beforeOpenParenthesisIndex = $tokens->getPrevMeaningfulToken($openParenthesisIndex);
return $tokens[$beforeOpenParenthesisIndex]->isGivenKind([
T_DECLARE,
T_ELSEIF,
T_FOR,
T_FOREACH,
T_IF,
T_SWITCH,
T_WHILE,
]);
}
public function findAlternativeSyntaxBlockEnd(Tokens $tokens, int $index): int
{
if (!isset($tokens[$index])) {
throw new \InvalidArgumentException("There is no token at index {$index}.");
}
if (!$this->isStartOfAlternativeSyntaxBlock($tokens, $index)) {
throw new \InvalidArgumentException("Token at index {$index} is not the start of an alternative syntax block.");
}
$startTokenKind = $tokens[$index]->getId();
$endTokenKinds = self::ALTERNATIVE_SYNTAX_BLOCK_EDGES[$startTokenKind];
$findKinds = [[$startTokenKind]];
foreach ($endTokenKinds as $endTokenKind) {
$findKinds[] = [$endTokenKind];
}
while (true) {
$index = $tokens->getNextTokenOfKind($index, $findKinds);
if ($tokens[$index]->isGivenKind($endTokenKinds)) {
return $index;
}
if ($this->isStartOfAlternativeSyntaxBlock($tokens, $index)) {
$index = $this->findAlternativeSyntaxBlockEnd($tokens, $index);
}
}
}
private function isStartOfAlternativeSyntaxBlock(Tokens $tokens, int $index): bool
{
$map = self::ALTERNATIVE_SYNTAX_BLOCK_EDGES;
$startTokenKind = $tokens[$index]->getId();
if (null === $startTokenKind || !isset($map[$startTokenKind])) {
return false;
}
$index = $tokens->getNextMeaningfulToken($index);
if ($tokens[$index]->equals('(')) {
$index = $tokens->getNextMeaningfulToken(
$tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index)
);
}
return $tokens[$index]->equals(':');
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
abstract class AbstractControlCaseStructuresAnalysis
{
private int $index;
private int $open;
private int $close;
public function __construct(int $index, int $open, int $close)
{
$this->index = $index;
$this->open = $open;
$this->close = $close;
}
public function getIndex(): int
{
return $this->index;
}
public function getOpenIndex(): int
{
return $this->open;
}
public function getCloseIndex(): int
{
return $this->close;
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class ArgumentAnalysis
{
/**
* The name of the argument.
*/
private string $name;
/**
* The index where the name is located in the supplied Tokens object.
*/
private int $nameIndex;
/**
* The default value of the argument.
*/
private ?string $default;
/**
* The type analysis of the argument.
*/
private ?TypeAnalysis $typeAnalysis;
public function __construct(string $name, int $nameIndex, ?string $default, ?TypeAnalysis $typeAnalysis = null)
{
$this->name = $name;
$this->nameIndex = $nameIndex;
$this->default = $default ?: null;
$this->typeAnalysis = $typeAnalysis ?: null;
}
public function getDefault(): ?string
{
return $this->default;
}
public function hasDefault(): bool
{
return null !== $this->default;
}
public function getName(): string
{
return $this->name;
}
public function getNameIndex(): int
{
return $this->nameIndex;
}
public function getTypeAnalysis(): ?TypeAnalysis
{
return $this->typeAnalysis;
}
public function hasTypeAnalysis(): bool
{
return null !== $this->typeAnalysis;
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @author Kuba Werłos <werlos@gmail.com>
*
* @internal
*/
final class CaseAnalysis
{
private int $index;
private int $colonIndex;
public function __construct(int $index, int $colonIndex)
{
$this->index = $index;
$this->colonIndex = $colonIndex;
}
public function getIndex(): int
{
return $this->index;
}
public function getColonIndex(): int
{
return $this->colonIndex;
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class DefaultAnalysis
{
private int $index;
private int $colonIndex;
public function __construct(int $index, int $colonIndex)
{
$this->index = $index;
$this->colonIndex = $colonIndex;
}
public function getIndex(): int
{
return $this->index;
}
public function getColonIndex(): int
{
return $this->colonIndex;
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class EnumAnalysis extends AbstractControlCaseStructuresAnalysis
{
/**
* @var list<CaseAnalysis>
*/
private array $cases;
/**
* @param list<CaseAnalysis> $cases
*/
public function __construct(int $index, int $open, int $close, array $cases)
{
parent::__construct($index, $open, $close);
$this->cases = $cases;
}
/**
* @return list<CaseAnalysis>
*/
public function getCases(): array
{
return $this->cases;
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class MatchAnalysis extends AbstractControlCaseStructuresAnalysis
{
private ?DefaultAnalysis $defaultAnalysis;
public function __construct(int $index, int $open, int $close, ?DefaultAnalysis $defaultAnalysis)
{
parent::__construct($index, $open, $close);
$this->defaultAnalysis = $defaultAnalysis;
}
public function getDefaultAnalysis(): ?DefaultAnalysis
{
return $this->defaultAnalysis;
}
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class NamespaceAnalysis implements StartEndTokenAwareAnalysis
{
/**
* The fully qualified namespace name.
*/
private string $fullName;
/**
* The short version of the namespace.
*/
private string $shortName;
/**
* The start index of the namespace declaration in the analyzed Tokens.
*/
private int $startIndex;
/**
* The end index of the namespace declaration in the analyzed Tokens.
*/
private int $endIndex;
/**
* The start index of the scope of the namespace in the analyzed Tokens.
*/
private int $scopeStartIndex;
/**
* The end index of the scope of the namespace in the analyzed Tokens.
*/
private int $scopeEndIndex;
public function __construct(string $fullName, string $shortName, int $startIndex, int $endIndex, int $scopeStartIndex, int $scopeEndIndex)
{
$this->fullName = $fullName;
$this->shortName = $shortName;
$this->startIndex = $startIndex;
$this->endIndex = $endIndex;
$this->scopeStartIndex = $scopeStartIndex;
$this->scopeEndIndex = $scopeEndIndex;
}
public function getFullName(): string
{
return $this->fullName;
}
public function getShortName(): string
{
return $this->shortName;
}
public function getStartIndex(): int
{
return $this->startIndex;
}
public function getEndIndex(): int
{
return $this->endIndex;
}
public function getScopeStartIndex(): int
{
return $this->scopeStartIndex;
}
public function getScopeEndIndex(): int
{
return $this->scopeEndIndex;
}
public function isGlobalNamespace(): bool
{
return '' === $this->getFullName();
}
}
@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis
{
public const TYPE_CLASS = 1; // "classy" could be class, interface or trait
public const TYPE_FUNCTION = 2;
public const TYPE_CONSTANT = 3;
/**
* The fully qualified use namespace.
*/
private string $fullName;
/**
* The short version of use namespace or the alias name in case of aliased use statements.
*/
private string $shortName;
/**
* Is the use statement being aliased?
*/
private bool $isAliased;
/**
* The start index of the namespace declaration in the analyzed Tokens.
*/
private int $startIndex;
/**
* The end index of the namespace declaration in the analyzed Tokens.
*/
private int $endIndex;
/**
* The type of import: class, function or constant.
*/
private int $type;
public function __construct(string $fullName, string $shortName, bool $isAliased, int $startIndex, int $endIndex, int $type)
{
$this->fullName = $fullName;
$this->shortName = $shortName;
$this->isAliased = $isAliased;
$this->startIndex = $startIndex;
$this->endIndex = $endIndex;
$this->type = $type;
}
public function getFullName(): string
{
return $this->fullName;
}
public function getShortName(): string
{
return $this->shortName;
}
public function isAliased(): bool
{
return $this->isAliased;
}
public function getStartIndex(): int
{
return $this->startIndex;
}
public function getEndIndex(): int
{
return $this->endIndex;
}
public function getType(): int
{
return $this->type;
}
public function isClass(): bool
{
return self::TYPE_CLASS === $this->type;
}
public function isFunction(): bool
{
return self::TYPE_FUNCTION === $this->type;
}
public function isConstant(): bool
{
return self::TYPE_CONSTANT === $this->type;
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
interface StartEndTokenAwareAnalysis
{
/**
* The start index of the analyzed subject inside of the Tokens.
*/
public function getStartIndex(): int;
/**
* The end index of the analyzed subject inside of the Tokens.
*/
public function getEndIndex(): int;
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class SwitchAnalysis extends AbstractControlCaseStructuresAnalysis
{
/**
* @var list<CaseAnalysis>
*/
private array $cases;
private ?DefaultAnalysis $defaultAnalysis;
/**
* @param list<CaseAnalysis> $cases
*/
public function __construct(int $index, int $open, int $close, array $cases, ?DefaultAnalysis $defaultAnalysis)
{
parent::__construct($index, $open, $close);
$this->cases = $cases;
$this->defaultAnalysis = $defaultAnalysis;
}
/**
* @return list<CaseAnalysis>
*/
public function getCases(): array
{
return $this->cases;
}
public function getDefaultAnalysis(): ?DefaultAnalysis
{
return $this->defaultAnalysis;
}
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class TypeAnalysis implements StartEndTokenAwareAnalysis
{
/**
* This list contains soft and hard reserved types that can be used or will be used by PHP at some point.
*
* More info:
*
* @see https://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.types
* @see https://php.net/manual/en/reserved.other-reserved-words.php
* @see https://php.net/manual/en/language.pseudo-types.php
*
* @var list<string>
*/
private static array $reservedTypes = [
'array',
'bool',
'callable',
'float',
'int',
'iterable',
'mixed',
'never',
'numeric',
'object',
'resource',
'self',
'string',
'void',
];
private string $name;
private int $startIndex;
private int $endIndex;
private bool $nullable;
public function __construct(string $name, int $startIndex, int $endIndex)
{
$this->name = $name;
$this->nullable = false;
if (str_starts_with($name, '?')) {
$this->name = substr($name, 1);
$this->nullable = true;
}
$this->startIndex = $startIndex;
$this->endIndex = $endIndex;
}
public function getName(): string
{
return $this->name;
}
public function getStartIndex(): int
{
return $this->startIndex;
}
public function getEndIndex(): int
{
return $this->endIndex;
}
public function isReservedType(): bool
{
return \in_array($this->name, self::$reservedTypes, true);
}
public function isNullable(): bool
{
return $this->nullable;
}
}
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
* @author Vladimir Reznichenko <kalessil@gmail.com>
*
* @internal
*/
final class ArgumentsAnalyzer
{
/**
* Count amount of parameters in a function/method reference.
*/
public function countArguments(Tokens $tokens, int $openParenthesis, int $closeParenthesis): int
{
return \count($this->getArguments($tokens, $openParenthesis, $closeParenthesis));
}
/**
* Returns start and end token indices of arguments.
*
* Returns an array with each key being the first token of an
* argument and the value the last. Including non-function tokens
* such as comments and white space tokens, but without the separation
* tokens like '(', ',' and ')'.
*
* @return array<int, int>
*/
public function getArguments(Tokens $tokens, int $openParenthesis, int $closeParenthesis): array
{
$arguments = [];
$firstSensibleToken = $tokens->getNextMeaningfulToken($openParenthesis);
if ($tokens[$firstSensibleToken]->equals(')')) {
return $arguments;
}
$paramContentIndex = $openParenthesis + 1;
$argumentsStart = $paramContentIndex;
for (; $paramContentIndex < $closeParenthesis; ++$paramContentIndex) {
$token = $tokens[$paramContentIndex];
// skip nested (), [], {} constructs
$blockDefinitionProbe = Tokens::detectBlockType($token);
if (null !== $blockDefinitionProbe && true === $blockDefinitionProbe['isStart']) {
$paramContentIndex = $tokens->findBlockEnd($blockDefinitionProbe['type'], $paramContentIndex);
continue;
}
// if comma matched, increase arguments counter
if ($token->equals(',')) {
if ($tokens->getNextMeaningfulToken($paramContentIndex) === $closeParenthesis) {
break; // trailing ',' in function call (PHP 7.3)
}
$arguments[$argumentsStart] = $paramContentIndex - 1;
$argumentsStart = $paramContentIndex + 1;
}
}
$arguments[$argumentsStart] = $paramContentIndex - 1;
return $arguments;
}
public function getArgumentInfo(Tokens $tokens, int $argumentStart, int $argumentEnd): ArgumentAnalysis
{
static $skipTypes = null;
if (null === $skipTypes) {
$skipTypes = [T_ELLIPSIS, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE];
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
$skipTypes[] = T_READONLY;
}
}
$info = [
'default' => null,
'name' => null,
'name_index' => null,
'type' => null,
'type_index_start' => null,
'type_index_end' => null,
];
$sawName = false;
for ($index = $argumentStart; $index <= $argumentEnd; ++$index) {
$token = $tokens[$index];
if (\defined('T_ATTRIBUTE') && $token->isGivenKind(T_ATTRIBUTE)) {
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
continue;
}
if (
$token->isComment()
|| $token->isWhitespace()
|| $token->isGivenKind($skipTypes)
|| $token->equals('&')
) {
continue;
}
if ($token->isGivenKind(T_VARIABLE)) {
$sawName = true;
$info['name_index'] = $index;
$info['name'] = $token->getContent();
continue;
}
if ($token->equals('=')) {
continue;
}
if ($sawName) {
$info['default'] .= $token->getContent();
} else {
$info['type_index_start'] = ($info['type_index_start'] > 0) ? $info['type_index_start'] : $index;
$info['type_index_end'] = $index;
$info['type'] .= $token->getContent();
}
}
return new ArgumentAnalysis(
$info['name'],
$info['name_index'],
$info['default'],
$info['type'] ? new TypeAnalysis($info['type'], $info['type_index_start'], $info['type_index_end']) : null
);
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class AttributeAnalyzer
{
private const TOKEN_KINDS_NOT_ALLOWED_IN_ATTRIBUTE = [
';',
'{',
[T_ATTRIBUTE],
[T_FUNCTION],
[T_OPEN_TAG],
[T_OPEN_TAG_WITH_ECHO],
[T_PRIVATE],
[T_PROTECTED],
[T_PUBLIC],
[T_RETURN],
[T_VARIABLE],
[CT::T_ATTRIBUTE_CLOSE],
];
/**
* Check if given index is an attribute declaration.
*/
public static function isAttribute(Tokens $tokens, int $index): bool
{
if (
!\defined('T_ATTRIBUTE') // attributes not available, PHP version lower than 8.0
|| !$tokens[$index]->isGivenKind(T_STRING) // checked token is not a string
|| !$tokens->isAnyTokenKindsFound([T_ATTRIBUTE]) // no attributes in the tokens collection
) {
return false;
}
$attributeStartIndex = $tokens->getPrevTokenOfKind($index, self::TOKEN_KINDS_NOT_ALLOWED_IN_ATTRIBUTE);
if (!$tokens[$attributeStartIndex]->isGivenKind(T_ATTRIBUTE)) {
return false;
}
// now, between attribute start and the attribute candidate index cannot be more "(" than ")"
$count = 0;
for ($i = $attributeStartIndex + 1; $i < $index; ++$i) {
if ($tokens[$i]->equals('(')) {
++$count;
} elseif ($tokens[$i]->equals(')')) {
--$count;
}
}
return 0 === $count;
}
}
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Kuba Werłos <werlos@gmail.com>
*
* @internal
*/
final class BlocksAnalyzer
{
public function isBlock(Tokens $tokens, ?int $openIndex, ?int $closeIndex): bool
{
if (null === $openIndex || null === $closeIndex) {
return false;
}
if (!$tokens->offsetExists($openIndex)) {
return false;
}
if (!$tokens->offsetExists($closeIndex)) {
return false;
}
$blockType = $this->getBlockType($tokens[$openIndex]);
if (null === $blockType) {
return false;
}
return $closeIndex === $tokens->findBlockEnd($blockType, $openIndex);
}
/**
* @return Tokens::BLOCK_TYPE_*
*/
private function getBlockType(Token $token): ?int
{
foreach (Tokens::getBlockEdgeDefinitions() as $blockType => $definition) {
if ($token->equals($definition['start'])) {
return $blockType;
}
}
return null;
}
}
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class ClassyAnalyzer
{
public function isClassyInvocation(Tokens $tokens, int $index): bool
{
$token = $tokens[$index];
if (!$token->isGivenKind(T_STRING)) {
throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $tokens[$index]->getName()));
}
if (\in_array(strtolower($token->getContent()), ['bool', 'float', 'int', 'iterable', 'object', 'parent', 'self', 'string', 'void', 'null', 'false', 'never'], true)) {
return false;
}
$next = $tokens->getNextMeaningfulToken($index);
$nextToken = $tokens[$next];
if ($nextToken->isGivenKind(T_NS_SEPARATOR)) {
return false;
}
if ($nextToken->isGivenKind([T_DOUBLE_COLON, T_ELLIPSIS, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, T_VARIABLE])) {
return true;
}
$prev = $tokens->getPrevMeaningfulToken($index);
while ($tokens[$prev]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) {
$prev = $tokens->getPrevMeaningfulToken($prev);
}
$prevToken = $tokens[$prev];
if ($prevToken->isGivenKind([T_EXTENDS, T_INSTANCEOF, T_INSTEADOF, T_IMPLEMENTS, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_TYPE_COLON, CT::T_USE_TRAIT])) {
return true;
}
if (AttributeAnalyzer::isAttribute($tokens, $index)) {
return true;
}
// `Foo & $bar` could be:
// - function reference parameter: function baz(Foo & $bar) {}
// - bit operator: $x = Foo & $bar;
if ($nextToken->equals('&') && $tokens[$tokens->getNextMeaningfulToken($next)]->isGivenKind(T_VARIABLE)) {
$checkIndex = $tokens->getPrevTokenOfKind($prev + 1, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]);
return $tokens[$checkIndex]->isGivenKind(T_FUNCTION);
}
if (!$prevToken->equals(',')) {
return false;
}
do {
$prev = $tokens->getPrevMeaningfulToken($prev);
} while ($tokens[$prev]->equalsAny([',', [T_NS_SEPARATOR], [T_STRING], [CT::T_NAMESPACE_OPERATOR]]));
return $tokens[$prev]->isGivenKind([T_IMPLEMENTS, CT::T_USE_TRAIT]);
}
}
@@ -0,0 +1,317 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Kuba Werłos <werlos@gmail.com>
*
* @internal
*/
final class CommentsAnalyzer
{
private const TYPE_HASH = 1;
private const TYPE_DOUBLE_SLASH = 2;
private const TYPE_SLASH_ASTERISK = 3;
public function isHeaderComment(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->isGivenKind([T_COMMENT, T_DOC_COMMENT])) {
throw new \InvalidArgumentException('Given index must point to a comment.');
}
if (null === $tokens->getNextMeaningfulToken($index)) {
return false;
}
$prevIndex = $tokens->getPrevNonWhitespace($index);
if ($tokens[$prevIndex]->equals(';')) {
$braceCloseIndex = $tokens->getPrevMeaningfulToken($prevIndex);
if (!$tokens[$braceCloseIndex]->equals(')')) {
return false;
}
$braceOpenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceCloseIndex);
$declareIndex = $tokens->getPrevMeaningfulToken($braceOpenIndex);
if (!$tokens[$declareIndex]->isGivenKind(T_DECLARE)) {
return false;
}
$prevIndex = $tokens->getPrevNonWhitespace($declareIndex);
}
return $tokens[$prevIndex]->isGivenKind(T_OPEN_TAG);
}
/**
* Check if comment at given index precedes structural element.
*
* @see https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#3-definitions
*/
public function isBeforeStructuralElement(Tokens $tokens, int $index): bool
{
$token = $tokens[$index];
if (!$token->isGivenKind([T_COMMENT, T_DOC_COMMENT])) {
throw new \InvalidArgumentException('Given index must point to a comment.');
}
$nextIndex = $index;
do {
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
// @TODO: drop condition when PHP 8.0+ is required
if (\defined('T_ATTRIBUTE')) {
while (null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_ATTRIBUTE)) {
$nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $nextIndex);
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
}
}
} while (null !== $nextIndex && $tokens[$nextIndex]->equals('('));
if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) {
return false;
}
if ($this->isStructuralElement($tokens, $nextIndex)) {
return true;
}
if ($this->isValidControl($tokens, $token, $nextIndex)) {
return true;
}
if ($this->isValidVariable($tokens, $nextIndex)) {
return true;
}
if ($this->isValidLanguageConstruct($tokens, $token, $nextIndex)) {
return true;
}
if ($tokens[$nextIndex]->isGivenKind(CT::T_USE_TRAIT)) {
return true;
}
return false;
}
/**
* Return array of indices that are part of a comment started at given index.
*
* @param int $index T_COMMENT index
*
* @return list<int>
*/
public function getCommentBlockIndices(Tokens $tokens, int $index): array
{
if (!$tokens[$index]->isGivenKind(T_COMMENT)) {
throw new \InvalidArgumentException('Given index must point to a comment.');
}
$commentType = $this->getCommentType($tokens[$index]->getContent());
$indices = [$index];
if (self::TYPE_SLASH_ASTERISK === $commentType) {
return $indices;
}
$count = \count($tokens);
++$index;
for (; $index < $count; ++$index) {
if ($tokens[$index]->isComment()) {
if ($commentType === $this->getCommentType($tokens[$index]->getContent())) {
$indices[] = $index;
continue;
}
break;
}
if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) {
break;
}
}
return $indices;
}
/**
* @see https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md#3-definitions
*/
private function isStructuralElement(Tokens $tokens, int $index): bool
{
static $skip;
if (null === $skip) {
$skip = [
T_PRIVATE,
T_PROTECTED,
T_PUBLIC,
T_VAR,
T_FUNCTION,
T_ABSTRACT,
T_CONST,
T_NAMESPACE,
T_REQUIRE,
T_REQUIRE_ONCE,
T_INCLUDE,
T_INCLUDE_ONCE,
T_FINAL,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
];
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
$skip[] = T_READONLY;
}
}
$token = $tokens[$index];
if ($token->isClassy() || $token->isGivenKind($skip)) {
return true;
}
if ($token->isGivenKind(T_STATIC)) {
return !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON);
}
return false;
}
/**
* Checks control structures (for, foreach, if, switch, while) for correct docblock usage.
*
* @param Token $docsToken docs Token
* @param int $controlIndex index of control structure Token
*/
private function isValidControl(Tokens $tokens, Token $docsToken, int $controlIndex): bool
{
static $controlStructures = [
T_FOR,
T_FOREACH,
T_IF,
T_SWITCH,
T_WHILE,
];
if (!$tokens[$controlIndex]->isGivenKind($controlStructures)) {
return false;
}
$index = $tokens->getNextMeaningfulToken($controlIndex);
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$docsContent = $docsToken->getContent();
for ($index = $index + 1; $index < $endIndex; ++$index) {
$token = $tokens[$index];
if (
$token->isGivenKind(T_VARIABLE)
&& str_contains($docsContent, $token->getContent())
) {
return true;
}
}
return false;
}
/**
* Checks variable assignments through `list()`, `print()` etc. calls for correct docblock usage.
*
* @param Token $docsToken docs Token
* @param int $languageConstructIndex index of variable Token
*/
private function isValidLanguageConstruct(Tokens $tokens, Token $docsToken, int $languageConstructIndex): bool
{
static $languageStructures = [
T_LIST,
T_PRINT,
T_ECHO,
CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN,
];
if (!$tokens[$languageConstructIndex]->isGivenKind($languageStructures)) {
return false;
}
$endKind = $tokens[$languageConstructIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)
? [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]
: ')';
$endIndex = $tokens->getNextTokenOfKind($languageConstructIndex, [$endKind]);
$docsContent = $docsToken->getContent();
for ($index = $languageConstructIndex + 1; $index < $endIndex; ++$index) {
$token = $tokens[$index];
if ($token->isGivenKind(T_VARIABLE) && str_contains($docsContent, $token->getContent())) {
return true;
}
}
return false;
}
/**
* Checks variable assignments for correct docblock usage.
*
* @param int $index index of variable Token
*/
private function isValidVariable(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->isGivenKind(T_VARIABLE)) {
return false;
}
$nextIndex = $tokens->getNextMeaningfulToken($index);
return $tokens[$nextIndex]->equals('=');
}
private function getCommentType(string $content): int
{
if (str_starts_with($content, '#')) {
return self::TYPE_HASH;
}
if ('*' === $content[1]) {
return self::TYPE_SLASH_ASTERISK;
}
return self::TYPE_DOUBLE_SLASH;
}
private function getLineBreakCount(Tokens $tokens, int $whiteStart, int $whiteEnd): int
{
$lineCount = 0;
for ($i = $whiteStart; $i < $whiteEnd; ++$i) {
$lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent());
}
return $lineCount;
}
}
@@ -0,0 +1,310 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\AbstractControlCaseStructuresAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\CaseAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\DefaultAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\EnumAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\MatchAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis;
use PhpCsFixer\Tokenizer\Tokens;
final class ControlCaseStructuresAnalyzer
{
/**
* @param list<int> $types Token types of interest of which analyzes must be returned
*
* @return \Generator<int, AbstractControlCaseStructuresAnalysis>
*/
public static function findControlStructures(Tokens $tokens, array $types): \Generator
{
if (\count($types) < 1) {
return; // quick skip
}
$typesWithCaseOrDefault = self::getTypesWithCaseOrDefault();
foreach ($types as $type) {
if (!\in_array($type, $typesWithCaseOrDefault, true)) {
throw new \InvalidArgumentException(sprintf('Unexpected type "%d".', $type));
}
}
if (!$tokens->isAnyTokenKindsFound($types)) {
return; // quick skip
}
$depth = -1;
/**
* @var list<array{
* kind: int|null,
* index: int,
* brace_count: int,
* cases: list<array{index: int, open: int}>,
* default: array{index: int, open: int}|null,
* alternative_syntax: bool,
* }> $stack
*/
$stack = [];
$isTypeOfInterest = false;
foreach ($tokens as $index => $token) {
if ($token->isGivenKind($typesWithCaseOrDefault)) {
++$depth;
$stack[$depth] = [
'kind' => $token->getId(),
'index' => $index,
'brace_count' => 0,
'cases' => [],
'default' => null,
'alternative_syntax' => false,
];
$isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
if ($token->isGivenKind(T_SWITCH)) {
$index = $tokens->getNextMeaningfulToken($index);
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index);
$stack[$depth]['alternative_syntax'] = $tokens[$stack[$depth]['open']]->equals(':');
} elseif (\defined('T_MATCH') && $token->isGivenKind(T_MATCH)) { // @TODO: drop condition when PHP 8.0+ is required
$index = $tokens->getNextMeaningfulToken($index);
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index);
} elseif (\defined('T_ENUM') && $token->isGivenKind(T_ENUM)) {
$stack[$depth]['open'] = $tokens->getNextTokenOfKind($index, ['{']);
}
continue;
}
if ($depth < 0) {
continue;
}
if ($token->equals('{')) {
++$stack[$depth]['brace_count'];
continue;
}
if ($token->equals('}')) {
--$stack[$depth]['brace_count'];
if (0 === $stack[$depth]['brace_count']) {
if ($stack[$depth]['alternative_syntax']) {
continue;
}
if ($isTypeOfInterest) {
$stack[$depth]['end'] = $index;
yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]);
}
array_pop($stack);
--$depth;
if ($depth < -1) { // @phpstan-ignore-line
throw new \RuntimeException('Analysis depth count failure.');
}
if (isset($stack[$depth]['kind'])) {
$isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
}
}
continue;
}
if ($tokens[$index]->isGivenKind(T_ENDSWITCH)) {
if (!$stack[$depth]['alternative_syntax']) {
throw new \RuntimeException('Analysis syntax failure, unexpected "T_ENDSWITCH".');
}
if (T_SWITCH !== $stack[$depth]['kind']) {
throw new \RuntimeException('Analysis type failure, unexpected "T_ENDSWITCH".');
}
if (0 !== $stack[$depth]['brace_count']) {
throw new \RuntimeException('Analysis count failure, unexpected "T_ENDSWITCH".');
}
$index = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
if ($isTypeOfInterest) {
$stack[$depth]['end'] = $index;
yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]);
}
array_pop($stack);
--$depth;
if ($depth < -1) { // @phpstan-ignore-line
throw new \RuntimeException('Analysis depth count failure ("T_ENDSWITCH").');
}
if (isset($stack[$depth]['kind'])) {
$isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
}
}
if (!$isTypeOfInterest) {
continue; // don't bother to analyze stuff that caller is not interested in
}
if ($token->isGivenKind(T_CASE)) {
$stack[$depth]['cases'][] = ['index' => $index, 'open' => self::findCaseOpen($tokens, $stack[$depth]['kind'], $index)];
} elseif ($token->isGivenKind(T_DEFAULT)) {
if (null !== $stack[$depth]['default']) {
throw new \RuntimeException('Analysis multiple "default" found.');
}
$stack[$depth]['default'] = ['index' => $index, 'open' => self::findDefaultOpen($tokens, $stack[$depth]['kind'], $index)];
}
}
}
/**
* @param array{
* kind: int,
* index: int,
* open: int,
* end: int,
* cases: list<array{index: int, open: int}>,
* default: null|array{index: int, open: int},
* } $analysis
*/
private static function buildControlCaseStructureAnalysis(array $analysis): AbstractControlCaseStructuresAnalysis
{
$default = null === $analysis['default']
? null
: new DefaultAnalysis($analysis['default']['index'], $analysis['default']['open'])
;
$cases = [];
foreach ($analysis['cases'] as $case) {
$cases[$case['index']] = new CaseAnalysis($case['index'], $case['open']);
}
sort($cases);
if (T_SWITCH === $analysis['kind']) {
return new SwitchAnalysis(
$analysis['index'],
$analysis['open'],
$analysis['end'],
$cases,
$default
);
}
if (\defined('T_ENUM') && T_ENUM === $analysis['kind']) {
return new EnumAnalysis(
$analysis['index'],
$analysis['open'],
$analysis['end'],
$cases
);
}
if (\defined('T_MATCH') && T_MATCH === $analysis['kind']) { // @TODO: drop condition when PHP 8.0+ is required
return new MatchAnalysis(
$analysis['index'],
$analysis['open'],
$analysis['end'],
$default
);
}
throw new \InvalidArgumentException(sprintf('Unexpected type "%d".', $analysis['kind']));
}
private static function findCaseOpen(Tokens $tokens, int $kind, int $index): int
{
if (T_SWITCH === $kind) {
$ternariesCount = 0;
do {
if ($tokens[$index]->equalsAny(['(', '{'])) { // skip constructs
$type = Tokens::detectBlockType($tokens[$index]);
$index = $tokens->findBlockEnd($type['type'], $index);
continue;
}
if ($tokens[$index]->equals('?')) {
++$ternariesCount;
continue;
}
if ($tokens[$index]->equalsAny([':', ';'])) {
if (0 === $ternariesCount) {
break;
}
--$ternariesCount;
}
} while (++$index);
return $index;
}
if (\defined('T_ENUM') && T_ENUM === $kind) {
return $tokens->getNextTokenOfKind($index, ['=', ';']);
}
throw new \InvalidArgumentException(sprintf('Unexpected case for type "%d".', $kind));
}
private static function findDefaultOpen(Tokens $tokens, int $kind, int $index): int
{
if (T_SWITCH === $kind) {
return $tokens->getNextTokenOfKind($index, [':', ';']);
}
if (\defined('T_MATCH') && T_MATCH === $kind) { // @TODO: drop condition when PHP 8.0+ is required
return $tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]);
}
throw new \InvalidArgumentException(sprintf('Unexpected default for type "%d".', $kind));
}
/**
* @return list<int>
*/
private static function getTypesWithCaseOrDefault(): array
{
$supportedTypes = [T_SWITCH];
if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required
$supportedTypes[] = T_MATCH;
}
if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
$supportedTypes[] = T_ENUM;
}
return $supportedTypes;
}
}
@@ -0,0 +1,269 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class FunctionsAnalyzer
{
/**
* @var array{tokens: string, imports: list<NamespaceUseAnalysis>, declarations: list<int>}
*/
private array $functionsAnalysis = ['tokens' => '', 'imports' => [], 'declarations' => []];
/**
* Important: risky because of the limited (file) scope of the tool.
*/
public function isGlobalFunctionCall(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->isGivenKind(T_STRING)) {
return false;
}
$nextIndex = $tokens->getNextMeaningfulToken($index);
if (!$tokens[$nextIndex]->equals('(')) {
return false;
}
$previousIsNamespaceSeparator = false;
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
$previousIsNamespaceSeparator = true;
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
}
$possibleKind = array_merge([T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, CT::T_RETURN_REF, T_STRING], Token::getObjectOperatorKinds());
// @TODO: drop condition when PHP 8.0+ is required
if (\defined('T_ATTRIBUTE')) {
$possibleKind[] = T_ATTRIBUTE;
}
if ($tokens[$prevIndex]->isGivenKind($possibleKind)) {
return false;
}
if ($previousIsNamespaceSeparator) {
return true;
}
if ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) {
return false;
}
if ($tokens->isChanged() || $tokens->getCodeHash() !== $this->functionsAnalysis['tokens']) {
$this->buildFunctionsAnalysis($tokens);
}
// figure out in which namespace we are
$namespaceAnalyzer = new NamespacesAnalyzer();
$declarations = $namespaceAnalyzer->getDeclarations($tokens);
$scopeStartIndex = 0;
$scopeEndIndex = \count($tokens) - 1;
$inGlobalNamespace = false;
foreach ($declarations as $declaration) {
$scopeStartIndex = $declaration->getScopeStartIndex();
$scopeEndIndex = $declaration->getScopeEndIndex();
if ($index >= $scopeStartIndex && $index <= $scopeEndIndex) {
$inGlobalNamespace = $declaration->isGlobalNamespace();
break;
}
}
$call = strtolower($tokens[$index]->getContent());
// check if the call is to a function declared in the same namespace as the call is done,
// if the call is already in the global namespace than declared functions are in the same
// global namespace and don't need checking
if (!$inGlobalNamespace) {
/** @var int $functionNameIndex */
foreach ($this->functionsAnalysis['declarations'] as $functionNameIndex) {
if ($functionNameIndex < $scopeStartIndex || $functionNameIndex > $scopeEndIndex) {
continue;
}
if (strtolower($tokens[$functionNameIndex]->getContent()) === $call) {
return false;
}
}
}
/** @var NamespaceUseAnalysis $functionUse */
foreach ($this->functionsAnalysis['imports'] as $functionUse) {
if ($functionUse->getStartIndex() < $scopeStartIndex || $functionUse->getEndIndex() > $scopeEndIndex) {
continue;
}
if ($call !== strtolower($functionUse->getShortName())) {
continue;
}
// global import like `use function \str_repeat;`
return $functionUse->getShortName() === ltrim($functionUse->getFullName(), '\\');
}
if (AttributeAnalyzer::isAttribute($tokens, $index)) {
return false;
}
return true;
}
/**
* @return array<string, ArgumentAnalysis>
*/
public function getFunctionArguments(Tokens $tokens, int $functionIndex): array
{
$argumentsStart = $tokens->getNextTokenOfKind($functionIndex, ['(']);
$argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart);
$argumentAnalyzer = new ArgumentsAnalyzer();
$arguments = [];
foreach ($argumentAnalyzer->getArguments($tokens, $argumentsStart, $argumentsEnd) as $start => $end) {
$argumentInfo = $argumentAnalyzer->getArgumentInfo($tokens, $start, $end);
$arguments[$argumentInfo->getName()] = $argumentInfo;
}
return $arguments;
}
public function getFunctionReturnType(Tokens $tokens, int $methodIndex): ?TypeAnalysis
{
$argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']);
$argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart);
$typeColonIndex = $tokens->getNextMeaningfulToken($argumentsEnd);
if (!$tokens[$typeColonIndex]->isGivenKind(CT::T_TYPE_COLON)) {
return null;
}
$type = '';
$typeStartIndex = $tokens->getNextMeaningfulToken($typeColonIndex);
$typeEndIndex = $typeStartIndex;
$functionBodyStart = $tokens->getNextTokenOfKind($typeColonIndex, ['{', ';', [T_DOUBLE_ARROW]]);
for ($i = $typeStartIndex; $i < $functionBodyStart; ++$i) {
if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) {
continue;
}
$type .= $tokens[$i]->getContent();
$typeEndIndex = $i;
}
return new TypeAnalysis($type, $typeStartIndex, $typeEndIndex);
}
public function isTheSameClassCall(Tokens $tokens, int $index): bool
{
if (!$tokens->offsetExists($index)) {
return false;
}
$operatorIndex = $tokens->getPrevMeaningfulToken($index);
if (null === $operatorIndex) {
return false;
}
if (!$tokens[$operatorIndex]->isObjectOperator() && !$tokens[$operatorIndex]->isGivenKind(T_DOUBLE_COLON)) {
return false;
}
$referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex);
if (null === $referenceIndex) {
return false;
}
return $tokens[$referenceIndex]->equalsAny([[T_VARIABLE, '$this'], [T_STRING, 'self'], [T_STATIC, 'static']], false);
}
private function buildFunctionsAnalysis(Tokens $tokens): void
{
$this->functionsAnalysis = [
'tokens' => $tokens->getCodeHash(),
'imports' => [],
'declarations' => [],
];
// find declarations
if ($tokens->isTokenKindFound(T_FUNCTION)) {
$end = \count($tokens);
for ($i = 0; $i < $end; ++$i) {
// skip classy, we are looking for functions not methods
if ($tokens[$i]->isGivenKind(Token::getClassyTokenKinds())) {
$i = $tokens->getNextTokenOfKind($i, ['(', '{']);
if ($tokens[$i]->equals('(')) { // anonymous class
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i);
$i = $tokens->getNextTokenOfKind($i, ['{']);
}
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i);
continue;
}
if (!$tokens[$i]->isGivenKind(T_FUNCTION)) {
continue;
}
$i = $tokens->getNextMeaningfulToken($i);
if ($tokens[$i]->isGivenKind(CT::T_RETURN_REF)) {
$i = $tokens->getNextMeaningfulToken($i);
}
if (!$tokens[$i]->isGivenKind(T_STRING)) {
continue;
}
$this->functionsAnalysis['declarations'][] = $i;
}
}
// find imported functions
$namespaceUsesAnalyzer = new NamespaceUsesAnalyzer();
if ($tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) {
$declarations = $namespaceUsesAnalyzer->getDeclarationsFromTokens($tokens);
foreach ($declarations as $declaration) {
if ($declaration->isFunction()) {
$this->functionsAnalysis['imports'][] = $declaration;
}
}
}
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class GotoLabelAnalyzer
{
public function belongsToGoToLabel(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->equals(':')) {
return false;
}
$prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_STRING)) {
return false;
}
$prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulTokenIndex);
return $tokens[$prevMeaningfulTokenIndex]->equalsAny([':', ';', '{', '}', [T_OPEN_TAG]]);
}
}
@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;
/**
* @internal
*/
final class NamespaceUsesAnalyzer
{
/**
* @return list<NamespaceUseAnalysis>
*/
public function getDeclarationsFromTokens(Tokens $tokens): array
{
$tokenAnalyzer = new TokensAnalyzer($tokens);
$useIndices = $tokenAnalyzer->getImportUseIndexes();
return $this->getDeclarations($tokens, $useIndices);
}
/**
* @return list<NamespaceUseAnalysis>
*/
public function getDeclarationsInNamespace(Tokens $tokens, NamespaceAnalysis $namespace): array
{
$namespaceUses = [];
foreach ($this->getDeclarationsFromTokens($tokens) as $namespaceUse) {
if ($namespaceUse->getStartIndex() >= $namespace->getScopeStartIndex() && $namespaceUse->getStartIndex() <= $namespace->getScopeEndIndex()) {
$namespaceUses[] = $namespaceUse;
}
}
return $namespaceUses;
}
/**
* @param list<int> $useIndices
*
* @return list<NamespaceUseAnalysis>
*/
private function getDeclarations(Tokens $tokens, array $useIndices): array
{
$uses = [];
foreach ($useIndices as $index) {
$endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
$analysis = $this->parseDeclaration($tokens, $index, $endIndex);
if (null !== $analysis) {
$uses[] = $analysis;
}
}
return $uses;
}
private function parseDeclaration(Tokens $tokens, int $startIndex, int $endIndex): ?NamespaceUseAnalysis
{
$fullName = $shortName = '';
$aliased = false;
$type = NamespaceUseAnalysis::TYPE_CLASS;
for ($i = $startIndex; $i <= $endIndex; ++$i) {
$token = $tokens[$i];
if ($token->equals(',') || $token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) {
// do not touch group use declarations until the logic of this is added (for example: `use some\a\{ClassD};`)
// ignore multiple use statements that should be split into few separate statements (for example: `use BarB, BarC as C;`)
return null;
}
if ($token->isGivenKind(CT::T_FUNCTION_IMPORT)) {
$type = NamespaceUseAnalysis::TYPE_FUNCTION;
} elseif ($token->isGivenKind(CT::T_CONST_IMPORT)) {
$type = NamespaceUseAnalysis::TYPE_CONSTANT;
}
if ($token->isWhitespace() || $token->isComment() || $token->isGivenKind(T_USE)) {
continue;
}
if ($token->isGivenKind(T_STRING)) {
$shortName = $token->getContent();
if (!$aliased) {
$fullName .= $shortName;
}
} elseif ($token->isGivenKind(T_NS_SEPARATOR)) {
$fullName .= $token->getContent();
} elseif ($token->isGivenKind(T_AS)) {
$aliased = true;
}
}
return new NamespaceUseAnalysis(
trim($fullName),
$shortName,
$aliased,
$startIndex,
$endIndex,
$type
);
}
}
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class NamespacesAnalyzer
{
/**
* @return list<NamespaceAnalysis>
*/
public function getDeclarations(Tokens $tokens): array
{
$namespaces = [];
for ($index = 1, $count = \count($tokens); $index < $count; ++$index) {
$token = $tokens[$index];
if (!$token->isGivenKind(T_NAMESPACE)) {
continue;
}
$declarationEndIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
$namespace = trim($tokens->generatePartialCode($index + 1, $declarationEndIndex - 1));
$declarationParts = explode('\\', $namespace);
$shortName = end($declarationParts);
if ($tokens[$declarationEndIndex]->equals('{')) {
$scopeEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $declarationEndIndex);
} else {
$scopeEndIndex = $tokens->getNextTokenOfKind($declarationEndIndex, [[T_NAMESPACE]]);
if (null === $scopeEndIndex) {
$scopeEndIndex = \count($tokens);
}
--$scopeEndIndex;
}
$namespaces[] = new NamespaceAnalysis(
$namespace,
$shortName,
$index,
$declarationEndIndex,
$index,
$scopeEndIndex
);
// Continue the analysis after the end of this namespace to find the next one
$index = $scopeEndIndex;
}
if (0 === \count($namespaces)) {
$namespaces[] = new NamespaceAnalysis('', '', 0, 0, 0, \count($tokens) - 1);
}
return $namespaces;
}
public function getNamespaceAt(Tokens $tokens, int $index): NamespaceAnalysis
{
if (!$tokens->offsetExists($index)) {
throw new \InvalidArgumentException(sprintf('Token index %d does not exist.', $index));
}
foreach ($this->getDeclarations($tokens) as $namespace) {
if ($namespace->getScopeStartIndex() <= $index && $namespace->getScopeEndIndex() >= $index) {
return $namespace;
}
}
throw new \LogicException(sprintf('Unable to get the namespace at index %d.', $index));
}
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class RangeAnalyzer
{
private function __construct()
{
// cannot create instance of util. class
}
/**
* Meaningful compare of tokens within ranges.
*
* @param array{start: int, end: int} $range1
* @param array{start: int, end: int} $range2
*/
public static function rangeEqualsRange(Tokens $tokens, array $range1, array $range2): bool
{
$leftStart = $range1['start'];
$leftEnd = $range1['end'];
if ($tokens[$leftStart]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
$leftStart = $tokens->getNextMeaningfulToken($leftStart);
}
while ($tokens[$leftStart]->equals('(') && $tokens[$leftEnd]->equals(')')) {
$leftStart = $tokens->getNextMeaningfulToken($leftStart);
$leftEnd = $tokens->getPrevMeaningfulToken($leftEnd);
}
$rightStart = $range2['start'];
$rightEnd = $range2['end'];
if ($tokens[$rightStart]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
$rightStart = $tokens->getNextMeaningfulToken($rightStart);
}
while ($tokens[$rightStart]->equals('(') && $tokens[$rightEnd]->equals(')')) {
$rightStart = $tokens->getNextMeaningfulToken($rightStart);
$rightEnd = $tokens->getPrevMeaningfulToken($rightEnd);
}
$arrayOpenTypes = ['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]];
$arrayCloseTypes = [']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]];
while (true) {
$leftToken = $tokens[$leftStart];
$rightToken = $tokens[$rightStart];
if (
!$leftToken->equals($rightToken)
&& !($leftToken->equalsAny($arrayOpenTypes) && $rightToken->equalsAny($arrayOpenTypes))
&& !($leftToken->equalsAny($arrayCloseTypes) && $rightToken->equalsAny($arrayCloseTypes))
) {
return false;
}
$leftStart = $tokens->getNextMeaningfulToken($leftStart);
$rightStart = $tokens->getNextMeaningfulToken($rightStart);
$reachedLeftEnd = null === $leftStart || $leftStart > $leftEnd; // reached end left or moved over
$reachedRightEnd = null === $rightStart || $rightStart > $rightEnd; // reached end right or moved over
if (!$reachedLeftEnd && !$reachedRightEnd) {
continue;
}
return $reachedLeftEnd && $reachedRightEnd;
}
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Kuba Werłos <werlos@gmail.com>
*
* @internal
*/
final class ReferenceAnalyzer
{
public function isReference(Tokens $tokens, int $index): bool
{
if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) {
return true;
}
if (!$tokens[$index]->equals('&')) {
return false;
}
/** @var int $index */
$index = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$index]->equalsAny(['=', [T_AS], [T_CALLABLE], [T_DOUBLE_ARROW], [CT::T_ARRAY_TYPEHINT]])) {
return true;
}
if ($tokens[$index]->isGivenKind(T_STRING)) {
$index = $tokens->getPrevMeaningfulToken($index);
}
return $tokens[$index]->equalsAny(['(', ',', [T_NS_SEPARATOR], [CT::T_NULLABLE_TYPE]]);
}
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class WhitespacesAnalyzer
{
public static function detectIndent(Tokens $tokens, int $index): string
{
while (true) {
$whitespaceIndex = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE]]);
if (null === $whitespaceIndex) {
return '';
}
$whitespaceToken = $tokens[$whitespaceIndex];
if (str_contains($whitespaceToken->getContent(), "\n")) {
break;
}
$prevToken = $tokens[$whitespaceIndex - 1];
if ($prevToken->isGivenKind([T_OPEN_TAG, T_COMMENT]) && "\n" === substr($prevToken->getContent(), -1)) {
break;
}
$index = $whitespaceIndex;
}
$explodedContent = explode("\n", $whitespaceToken->getContent());
return end($explodedContent);
}
}
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*/
final class CT
{
public const T_ARRAY_INDEX_CURLY_BRACE_CLOSE = 10001;
public const T_ARRAY_INDEX_CURLY_BRACE_OPEN = 10002;
public const T_ARRAY_SQUARE_BRACE_CLOSE = 10003;
public const T_ARRAY_SQUARE_BRACE_OPEN = 10004;
public const T_ARRAY_TYPEHINT = 10005;
public const T_BRACE_CLASS_INSTANTIATION_CLOSE = 10006;
public const T_BRACE_CLASS_INSTANTIATION_OPEN = 10007;
public const T_CLASS_CONSTANT = 10008;
public const T_CONST_IMPORT = 10009;
public const T_CURLY_CLOSE = 10010;
public const T_DESTRUCTURING_SQUARE_BRACE_CLOSE = 10011;
public const T_DESTRUCTURING_SQUARE_BRACE_OPEN = 10012;
public const T_DOLLAR_CLOSE_CURLY_BRACES = 10013;
public const T_DYNAMIC_PROP_BRACE_CLOSE = 10014;
public const T_DYNAMIC_PROP_BRACE_OPEN = 10015;
public const T_DYNAMIC_VAR_BRACE_CLOSE = 10016;
public const T_DYNAMIC_VAR_BRACE_OPEN = 10017;
public const T_FUNCTION_IMPORT = 10018;
public const T_GROUP_IMPORT_BRACE_CLOSE = 10019;
public const T_GROUP_IMPORT_BRACE_OPEN = 10020;
public const T_NAMESPACE_OPERATOR = 10021;
public const T_NULLABLE_TYPE = 10022;
public const T_RETURN_REF = 10023;
public const T_TYPE_ALTERNATION = 10024;
public const T_TYPE_COLON = 10025;
public const T_USE_LAMBDA = 10026;
public const T_USE_TRAIT = 10027;
public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC = 10028;
public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED = 10029;
public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE = 10030;
public const T_ATTRIBUTE_CLOSE = 10031;
public const T_NAMED_ARGUMENT_NAME = 10032;
public const T_NAMED_ARGUMENT_COLON = 10033;
public const T_FIRST_CLASS_CALLABLE = 10034;
public const T_TYPE_INTERSECTION = 10035;
private function __construct()
{
}
/**
* Get name for custom token.
*
* @param int $value custom token value
*/
public static function getName(int $value): string
{
if (!self::has($value)) {
throw new \InvalidArgumentException(sprintf('No custom token was found for "%s".', $value));
}
$tokens = self::getMapById();
return 'CT::'.$tokens[$value];
}
/**
* Check if given custom token exists.
*
* @param int $value custom token value
*/
public static function has(int $value): bool
{
$tokens = self::getMapById();
return isset($tokens[$value]);
}
/**
* @return array<self::T_*, string>
*/
private static function getMapById(): array
{
static $constants;
if (null === $constants) {
$reflection = new \ReflectionClass(__CLASS__);
$constants = array_flip($reflection->getConstants());
}
return $constants;
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class CodeHasher
{
private function __construct()
{
// cannot create instance of util. class
}
/**
* Calculate hash for code.
*/
public static function calculateCodeHash(string $code): string
{
return md5($code);
}
}
@@ -0,0 +1,513 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
/**
* Representation of single token.
* As a token prototype you should understand a single element generated by token_get_all.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*/
final class Token
{
/**
* Content of token prototype.
*/
private string $content;
/**
* ID of token prototype, if available.
*/
private ?int $id = null;
/**
* If token prototype is an array.
*/
private bool $isArray;
/**
* Flag is token was changed.
*/
private bool $changed = false;
/**
* @param array{int, string}|string $token token prototype
*/
public function __construct($token)
{
if (\is_array($token)) {
if (!\is_int($token[0])) {
throw new \InvalidArgumentException(sprintf(
'Id must be an int, got "%s".',
get_debug_type($token[0])
));
}
if (!\is_string($token[1])) {
throw new \InvalidArgumentException(sprintf(
'Content must be a string, got "%s".',
get_debug_type($token[1])
));
}
if ('' === $token[1]) {
throw new \InvalidArgumentException('Cannot set empty content for id-based Token.');
}
$this->isArray = true;
$this->id = $token[0];
$this->content = $token[1];
} elseif (\is_string($token)) {
$this->isArray = false;
$this->content = $token;
} else {
throw new \InvalidArgumentException(sprintf('Cannot recognize input value as valid Token prototype, got "%s".', get_debug_type($token)));
}
}
/**
* @return list<int>
*/
public static function getCastTokenKinds(): array
{
static $castTokens = [T_ARRAY_CAST, T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, T_OBJECT_CAST, T_STRING_CAST, T_UNSET_CAST];
return $castTokens;
}
/**
* Get classy tokens kinds: T_CLASS, T_INTERFACE and T_TRAIT.
*
* @return list<int>
*/
public static function getClassyTokenKinds(): array
{
static $classTokens;
if (null === $classTokens) {
$classTokens = [T_CLASS, T_TRAIT, T_INTERFACE];
if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
$classTokens[] = T_ENUM;
}
}
return $classTokens;
}
/**
* Get object operator tokens kinds: T_OBJECT_OPERATOR and (if available) T_NULLSAFE_OBJECT_OPERATOR.
*
* @return list<int>
*/
public static function getObjectOperatorKinds(): array
{
static $objectOperators = null;
if (null === $objectOperators) {
$objectOperators = [T_OBJECT_OPERATOR];
if (\defined('T_NULLSAFE_OBJECT_OPERATOR')) {
$objectOperators[] = T_NULLSAFE_OBJECT_OPERATOR;
}
}
return $objectOperators;
}
/**
* Check if token is equals to given one.
*
* If tokens are arrays, then only keys defined in parameter token are checked.
*
* @param array{0: int, 1?: string}|string|Token $other token or it's prototype
* @param bool $caseSensitive perform a case sensitive comparison
*/
public function equals($other, bool $caseSensitive = true): bool
{
if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition with new MAJOR release 4.0
if ('&' === $other) {
return '&' === $this->content && (null === $this->id || $this->isGivenKind([T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]));
}
if (null === $this->id && '&' === $this->content) {
return $other instanceof self && '&' === $other->content && (null === $other->id || $other->isGivenKind([T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]));
}
}
if ($other instanceof self) {
// Inlined getPrototype() on this very hot path.
// We access the private properties of $other directly to save function call overhead.
// This is only possible because $other is of the same class as `self`.
if (!$other->isArray) {
$otherPrototype = $other->content;
} else {
$otherPrototype = [
$other->id,
$other->content,
];
}
} else {
$otherPrototype = $other;
}
if ($this->isArray !== \is_array($otherPrototype)) {
return false;
}
if (!$this->isArray) {
return $this->content === $otherPrototype;
}
if ($this->id !== $otherPrototype[0]) {
return false;
}
if (isset($otherPrototype[1])) {
if ($caseSensitive) {
if ($this->content !== $otherPrototype[1]) {
return false;
}
} elseif (0 !== strcasecmp($this->content, $otherPrototype[1])) {
return false;
}
}
// detect unknown keys
unset($otherPrototype[0], $otherPrototype[1]);
/*
* @phpstan-ignore-next-line This validation is required when the method
* is called in a codebase that does not use
* static analysis.
*/
return empty($otherPrototype);
}
/**
* Check if token is equals to one of given.
*
* @param list<array{0: int, 1?: string}|string|Token> $others array of tokens or token prototypes
* @param bool $caseSensitive perform a case sensitive comparison
*/
public function equalsAny(array $others, bool $caseSensitive = true): bool
{
foreach ($others as $other) {
if ($this->equals($other, $caseSensitive)) {
return true;
}
}
return false;
}
/**
* A helper method used to find out whether a certain input token has to be case-sensitively matched.
*
* @param bool|list<bool> $caseSensitive global case sensitiveness or an array of booleans, whose keys should match
* the ones used in $sequence. If any is missing, the default case-sensitive
* comparison is used
* @param int $key the key of the token that has to be looked up
*/
public static function isKeyCaseSensitive($caseSensitive, int $key): bool
{
if (\is_array($caseSensitive)) {
return $caseSensitive[$key] ?? true;
}
return $caseSensitive;
}
/**
* @return array{int, string}|string
*/
public function getPrototype()
{
if (!$this->isArray) {
return $this->content;
}
return [
$this->id,
$this->content,
];
}
/**
* Get token's content.
*
* It shall be used only for getting the content of token, not for checking it against excepted value.
*/
public function getContent(): string
{
return $this->content;
}
/**
* Get token's id.
*
* It shall be used only for getting the internal id of token, not for checking it against excepted value.
*/
public function getId(): ?int
{
return $this->id;
}
/**
* Get token's name.
*
* It shall be used only for getting the name of token, not for checking it against excepted value.
*
* @return null|string token name
*/
public function getName(): ?string
{
if (null === $this->id) {
return null;
}
return self::getNameForId($this->id);
}
/**
* Get token's name.
*
* It shall be used only for getting the name of token, not for checking it against excepted value.
*
* @return null|string token name
*/
public static function getNameForId(int $id): ?string
{
if (CT::has($id)) {
return CT::getName($id);
}
$name = token_name($id);
return 'UNKNOWN' === $name ? null : $name;
}
/**
* Generate array containing all keywords that exists in PHP version in use.
*
* @return array<int, int>
*/
public static function getKeywords(): array
{
static $keywords = null;
if (null === $keywords) {
$keywords = self::getTokenKindsForNames(['T_ABSTRACT', 'T_ARRAY', 'T_AS', 'T_BREAK', 'T_CALLABLE', 'T_CASE',
'T_CATCH', 'T_CLASS', 'T_CLONE', 'T_CONST', 'T_CONTINUE', 'T_DECLARE', 'T_DEFAULT', 'T_DO',
'T_ECHO', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENDDECLARE', 'T_ENDFOR', 'T_ENDFOREACH',
'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_EVAL', 'T_EXIT', 'T_EXTENDS', 'T_FINAL',
'T_FINALLY', 'T_FN', 'T_FOR', 'T_FOREACH', 'T_FUNCTION', 'T_GLOBAL', 'T_GOTO', 'T_HALT_COMPILER',
'T_IF', 'T_IMPLEMENTS', 'T_INCLUDE', 'T_INCLUDE_ONCE', 'T_INSTANCEOF', 'T_INSTEADOF',
'T_INTERFACE', 'T_ISSET', 'T_LIST', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR',
'T_NAMESPACE', 'T_MATCH', 'T_NEW', 'T_PRINT', 'T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC', 'T_REQUIRE',
'T_REQUIRE_ONCE', 'T_RETURN', 'T_STATIC', 'T_SWITCH', 'T_THROW', 'T_TRAIT', 'T_TRY',
'T_UNSET', 'T_USE', 'T_VAR', 'T_WHILE', 'T_YIELD', 'T_YIELD_FROM', 'T_READONLY', 'T_ENUM',
]) + [
CT::T_ARRAY_TYPEHINT => CT::T_ARRAY_TYPEHINT,
CT::T_CLASS_CONSTANT => CT::T_CLASS_CONSTANT,
CT::T_CONST_IMPORT => CT::T_CONST_IMPORT,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
CT::T_FUNCTION_IMPORT => CT::T_FUNCTION_IMPORT,
CT::T_NAMESPACE_OPERATOR => CT::T_NAMESPACE_OPERATOR,
CT::T_USE_LAMBDA => CT::T_USE_LAMBDA,
CT::T_USE_TRAIT => CT::T_USE_TRAIT,
];
}
return $keywords;
}
/**
* Generate array containing all predefined constants that exists in PHP version in use.
*
* @see https://php.net/manual/en/language.constants.predefined.php
*
* @return array<int, int>
*/
public static function getMagicConstants(): array
{
static $magicConstants = null;
if (null === $magicConstants) {
$magicConstants = self::getTokenKindsForNames(['T_CLASS_C', 'T_DIR', 'T_FILE', 'T_FUNC_C', 'T_LINE', 'T_METHOD_C', 'T_NS_C', 'T_TRAIT_C']);
}
return $magicConstants;
}
/**
* Check if token prototype is an array.
*
* @return bool is array
*/
public function isArray(): bool
{
return $this->isArray;
}
/**
* Check if token is one of type cast tokens.
*/
public function isCast(): bool
{
return $this->isGivenKind(self::getCastTokenKinds());
}
/**
* Check if token is one of classy tokens: T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM.
*/
public function isClassy(): bool
{
return $this->isGivenKind(self::getClassyTokenKinds());
}
/**
* Check if token is one of comment tokens: T_COMMENT or T_DOC_COMMENT.
*/
public function isComment(): bool
{
static $commentTokens = [T_COMMENT, T_DOC_COMMENT];
return $this->isGivenKind($commentTokens);
}
/**
* Check if token is one of object operator tokens: T_OBJECT_OPERATOR or T_NULLSAFE_OBJECT_OPERATOR.
*/
public function isObjectOperator(): bool
{
return $this->isGivenKind(self::getObjectOperatorKinds());
}
/**
* Check if token is one of given kind.
*
* @param int|list<int> $possibleKind kind or array of kinds
*/
public function isGivenKind($possibleKind): bool
{
return $this->isArray && (\is_array($possibleKind) ? \in_array($this->id, $possibleKind, true) : $this->id === $possibleKind);
}
/**
* Check if token is a keyword.
*/
public function isKeyword(): bool
{
$keywords = static::getKeywords();
return $this->isArray && isset($keywords[$this->id]);
}
/**
* Check if token is a native PHP constant: true, false or null.
*/
public function isNativeConstant(): bool
{
static $nativeConstantStrings = ['true', 'false', 'null'];
return $this->isArray && \in_array(strtolower($this->content), $nativeConstantStrings, true);
}
/**
* Returns if the token is of a Magic constants type.
*
* @see https://php.net/manual/en/language.constants.predefined.php
*/
public function isMagicConstant(): bool
{
$magicConstants = static::getMagicConstants();
return $this->isArray && isset($magicConstants[$this->id]);
}
/**
* Check if token is whitespace.
*
* @param null|string $whitespaces whitespace characters, default is " \t\n\r\0\x0B"
*/
public function isWhitespace(?string $whitespaces = " \t\n\r\0\x0B"): bool
{
if (null === $whitespaces) {
$whitespaces = " \t\n\r\0\x0B";
}
if ($this->isArray && !$this->isGivenKind(T_WHITESPACE)) {
return false;
}
return '' === trim($this->content, $whitespaces);
}
/**
* @return array{
* id: int|null,
* name: string|null,
* content: string,
* isArray: bool,
* changed: bool,
* }
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->getName(),
'content' => $this->content,
'isArray' => $this->isArray,
'changed' => $this->changed,
];
}
public function toJson(): string
{
$jsonResult = json_encode($this->toArray(), JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK);
if (JSON_ERROR_NONE !== json_last_error()) {
$jsonResult = json_encode(
[
'errorDescription' => 'Cannot encode Tokens to JSON.',
'rawErrorMessage' => json_last_error_msg(),
],
JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK
);
}
return $jsonResult;
}
/**
* @param list<string> $tokenNames
*
* @return array<int, int>
*/
private static function getTokenKindsForNames(array $tokenNames): array
{
$keywords = [];
foreach ($tokenNames as $keywordName) {
if (\defined($keywordName)) {
$keyword = \constant($keywordName);
$keywords[$keyword] = $keyword;
}
}
return $keywords;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,766 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer;
/**
* Analyzer of Tokens collection.
*
* Its role is to provide the ability to analyze collection.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
* @author Gregor Harlan <gharlan@web.de>
*
* @internal
*/
final class TokensAnalyzer
{
/**
* Tokens collection instance.
*/
private Tokens $tokens;
private ?GotoLabelAnalyzer $gotoLabelAnalyzer = null;
public function __construct(Tokens $tokens)
{
$this->tokens = $tokens;
}
/**
* Get indices of methods and properties in classy code (classes, interfaces and traits).
*
* @return array<int, array{classIndex: int, token: Token, type: string}>
*/
public function getClassyElements(): array
{
$elements = [];
for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) {
if ($this->tokens[$index]->isClassy()) {
[$index, $newElements] = $this->findClassyElements($index, $index);
$elements += $newElements;
}
}
ksort($elements);
return $elements;
}
/**
* Get indices of namespace uses.
*
* @param bool $perNamespace Return namespace uses per namespace
*
* @return ($perNamespace is true ? array<int, list<int>> : list<int>)
*/
public function getImportUseIndexes(bool $perNamespace = false): array
{
$tokens = $this->tokens;
$uses = [];
$namespaceIndex = 0;
for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) {
$token = $tokens[$index];
if ($token->isGivenKind(T_NAMESPACE)) {
$nextTokenIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
$nextToken = $tokens[$nextTokenIndex];
if ($nextToken->equals('{')) {
$index = $nextTokenIndex;
}
if ($perNamespace) {
++$namespaceIndex;
}
continue;
}
if ($token->isGivenKind(T_USE)) {
$uses[$namespaceIndex][] = $index;
}
}
if (!$perNamespace && isset($uses[$namespaceIndex])) {
return $uses[$namespaceIndex];
}
return $uses;
}
/**
* Check if there is an array at given index.
*/
public function isArray(int $index): bool
{
return $this->tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]);
}
/**
* Check if the array at index is multiline.
*
* This only checks the root-level of the array.
*/
public function isArrayMultiLine(int $index): bool
{
if (!$this->isArray($index)) {
throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index));
}
$tokens = $this->tokens;
// Skip only when it's an array, for short arrays we need the brace for correct
// level counting
if ($tokens[$index]->isGivenKind(T_ARRAY)) {
$index = $tokens->getNextMeaningfulToken($index);
}
return $this->isBlockMultiline($tokens, $index);
}
public function isBlockMultiline(Tokens $tokens, int $index): bool
{
$blockType = Tokens::detectBlockType($tokens[$index]);
if (null === $blockType || !$blockType['isStart']) {
throw new \InvalidArgumentException(sprintf('Not an block start at given index %d.', $index));
}
$endIndex = $tokens->findBlockEnd($blockType['type'], $index);
for (++$index; $index < $endIndex; ++$index) {
$token = $tokens[$index];
$blockType = Tokens::detectBlockType($token);
if (null !== $blockType && $blockType['isStart']) {
$index = $tokens->findBlockEnd($blockType['type'], $index);
continue;
}
if (
$token->isWhitespace()
&& !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC)
&& str_contains($token->getContent(), "\n")
) {
return true;
}
}
return false;
}
/**
* @param int $index Index of the T_FUNCTION token
*
* @return array{visibility: null|T_PRIVATE|T_PROTECTED|T_PUBLIC, static: bool, abstract: bool, final: bool}
*/
public function getMethodAttributes(int $index): array
{
$tokens = $this->tokens;
$token = $tokens[$index];
if (!$token->isGivenKind(T_FUNCTION)) {
throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got "%s".', $index, $token->getName()));
}
$attributes = [
'visibility' => null,
'static' => false,
'abstract' => false,
'final' => false,
];
for ($i = $index; $i >= 0; --$i) {
$tokenIndex = $tokens->getPrevMeaningfulToken($i);
$i = $tokenIndex;
$token = $tokens[$tokenIndex];
if ($token->isGivenKind(T_STATIC)) {
$attributes['static'] = true;
continue;
}
if ($token->isGivenKind(T_FINAL)) {
$attributes['final'] = true;
continue;
}
if ($token->isGivenKind(T_ABSTRACT)) {
$attributes['abstract'] = true;
continue;
}
// visibility
if ($token->isGivenKind(T_PRIVATE)) {
$attributes['visibility'] = T_PRIVATE;
continue;
}
if ($token->isGivenKind(T_PROTECTED)) {
$attributes['visibility'] = T_PROTECTED;
continue;
}
if ($token->isGivenKind(T_PUBLIC)) {
$attributes['visibility'] = T_PUBLIC;
continue;
}
// found a meaningful token that is not part of
// the function signature; stop looking
break;
}
return $attributes;
}
/**
* Check if there is an anonymous class under given index.
*/
public function isAnonymousClass(int $index): bool
{
if (!$this->tokens[$index]->isClassy()) {
throw new \LogicException(sprintf('No classy token at given index %d.', $index));
}
if (!$this->tokens[$index]->isGivenKind(T_CLASS)) {
return false;
}
$index = $this->tokens->getPrevMeaningfulToken($index);
while ($this->tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
$index = $this->tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
$index = $this->tokens->getPrevMeaningfulToken($index);
}
return $this->tokens[$index]->isGivenKind(T_NEW);
}
/**
* Check if the function under given index is a lambda.
*/
public function isLambda(int $index): bool
{
if (!$this->tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) {
throw new \LogicException(sprintf('No T_FUNCTION or T_FN at given index %d, got "%s".', $index, $this->tokens[$index]->getName()));
}
$startParenthesisIndex = $this->tokens->getNextMeaningfulToken($index);
$startParenthesisToken = $this->tokens[$startParenthesisIndex];
// skip & for `function & () {}` syntax
if ($startParenthesisToken->isGivenKind(CT::T_RETURN_REF)) {
$startParenthesisIndex = $this->tokens->getNextMeaningfulToken($startParenthesisIndex);
$startParenthesisToken = $this->tokens[$startParenthesisIndex];
}
return $startParenthesisToken->equals('(');
}
/**
* Check if the T_STRING under given index is a constant invocation.
*/
public function isConstantInvocation(int $index): bool
{
if (!$this->tokens[$index]->isGivenKind(T_STRING)) {
throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $this->tokens[$index]->getName()));
}
$nextIndex = $this->tokens->getNextMeaningfulToken($index);
if (
$this->tokens[$nextIndex]->equalsAny(['(', '{'])
|| $this->tokens[$nextIndex]->isGivenKind([T_AS, T_DOUBLE_COLON, T_ELLIPSIS, T_NS_SEPARATOR, CT::T_RETURN_REF, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, T_VARIABLE])
) {
return false;
}
$prevIndex = $this->tokens->getPrevMeaningfulToken($index);
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, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]) || $this->tokens[$prevIndex]->isObjectOperator()) {
return false;
}
while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) {
$prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
}
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])) {
return false;
}
// `FOO & $bar` could be:
// - function reference parameter: function baz(Foo & $bar) {}
// - bit operator: $x = FOO & $bar;
if ($this->tokens[$nextIndex]->equals('&') && $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(T_VARIABLE)) {
$checkIndex = $this->tokens->getPrevTokenOfKind($prevIndex, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]);
if ($this->tokens[$checkIndex]->isGivenKind(T_FUNCTION)) {
return false;
}
}
// check for `extends`/`implements`/`use` list
if ($this->tokens[$prevIndex]->equals(',')) {
$checkIndex = $prevIndex;
while ($this->tokens[$checkIndex]->equalsAny([',', [T_AS], [CT::T_NAMESPACE_OPERATOR], [T_NS_SEPARATOR], [T_STRING]])) {
$checkIndex = $this->tokens->getPrevMeaningfulToken($checkIndex);
}
if ($this->tokens[$checkIndex]->isGivenKind([T_EXTENDS, CT::T_GROUP_IMPORT_BRACE_OPEN, T_IMPLEMENTS, T_USE, CT::T_USE_TRAIT])) {
return false;
}
}
// check for array in double quoted string: `"..$foo[bar].."`
if ($this->tokens[$prevIndex]->equals('[') && $this->tokens[$nextIndex]->equals(']')) {
$checkToken = $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)];
if ($checkToken->equals('"') || $checkToken->isGivenKind([T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES, T_ENCAPSED_AND_WHITESPACE, T_VARIABLE])) {
return false;
}
}
// check for attribute: `#[Foo]`
if (AttributeAnalyzer::isAttribute($this->tokens, $index)) {
return false;
}
// check for goto label
if ($this->tokens[$nextIndex]->equals(':')) {
if (null === $this->gotoLabelAnalyzer) {
$this->gotoLabelAnalyzer = new GotoLabelAnalyzer();
}
if ($this->gotoLabelAnalyzer->belongsToGoToLabel($this->tokens, $nextIndex)) {
return false;
}
}
// check for non-capturing catches
while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING, CT::T_TYPE_ALTERNATION])) {
$prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
}
if ($this->tokens[$prevIndex]->equals('(')) {
$prevPrevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
if ($this->tokens[$prevPrevIndex]->isGivenKind(T_CATCH)) {
return false;
}
}
return true;
}
/**
* Checks if there is a unary successor operator under given index.
*/
public function isUnarySuccessorOperator(int $index): bool
{
static $allowedPrevToken = [
']',
[T_STRING],
[T_VARIABLE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
[CT::T_DYNAMIC_PROP_BRACE_CLOSE],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
];
$tokens = $this->tokens;
$token = $tokens[$index];
if (!$token->isGivenKind([T_INC, T_DEC])) {
return false;
}
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
return $prevToken->equalsAny($allowedPrevToken);
}
/**
* Checks if there is a unary predecessor operator under given index.
*/
public function isUnaryPredecessorOperator(int $index): bool
{
static $potentialSuccessorOperator = [T_INC, T_DEC];
static $potentialBinaryOperator = ['+', '-', '&', [CT::T_RETURN_REF]];
static $otherOperators;
if (null === $otherOperators) {
$otherOperators = ['!', '~', '@', [T_ELLIPSIS]];
}
static $disallowedPrevTokens;
if (null === $disallowedPrevTokens) {
$disallowedPrevTokens = [
']',
'}',
')',
'"',
'`',
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
[CT::T_DYNAMIC_PROP_BRACE_CLOSE],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
[T_CLASS_C],
[T_CONSTANT_ENCAPSED_STRING],
[T_DEC],
[T_DIR],
[T_DNUMBER],
[T_FILE],
[T_FUNC_C],
[T_INC],
[T_LINE],
[T_LNUMBER],
[T_METHOD_C],
[T_NS_C],
[T_STRING],
[T_TRAIT_C],
[T_VARIABLE],
];
}
$tokens = $this->tokens;
$token = $tokens[$index];
if ($token->isGivenKind($potentialSuccessorOperator)) {
return !$this->isUnarySuccessorOperator($index);
}
if ($token->equalsAny($otherOperators)) {
return true;
}
if (!$token->equalsAny($potentialBinaryOperator)) {
return false;
}
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
if (!$prevToken->equalsAny($disallowedPrevTokens)) {
return true;
}
if (!$token->equals('&') || !$prevToken->isGivenKind(T_STRING)) {
return false;
}
static $searchTokens = [
';',
'{',
'}',
[T_FUNCTION],
[T_OPEN_TAG],
[T_OPEN_TAG_WITH_ECHO],
];
$prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)];
return $prevToken->isGivenKind(T_FUNCTION);
}
/**
* Checks if there is a binary operator under given index.
*/
public function isBinaryOperator(int $index): bool
{
static $nonArrayOperators = [
'=' => true,
'*' => true,
'/' => true,
'%' => true,
'<' => true,
'>' => true,
'|' => true,
'^' => true,
'.' => true,
];
static $potentialUnaryNonArrayOperators = [
'+' => true,
'-' => true,
'&' => true,
];
static $arrayOperators;
if (null === $arrayOperators) {
$arrayOperators = [
T_AND_EQUAL => true, // &=
T_BOOLEAN_AND => true, // &&
T_BOOLEAN_OR => true, // ||
T_CONCAT_EQUAL => true, // .=
T_DIV_EQUAL => true, // /=
T_DOUBLE_ARROW => true, // =>
T_IS_EQUAL => true, // ==
T_IS_GREATER_OR_EQUAL => true, // >=
T_IS_IDENTICAL => true, // ===
T_IS_NOT_EQUAL => true, // !=, <>
T_IS_NOT_IDENTICAL => true, // !==
T_IS_SMALLER_OR_EQUAL => true, // <=
T_LOGICAL_AND => true, // and
T_LOGICAL_OR => true, // or
T_LOGICAL_XOR => true, // xor
T_MINUS_EQUAL => true, // -=
T_MOD_EQUAL => true, // %=
T_MUL_EQUAL => true, // *=
T_OR_EQUAL => true, // |=
T_PLUS_EQUAL => true, // +=
T_POW => true, // **
T_POW_EQUAL => true, // **=
T_SL => true, // <<
T_SL_EQUAL => true, // <<=
T_SR => true, // >>
T_SR_EQUAL => true, // >>=
T_XOR_EQUAL => true, // ^=
T_SPACESHIP => true, // <=>
T_COALESCE => true, // ??
T_COALESCE_EQUAL => true, // ??=
];
}
$tokens = $this->tokens;
$token = $tokens[$index];
if ($token->isGivenKind([T_INLINE_HTML, T_ENCAPSED_AND_WHITESPACE, CT::T_TYPE_INTERSECTION])) {
return false;
}
if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) {
return !$this->isUnaryPredecessorOperator($index);
}
if ($token->isArray()) {
return isset($arrayOperators[$token->getId()]);
}
if (isset($nonArrayOperators[$token->getContent()])) {
return true;
}
return false;
}
/**
* Check if `T_WHILE` token at given index is `do { ... } while ();` syntax
* and not `while () { ...}`.
*/
public function isWhilePartOfDoWhile(int $index): bool
{
$tokens = $this->tokens;
$token = $tokens[$index];
if (!$token->isGivenKind(T_WHILE)) {
throw new \LogicException(sprintf('No T_WHILE at given index %d, got "%s".', $index, $token->getName()));
}
$endIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$endIndex]->equals('}')) {
return false;
}
$startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex);
$beforeStartIndex = $tokens->getPrevMeaningfulToken($startIndex);
return $tokens[$beforeStartIndex]->isGivenKind(T_DO);
}
public function isSuperGlobal(int $index): bool
{
static $superNames = [
'$_COOKIE' => true,
'$_ENV' => true,
'$_FILES' => true,
'$_GET' => true,
'$_POST' => true,
'$_REQUEST' => true,
'$_SERVER' => true,
'$_SESSION' => true,
'$GLOBALS' => true,
];
$token = $this->tokens[$index];
if (!$token->isGivenKind(T_VARIABLE)) {
return false;
}
return isset($superNames[strtoupper($token->getContent())]);
}
/**
* Find classy elements.
*
* Searches in tokens from the classy (start) index till the end (index) of the classy.
* Returns an array; first value is the index until the method has analysed (int), second the found classy elements (array).
*
* @param int $classIndex classy index
*
* @return array{int, array<int, array{classIndex: int, token: Token, type: string}>}
*/
private function findClassyElements(int $classIndex, int $index): array
{
$elements = [];
$curlyBracesLevel = 0;
$bracesLevel = 0;
++$index; // skip the classy index itself
for ($count = \count($this->tokens); $index < $count; ++$index) {
$token = $this->tokens[$index];
if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) {
continue;
}
if ($token->isGivenKind(T_CLASS)) { // anonymous class in class
// check for nested anonymous classes inside the new call of an anonymous class,
// for example `new class(function (){new class(function (){new class(function (){}){};}){};}){};` etc.
// if class(XYZ) {} skip till `(` as XYZ might contain functions etc.
$nestedClassIndex = $index;
$index = $this->tokens->getNextMeaningfulToken($index);
if ($this->tokens[$index]->equals('(')) {
++$index; // move after `(`
for ($nestedBracesLevel = 1; $index < $count; ++$index) {
$token = $this->tokens[$index];
if ($token->equals('(')) {
++$nestedBracesLevel;
continue;
}
if ($token->equals(')')) {
--$nestedBracesLevel;
if (0 === $nestedBracesLevel) {
[$index, $newElements] = $this->findClassyElements($nestedClassIndex, $index);
$elements += $newElements;
break;
}
continue;
}
if ($token->isGivenKind(T_CLASS)) { // anonymous class in class
[$index, $newElements] = $this->findClassyElements($index, $index);
$elements += $newElements;
}
}
} else {
[$index, $newElements] = $this->findClassyElements($nestedClassIndex, $nestedClassIndex);
$elements += $newElements;
}
continue;
}
if ($token->equals('(')) {
++$bracesLevel;
continue;
}
if ($token->equals(')')) {
--$bracesLevel;
continue;
}
if ($token->equals('{')) {
++$curlyBracesLevel;
continue;
}
if ($token->equals('}')) {
--$curlyBracesLevel;
if (0 === $curlyBracesLevel) {
break;
}
continue;
}
if (1 !== $curlyBracesLevel || !$token->isArray()) {
continue;
}
if (0 === $bracesLevel && $token->isGivenKind(T_VARIABLE)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'property',
];
continue;
}
if ($token->isGivenKind(T_FUNCTION)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'method',
];
} elseif ($token->isGivenKind(T_CONST)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'const',
];
} elseif ($token->isGivenKind(CT::T_USE_TRAIT)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'trait_import',
];
} elseif ($token->isGivenKind(T_CASE)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'case',
];
}
}
return [$index, $elements];
}
}
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `array` typehint from T_ARRAY into CT::T_ARRAY_TYPEHINT.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class ArrayTypehintTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isGivenKind(T_ARRAY)) {
return;
}
$nextIndex = $tokens->getNextMeaningfulToken($index);
$nextToken = $tokens[$nextIndex];
if (!$nextToken->equals('(')) {
$tokens[$index] = new Token([CT::T_ARRAY_TYPEHINT, $token->getContent()]);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_ARRAY_TYPEHINT];
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transforms attribute related Tokens.
*
* @internal
*/
final class AttributeTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// must run before all other transformers that might touch attributes
return 200;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 80000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$tokens[$index]->isGivenKind(T_ATTRIBUTE)) {
return;
}
$level = 1;
do {
++$index;
if ($tokens[$index]->equals('[')) {
++$level;
} elseif ($tokens[$index]->equals(']')) {
--$level;
}
} while (0 < $level);
$tokens[$index] = new Token([CT::T_ATTRIBUTE_CLOSE, ']']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_ATTRIBUTE_CLOSE,
];
}
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform braced class instantiation braces in `(new Foo())` into CT::T_BRACE_CLASS_INSTANTIATION_OPEN
* and CT::T_BRACE_CLASS_INSTANTIATION_CLOSE.
*
* @author Sebastiaans Stok <s.stok@rollerscapes.net>
*
* @internal
*/
final class BraceClassInstantiationTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// must run after CurlyBraceTransformer and SquareBraceTransformer
return -2;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$tokens[$index]->equals('(') || !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NEW)) {
return;
}
if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny([
']',
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[T_ARRAY],
[T_CLASS],
[T_ELSEIF],
[T_FOR],
[T_FOREACH],
[T_IF],
[T_STATIC],
[T_STRING],
[T_SWITCH],
[T_VARIABLE],
[T_WHILE],
])) {
return;
}
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$tokens[$index] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '(']);
$tokens[$closeIndex] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, CT::T_BRACE_CLASS_INSTANTIATION_CLOSE];
}
}
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `class` class' constant from T_CLASS into CT::T_CLASS_CONSTANT.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class ClassConstantTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50500;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equalsAny([
[T_CLASS, 'class'],
[T_STRING, 'class'],
], false)) {
return;
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
$prevToken = $tokens[$prevIndex];
if ($prevToken->isGivenKind(T_DOUBLE_COLON)) {
$tokens[$index] = new Token([CT::T_CLASS_CONSTANT, $token->getContent()]);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_CLASS_CONSTANT];
}
}
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transforms for Constructor Property Promotion.
*
* Transform T_PUBLIC, T_PROTECTED and T_PRIVATE of Constructor Property Promotion into custom tokens.
*
* @internal
*/
final class ConstructorPromotionTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 80000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
return;
}
$index = $tokens->getNextMeaningfulToken($index);
if (!$tokens[$index]->isGivenKind(T_STRING) || '__construct' !== strtolower($tokens[$index]->getContent())) {
return;
}
/** @var int $openIndex */
$openIndex = $tokens->getNextMeaningfulToken($index); // we are @ '(' now
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
for ($index = $openIndex; $index < $closeIndex; ++$index) {
if ($tokens[$index]->isGivenKind(T_PUBLIC)) {
$tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $tokens[$index]->getContent()]);
} elseif ($tokens[$index]->isGivenKind(T_PROTECTED)) {
$tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $tokens[$index]->getContent()]);
} elseif ($tokens[$index]->isGivenKind(T_PRIVATE)) {
$tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $tokens[$index]->getContent()]);
}
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
];
}
}
@@ -0,0 +1,253 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform discriminate overloaded curly braces tokens.
*
* Performed transformations:
* - closing `}` for T_CURLY_OPEN into CT::T_CURLY_CLOSE,
* - closing `}` for T_DOLLAR_OPEN_CURLY_BRACES into CT::T_DOLLAR_CLOSE_CURLY_BRACES,
* - in `$foo->{$bar}` into CT::T_DYNAMIC_PROP_BRACE_OPEN and CT::T_DYNAMIC_PROP_BRACE_CLOSE,
* - in `${$foo}` into CT::T_DYNAMIC_VAR_BRACE_OPEN and CT::T_DYNAMIC_VAR_BRACE_CLOSE,
* - in `$array{$index}` into CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN and CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
* - in `use some\a\{ClassA, ClassB, ClassC as C}` into CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class CurlyBraceTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
$this->transformIntoCurlyCloseBrace($tokens, $token, $index);
$this->transformIntoDollarCloseBrace($tokens, $token, $index);
$this->transformIntoDynamicPropBraces($tokens, $token, $index);
$this->transformIntoDynamicVarBraces($tokens, $token, $index);
$this->transformIntoCurlyIndexBraces($tokens, $token, $index);
$this->transformIntoGroupUseBraces($tokens, $token, $index);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_CURLY_CLOSE,
CT::T_DOLLAR_CLOSE_CURLY_BRACES,
CT::T_DYNAMIC_PROP_BRACE_OPEN,
CT::T_DYNAMIC_PROP_BRACE_CLOSE,
CT::T_DYNAMIC_VAR_BRACE_OPEN,
CT::T_DYNAMIC_VAR_BRACE_CLOSE,
CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
CT::T_GROUP_IMPORT_BRACE_OPEN,
CT::T_GROUP_IMPORT_BRACE_CLOSE,
];
}
/**
* Transform closing `}` for T_CURLY_OPEN into CT::T_CURLY_CLOSE.
*
* This should be done at very beginning of curly braces transformations.
*/
private function transformIntoCurlyCloseBrace(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isGivenKind(T_CURLY_OPEN)) {
return;
}
$level = 1;
do {
++$index;
if ($tokens[$index]->equals('{') || $tokens[$index]->isGivenKind(T_CURLY_OPEN)) { // we count all kind of {
++$level;
} elseif ($tokens[$index]->equals('}')) { // we count all kind of }
--$level;
}
} while (0 < $level);
$tokens[$index] = new Token([CT::T_CURLY_CLOSE, '}']);
}
private function transformIntoDollarCloseBrace(Tokens $tokens, Token $token, int $index): void
{
if ($token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) {
$nextIndex = $tokens->getNextTokenOfKind($index, ['}']);
$tokens[$nextIndex] = new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']);
}
}
private function transformIntoDynamicPropBraces(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isObjectOperator()) {
return;
}
if (!$tokens[$index + 1]->equals('{')) {
return;
}
$openIndex = $index + 1;
$closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $openIndex);
$tokens[$openIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_OPEN, '{']);
$tokens[$closeIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}']);
}
private function transformIntoDynamicVarBraces(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals('$')) {
return;
}
$openIndex = $tokens->getNextMeaningfulToken($index);
if (null === $openIndex) {
return;
}
$openToken = $tokens[$openIndex];
if (!$openToken->equals('{')) {
return;
}
$closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $openIndex);
$tokens[$openIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_OPEN, '{']);
$tokens[$closeIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}']);
}
private function transformIntoCurlyIndexBraces(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals('{')) {
return;
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevIndex]->equalsAny([
[T_STRING],
[T_VARIABLE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
']',
')',
])) {
return;
}
if (
$tokens[$prevIndex]->isGivenKind(T_STRING)
&& !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isObjectOperator()
) {
return;
}
if (
$tokens[$prevIndex]->equals(')')
&& !$tokens[$tokens->getPrevMeaningfulToken(
$tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex)
)]->isGivenKind(T_ARRAY)
) {
return;
}
$closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index);
$tokens[$index] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']);
$tokens[$closeIndex] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}']);
}
private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals('{')) {
return;
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
return;
}
$closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index);
$tokens[$index] = new Token([CT::T_GROUP_IMPORT_BRACE_OPEN, '{']);
$tokens[$closeIndex] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']);
}
/**
* We do not want to rely on `$tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)` here,
* as it relies on block types that are assuming that `}` tokens are already transformed to Custom Tokens that are allowing to distinguish different block types.
* As we are just about to transform `{` and `}` into Custom Tokens by this transformer, thus we need to compare those tokens manually by content without using `Tokens::findBlockEnd`.
*/
private function naivelyFindCurlyBlockEnd(Tokens $tokens, int $startIndex): int
{
if (!$tokens->offsetExists($startIndex)) {
throw new \OutOfBoundsException(sprintf('Unavailable index: "%s".', $startIndex));
}
if ('{' !== $tokens[$startIndex]->getContent()) {
throw new \InvalidArgumentException(sprintf('Wrong start index: "%s".', $startIndex));
}
$blockLevel = 1;
$endIndex = $tokens->count() - 1;
for ($index = $startIndex + 1; $index !== $endIndex; ++$index) {
$token = $tokens[$index];
if ('{' === $token->getContent()) {
++$blockLevel;
continue;
}
if ('}' === $token->getContent()) {
--$blockLevel;
if (0 === $blockLevel) {
if (!$token->equals('}')) {
throw new \UnexpectedValueException(sprintf('Detected block end for index: "%s" was already transformed into other token type: "%s".', $startIndex, $token->getName()));
}
return $index;
}
}
}
throw new \UnexpectedValueException(sprintf('Missing block end for index: "%s".', $startIndex));
}
}
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class FirstClassCallableTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 80100;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (
$token->isGivenKind(T_ELLIPSIS)
&& $tokens[$tokens->getPrevMeaningfulToken($index)]->equals('(')
&& $tokens[$tokens->getNextMeaningfulToken($index)]->equals(')')
) {
$tokens[$index] = new Token([CT::T_FIRST_CLASS_CALLABLE, '...']);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_FIRST_CLASS_CALLABLE,
];
}
}
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform const/function import tokens.
*
* Performed transformations:
* - T_CONST into CT::T_CONST_IMPORT
* - T_FUNCTION into CT::T_FUNCTION_IMPORT
*
* @author Gregor Harlan <gharlan@web.de>
*
* @internal
*/
final class ImportTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// Should run after CurlyBraceTransformer and ReturnRefTransformer
return -1;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50600;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isGivenKind([T_CONST, T_FUNCTION])) {
return;
}
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
if (!$prevToken->isGivenKind(T_USE)) {
$nextToken = $tokens[$tokens->getNextTokenOfKind($index, ['=', '(', [CT::T_RETURN_REF], [CT::T_GROUP_IMPORT_BRACE_CLOSE]])];
if (!$nextToken->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) {
return;
}
}
$tokens[$index] = new Token([
$token->isGivenKind(T_FUNCTION) ? CT::T_FUNCTION_IMPORT : CT::T_CONST_IMPORT,
$token->getContent(),
]);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT];
}
}
@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED and T_NAME_RELATIVE into T_NAMESPACE T_NS_SEPARATOR T_STRING.
*
* @internal
*/
final class NameQualifiedTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
return 1; // must run before NamespaceOperatorTransformer
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 80000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($token->isGivenKind([T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED])) {
$this->transformQualified($tokens, $token, $index);
} elseif ($token->isGivenKind(T_NAME_RELATIVE)) {
$this->transformRelative($tokens, $token, $index);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [];
}
private function transformQualified(Tokens $tokens, Token $token, int $index): void
{
$parts = explode('\\', $token->getContent());
$newTokens = [];
if ('' === $parts[0]) {
$newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
array_shift($parts);
}
foreach ($parts as $part) {
$newTokens[] = new Token([T_STRING, $part]);
$newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
}
array_pop($newTokens);
$tokens->overrideRange($index, $index, $newTokens);
}
private function transformRelative(Tokens $tokens, Token $token, int $index): void
{
$parts = explode('\\', $token->getContent());
$newTokens = [
new Token([T_NAMESPACE, array_shift($parts)]),
new Token([T_NS_SEPARATOR, '\\']),
];
foreach ($parts as $part) {
$newTokens[] = new Token([T_STRING, $part]);
$newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
}
array_pop($newTokens);
$tokens->overrideRange($index, $index, $newTokens);
}
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform named argument tokens.
*
* @internal
*/
final class NamedArgumentTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after TypeColonTransformer
return -15;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 80000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$tokens[$index]->equals(':')) {
return;
}
$stringIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$stringIndex]->isGivenKind(T_STRING)) {
return;
}
$preStringIndex = $tokens->getPrevMeaningfulToken($stringIndex);
// if equals any [';', '{', '}', [T_OPEN_TAG]] than it is a goto label
// if equals ')' than likely it is a type colon, but sure not a name argument
// if equals '?' than it is part of ternary statement
if (!$tokens[$preStringIndex]->equalsAny([',', '('])) {
return;
}
$tokens[$stringIndex] = new Token([CT::T_NAMED_ARGUMENT_NAME, $tokens[$stringIndex]->getContent()]);
$tokens[$index] = new Token([CT::T_NAMED_ARGUMENT_COLON, ':']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_NAMED_ARGUMENT_COLON,
CT::T_NAMED_ARGUMENT_NAME,
];
}
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `namespace` operator from T_NAMESPACE into CT::T_NAMESPACE_OPERATOR.
*
* @author Gregor Harlan <gharlan@web.de>
*
* @internal
*/
final class NamespaceOperatorTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50300;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isGivenKind(T_NAMESPACE)) {
return;
}
$nextIndex = $tokens->getNextMeaningfulToken($index);
if ($tokens[$nextIndex]->isGivenKind(T_NS_SEPARATOR)) {
$tokens[$index] = new Token([CT::T_NAMESPACE_OPERATOR, $token->getContent()]);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_NAMESPACE_OPERATOR];
}
}
@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `?` operator into CT::T_NULLABLE_TYPE in `function foo(?Bar $b) {}`.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class NullableTypeTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after TypeColonTransformer
return -20;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 70100;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals('?')) {
return;
}
static $types;
if (null === $types) {
$types = [
'(',
',',
[CT::T_TYPE_COLON],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE],
[CT::T_ATTRIBUTE_CLOSE],
[T_PRIVATE],
[T_PROTECTED],
[T_PUBLIC],
[T_VAR],
[T_STATIC],
];
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
$types[] = [T_READONLY];
}
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$prevIndex]->equalsAny($types)) {
$tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_NULLABLE_TYPE];
}
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `&` operator into CT::T_RETURN_REF in `function & foo() {}`.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class ReturnRefTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($token->equals('&') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_FUNCTION, T_FN])) {
$tokens[$index] = new Token([CT::T_RETURN_REF, '&']);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_RETURN_REF];
}
}
@@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform discriminate overloaded square braces tokens.
*
* Performed transformations:
* - in `[1, 2, 3]` into CT::T_ARRAY_SQUARE_BRACE_OPEN and CT::T_ARRAY_SQUARE_BRACE_CLOSE,
* - in `[$a, &$b, [$c]] = array(1, 2, array(3))` into CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN and CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class SquareBraceTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// must run after CurlyBraceTransformer and AttributeTransformer
return -1;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
// Short array syntax was introduced in PHP 5.4, but the fixer is smart
// enough to handle it even before 5.4.
// Same for array destructing syntax sugar `[` introduced in PHP 7.1.
return 50000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($this->isArrayDestructing($tokens, $index)) {
$this->transformIntoDestructuringSquareBrace($tokens, $index);
return;
}
if ($this->isShortArray($tokens, $index)) {
$this->transformIntoArraySquareBrace($tokens, $index);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_ARRAY_SQUARE_BRACE_OPEN,
CT::T_ARRAY_SQUARE_BRACE_CLOSE,
CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN,
CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE,
];
}
private function transformIntoArraySquareBrace(Tokens $tokens, int $index): void
{
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index);
$tokens[$index] = new Token([CT::T_ARRAY_SQUARE_BRACE_OPEN, '[']);
$tokens[$endIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']']);
}
private function transformIntoDestructuringSquareBrace(Tokens $tokens, int $index): void
{
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index);
$tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']);
$tokens[$endIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']);
$previousMeaningfulIndex = $index;
$index = $tokens->getNextMeaningfulToken($index);
while ($index < $endIndex) {
if ($tokens[$index]->equals('[') && $tokens[$previousMeaningfulIndex]->equalsAny([[CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], ','])) {
$tokens[$tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index)] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']);
$tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']);
}
$previousMeaningfulIndex = $index;
$index = $tokens->getNextMeaningfulToken($index);
}
}
/**
* Check if token under given index is short array opening.
*/
private function isShortArray(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->equals('[')) {
return false;
}
static $disallowedPrevTokens = [
')',
']',
'}',
'"',
[T_CONSTANT_ENCAPSED_STRING],
[T_STRING],
[T_STRING_VARNAME],
[T_VARIABLE],
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[CT::T_DYNAMIC_PROP_BRACE_CLOSE],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
];
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
if ($prevToken->equalsAny($disallowedPrevTokens)) {
return false;
}
$nextToken = $tokens[$tokens->getNextMeaningfulToken($index)];
if ($nextToken->equals(']')) {
return true;
}
return !$this->isArrayDestructing($tokens, $index);
}
private function isArrayDestructing(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->equals('[')) {
return false;
}
static $disallowedPrevTokens = [
')',
']',
'"',
[T_CONSTANT_ENCAPSED_STRING],
[T_STRING],
[T_STRING_VARNAME],
[T_VARIABLE],
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[CT::T_DYNAMIC_PROP_BRACE_CLOSE],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
];
$prevIndex = $tokens->getPrevMeaningfulToken($index);
$prevToken = $tokens[$prevIndex];
if ($prevToken->equalsAny($disallowedPrevTokens)) {
return false;
}
if ($prevToken->isGivenKind(T_AS)) {
return true;
}
if ($prevToken->isGivenKind(T_DOUBLE_ARROW)) {
$variableIndex = $tokens->getPrevMeaningfulToken($prevIndex);
if (!$tokens[$variableIndex]->isGivenKind(T_VARIABLE)) {
return false;
}
$prevVariableIndex = $tokens->getPrevMeaningfulToken($variableIndex);
if ($tokens[$prevVariableIndex]->isGivenKind(T_AS)) {
return true;
}
}
$type = Tokens::detectBlockType($tokens[$index]);
$end = $tokens->findBlockEnd($type['type'], $index);
$nextToken = $tokens[$tokens->getNextMeaningfulToken($end)];
return $nextToken->equals('=');
}
}
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTypeTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `|` operator into CT::T_TYPE_ALTERNATION in `function foo(Type1 | Type2 $x) {`
* or `} catch (ExceptionType1 | ExceptionType2 $e) {`.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class TypeAlternationTransformer extends AbstractTypeTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after ArrayTypehintTransformer, TypeColonTransformer and AttributeTransformer
return -15;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 70100;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
$this->doProcess($tokens, $index, '|');
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_TYPE_ALTERNATION];
}
protected function replaceToken(Tokens $tokens, int $index): void
{
$tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']);
}
}
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `:` operator into CT::T_TYPE_COLON in `function foo() : int {}`.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class TypeColonTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after ReturnRefTransformer and UseTransformer
// and before TypeAlternationTransformer
return -10;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 70000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals(':')) {
return;
}
$endIndex = $tokens->getPrevMeaningfulToken($index);
if (
\defined('T_ENUM') // @TODO: drop condition when PHP 8.1+ is required
&& $tokens[$tokens->getPrevMeaningfulToken($endIndex)]->isGivenKind(T_ENUM)
) {
$tokens[$index] = new Token([CT::T_TYPE_COLON, ':']);
return;
}
if (!$tokens[$endIndex]->equals(')')) {
return;
}
$startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex);
$prevIndex = $tokens->getPrevMeaningfulToken($startIndex);
$prevToken = $tokens[$prevIndex];
// if this could be a function name we need to take one more step
if ($prevToken->isGivenKind(T_STRING)) {
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
$prevToken = $tokens[$prevIndex];
}
if ($prevToken->isGivenKind([T_FUNCTION, CT::T_RETURN_REF, CT::T_USE_LAMBDA, T_FN])) {
$tokens[$index] = new Token([CT::T_TYPE_COLON, ':']);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_TYPE_COLON];
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTypeTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `&` operator into CT::T_TYPE_INTERSECTION in `function foo(Type1 & Type2 $x) {`
* or `} catch (ExceptionType1 & ExceptionType2 $e) {`.
*
* @internal
*/
final class TypeIntersectionTransformer extends AbstractTypeTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after ArrayTypehintTransformer, TypeColonTransformer and AttributeTransformer
return -15;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 80100;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
$this->doProcess($tokens, $index, [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, '&']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_TYPE_INTERSECTION];
}
protected function replaceToken(Tokens $tokens, int $index): void
{
$tokens[$index] = new Token([CT::T_TYPE_INTERSECTION, '&']);
}
}
@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform T_USE into:
* - CT::T_USE_TRAIT for imports,
* - CT::T_USE_LAMBDA for lambda variable uses.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class UseTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// Should run after CurlyBraceTransformer and before TypeColonTransformer
return -5;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50300;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($token->isGivenKind(T_USE) && $this->isUseForLambda($tokens, $index)) {
$tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]);
return;
}
// Only search inside class/trait body for `T_USE` for traits.
// Cannot import traits inside interfaces or anywhere else
$classTypes = [T_TRAIT];
if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
$classTypes[] = T_ENUM;
}
if ($token->isGivenKind(T_CLASS)) {
if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON)) {
return;
}
} elseif (!$token->isGivenKind($classTypes)) {
return;
}
$index = $tokens->getNextTokenOfKind($index, ['{']);
$innerLimit = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
while ($index < $innerLimit) {
$token = $tokens[++$index];
if (!$token->isGivenKind(T_USE)) {
continue;
}
if ($this->isUseForLambda($tokens, $index)) {
$tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]);
} else {
$tokens[$index] = new Token([CT::T_USE_TRAIT, $token->getContent()]);
}
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_USE_TRAIT, CT::T_USE_LAMBDA];
}
/**
* Check if token under given index is `use` statement for lambda function.
*/
private function isUseForLambda(Tokens $tokens, int $index): bool
{
$nextToken = $tokens[$tokens->getNextMeaningfulToken($index)];
// test `function () use ($foo) {}` case
return $nextToken->equals('(');
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Move trailing whitespaces from comments and docs into following T_WHITESPACE token.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class WhitespacyCommentTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 50000;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isComment()) {
return;
}
$content = $token->getContent();
$trimmedContent = rtrim($content);
// nothing trimmed, nothing to do
if ($content === $trimmedContent) {
return;
}
$whitespaces = substr($content, \strlen($trimmedContent));
$tokens[$index] = new Token([$token->getId(), $trimmedContent]);
if (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) {
$tokens[$index + 1] = new Token([T_WHITESPACE, $whitespaces.$tokens[$index + 1]->getContent()]);
} else {
$tokens->insertAt($index + 1, new Token([T_WHITESPACE, $whitespaces]));
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [];
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
/**
* Interface for Transformer class.
*
* Transformer role is to register custom tokens and transform Tokens collection to use them.
*
* Custom token is a user defined token type and is used to separate different meaning of original token type.
* For example T_ARRAY is a token for both creating new array and typehinting a parameter. This two meaning should have two token types.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
interface TransformerInterface
{
/**
* Get tokens created by Transformer.
*
* @return list<int>
*/
public function getCustomTokens(): array;
/**
* Return the name of the transformer.
*
* The name must be all lowercase and without any spaces.
*
* @return string The name of the fixer
*/
public function getName(): string;
/**
* Returns the priority of the transformer.
*
* The default priority is 0 and higher priorities are executed first.
*/
public function getPriority(): int;
/**
* Return minimal required PHP version id to transform the code.
*
* Custom Token kinds from Transformers are always registered, but sometimes
* there is no need to analyse the Tokens if for sure we cannot find examined
* token kind, e.g. transforming `T_FUNCTION` in `<?php use function Foo\\bar;`
* code.
*/
public function getRequiredPhpVersionId(): int;
/**
* Process Token to transform it into custom token when needed.
*/
public function process(Tokens $tokens, Token $token, int $index): void;
}
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
/**
* Collection of Transformer classes.
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class Transformers
{
/**
* The registered transformers.
*
* @var list<TransformerInterface>
*/
private array $items = [];
/**
* Register built in Transformers.
*/
private function __construct()
{
$this->registerBuiltInTransformers();
usort($this->items, static function (TransformerInterface $a, TransformerInterface $b): int {
return $b->getPriority() <=> $a->getPriority();
});
}
public static function createSingleton(): self
{
static $instance = null;
if (!$instance) {
$instance = new self();
}
return $instance;
}
/**
* Transform given Tokens collection through all Transformer classes.
*
* @param Tokens $tokens Tokens collection
*/
public function transform(Tokens $tokens): void
{
foreach ($this->items as $transformer) {
foreach ($tokens as $index => $token) {
$transformer->process($tokens, $token, $index);
}
}
}
/**
* @param TransformerInterface $transformer Transformer
*/
private function registerTransformer(TransformerInterface $transformer): void
{
if (\PHP_VERSION_ID >= $transformer->getRequiredPhpVersionId()) {
$this->items[] = $transformer;
}
}
private function registerBuiltInTransformers(): void
{
static $registered = false;
if ($registered) {
return;
}
$registered = true;
foreach ($this->findBuiltInTransformers() as $transformer) {
$this->registerTransformer($transformer);
}
}
/**
* @return \Generator<TransformerInterface>
*/
private function findBuiltInTransformers(): iterable
{
/** @var SplFileInfo $file */
foreach (Finder::create()->files()->in(__DIR__.'/Transformer') as $file) {
$relativeNamespace = $file->getRelativePath();
$class = __NAMESPACE__.'\\Transformer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php');
yield new $class();
}
}
}