Missing dependancies
This commit is contained in:
+49
@@ -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;
|
||||
}
|
||||
+96
@@ -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
|
||||
;
|
||||
}
|
||||
}
|
||||
Vendored
+116
@@ -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(':');
|
||||
}
|
||||
}
|
||||
+49
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+79
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+44
@@ -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;
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+96
@@ -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();
|
||||
}
|
||||
}
|
||||
Vendored
+110
@@ -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;
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
}
|
||||
Vendored
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
+96
@@ -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;
|
||||
}
|
||||
}
|
||||
+157
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
+70
@@ -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;
|
||||
}
|
||||
}
|
||||
+63
@@ -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;
|
||||
}
|
||||
}
|
||||
+83
@@ -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]);
|
||||
}
|
||||
}
|
||||
+317
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+310
@@ -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;
|
||||
}
|
||||
}
|
||||
+269
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -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]]);
|
||||
}
|
||||
}
|
||||
+121
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
+88
@@ -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));
|
||||
}
|
||||
}
|
||||
+90
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -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]]);
|
||||
}
|
||||
}
|
||||
+52
@@ -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];
|
||||
}
|
||||
}
|
||||
Vendored
+63
@@ -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];
|
||||
}
|
||||
}
|
||||
Vendored
+79
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
+90
@@ -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];
|
||||
}
|
||||
}
|
||||
Vendored
+66
@@ -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];
|
||||
}
|
||||
}
|
||||
+80
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Vendored
+253
@@ -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));
|
||||
}
|
||||
}
|
||||
www-api/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Transformer/FirstClassCallableTransformer.php
Vendored
+58
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
+84
@@ -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];
|
||||
}
|
||||
}
|
||||
Vendored
+101
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+85
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Vendored
+62
@@ -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];
|
||||
}
|
||||
}
|
||||
Vendored
+94
@@ -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];
|
||||
}
|
||||
}
|
||||
Vendored
+56
@@ -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];
|
||||
}
|
||||
}
|
||||
Vendored
+199
@@ -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('=');
|
||||
}
|
||||
}
|
||||
Vendored
+69
@@ -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, '|']);
|
||||
}
|
||||
}
|
||||
Vendored
+95
@@ -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];
|
||||
}
|
||||
}
|
||||
Vendored
+67
@@ -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, '&']);
|
||||
}
|
||||
}
|
||||
+114
@@ -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('(');
|
||||
}
|
||||
}
|
||||
Vendored
+73
@@ -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 [];
|
||||
}
|
||||
}
|
||||
+68
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user