Missing dependancies

This commit is contained in:
tokslaw7
2023-06-21 12:19:22 +00:00
parent fbf3f180c2
commit 421f25c80d
2356 changed files with 342670 additions and 4743 deletions
@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*
* @TODO 4.0 remove this analyzer and move this logic into a transformer
*/
final class AlternativeSyntaxAnalyzer
{
private const ALTERNATIVE_SYNTAX_BLOCK_EDGES = [
T_IF => [T_ENDIF, T_ELSE, T_ELSEIF],
T_ELSE => [T_ENDIF],
T_ELSEIF => [T_ENDIF, T_ELSE, T_ELSEIF],
T_FOR => [T_ENDFOR],
T_FOREACH => [T_ENDFOREACH],
T_WHILE => [T_ENDWHILE],
T_SWITCH => [T_ENDSWITCH],
];
public function belongsToAlternativeSyntax(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->equals(':')) {
return false;
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$prevIndex]->isGivenKind(T_ELSE)) {
return true;
}
if (!$tokens[$prevIndex]->equals(')')) {
return false;
}
$openParenthesisIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex);
$beforeOpenParenthesisIndex = $tokens->getPrevMeaningfulToken($openParenthesisIndex);
return $tokens[$beforeOpenParenthesisIndex]->isGivenKind([
T_DECLARE,
T_ELSEIF,
T_FOR,
T_FOREACH,
T_IF,
T_SWITCH,
T_WHILE,
]);
}
public function findAlternativeSyntaxBlockEnd(Tokens $tokens, int $index): int
{
if (!isset($tokens[$index])) {
throw new \InvalidArgumentException("There is no token at index {$index}.");
}
if (!$this->isStartOfAlternativeSyntaxBlock($tokens, $index)) {
throw new \InvalidArgumentException("Token at index {$index} is not the start of an alternative syntax block.");
}
$startTokenKind = $tokens[$index]->getId();
$endTokenKinds = self::ALTERNATIVE_SYNTAX_BLOCK_EDGES[$startTokenKind];
$findKinds = [[$startTokenKind]];
foreach ($endTokenKinds as $endTokenKind) {
$findKinds[] = [$endTokenKind];
}
while (true) {
$index = $tokens->getNextTokenOfKind($index, $findKinds);
if ($tokens[$index]->isGivenKind($endTokenKinds)) {
return $index;
}
if ($this->isStartOfAlternativeSyntaxBlock($tokens, $index)) {
$index = $this->findAlternativeSyntaxBlockEnd($tokens, $index);
}
}
}
private function isStartOfAlternativeSyntaxBlock(Tokens $tokens, int $index): bool
{
$map = self::ALTERNATIVE_SYNTAX_BLOCK_EDGES;
$startTokenKind = $tokens[$index]->getId();
if (null === $startTokenKind || !isset($map[$startTokenKind])) {
return false;
}
$index = $tokens->getNextMeaningfulToken($index);
if ($tokens[$index]->equals('(')) {
$index = $tokens->getNextMeaningfulToken(
$tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index)
);
}
return $tokens[$index]->equals(':');
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
abstract class AbstractControlCaseStructuresAnalysis
{
private int $index;
private int $open;
private int $close;
public function __construct(int $index, int $open, int $close)
{
$this->index = $index;
$this->open = $open;
$this->close = $close;
}
public function getIndex(): int
{
return $this->index;
}
public function getOpenIndex(): int
{
return $this->open;
}
public function getCloseIndex(): int
{
return $this->close;
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class ArgumentAnalysis
{
/**
* The name of the argument.
*/
private string $name;
/**
* The index where the name is located in the supplied Tokens object.
*/
private int $nameIndex;
/**
* The default value of the argument.
*/
private ?string $default;
/**
* The type analysis of the argument.
*/
private ?TypeAnalysis $typeAnalysis;
public function __construct(string $name, int $nameIndex, ?string $default, ?TypeAnalysis $typeAnalysis = null)
{
$this->name = $name;
$this->nameIndex = $nameIndex;
$this->default = $default ?: null;
$this->typeAnalysis = $typeAnalysis ?: null;
}
public function getDefault(): ?string
{
return $this->default;
}
public function hasDefault(): bool
{
return null !== $this->default;
}
public function getName(): string
{
return $this->name;
}
public function getNameIndex(): int
{
return $this->nameIndex;
}
public function getTypeAnalysis(): ?TypeAnalysis
{
return $this->typeAnalysis;
}
public function hasTypeAnalysis(): bool
{
return null !== $this->typeAnalysis;
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @author Kuba Werłos <werlos@gmail.com>
*
* @internal
*/
final class CaseAnalysis
{
private int $index;
private int $colonIndex;
public function __construct(int $index, int $colonIndex)
{
$this->index = $index;
$this->colonIndex = $colonIndex;
}
public function getIndex(): int
{
return $this->index;
}
public function getColonIndex(): int
{
return $this->colonIndex;
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class DefaultAnalysis
{
private int $index;
private int $colonIndex;
public function __construct(int $index, int $colonIndex)
{
$this->index = $index;
$this->colonIndex = $colonIndex;
}
public function getIndex(): int
{
return $this->index;
}
public function getColonIndex(): int
{
return $this->colonIndex;
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class EnumAnalysis extends AbstractControlCaseStructuresAnalysis
{
/**
* @var list<CaseAnalysis>
*/
private array $cases;
/**
* @param list<CaseAnalysis> $cases
*/
public function __construct(int $index, int $open, int $close, array $cases)
{
parent::__construct($index, $open, $close);
$this->cases = $cases;
}
/**
* @return list<CaseAnalysis>
*/
public function getCases(): array
{
return $this->cases;
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class MatchAnalysis extends AbstractControlCaseStructuresAnalysis
{
private ?DefaultAnalysis $defaultAnalysis;
public function __construct(int $index, int $open, int $close, ?DefaultAnalysis $defaultAnalysis)
{
parent::__construct($index, $open, $close);
$this->defaultAnalysis = $defaultAnalysis;
}
public function getDefaultAnalysis(): ?DefaultAnalysis
{
return $this->defaultAnalysis;
}
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class NamespaceAnalysis implements StartEndTokenAwareAnalysis
{
/**
* The fully qualified namespace name.
*/
private string $fullName;
/**
* The short version of the namespace.
*/
private string $shortName;
/**
* The start index of the namespace declaration in the analyzed Tokens.
*/
private int $startIndex;
/**
* The end index of the namespace declaration in the analyzed Tokens.
*/
private int $endIndex;
/**
* The start index of the scope of the namespace in the analyzed Tokens.
*/
private int $scopeStartIndex;
/**
* The end index of the scope of the namespace in the analyzed Tokens.
*/
private int $scopeEndIndex;
public function __construct(string $fullName, string $shortName, int $startIndex, int $endIndex, int $scopeStartIndex, int $scopeEndIndex)
{
$this->fullName = $fullName;
$this->shortName = $shortName;
$this->startIndex = $startIndex;
$this->endIndex = $endIndex;
$this->scopeStartIndex = $scopeStartIndex;
$this->scopeEndIndex = $scopeEndIndex;
}
public function getFullName(): string
{
return $this->fullName;
}
public function getShortName(): string
{
return $this->shortName;
}
public function getStartIndex(): int
{
return $this->startIndex;
}
public function getEndIndex(): int
{
return $this->endIndex;
}
public function getScopeStartIndex(): int
{
return $this->scopeStartIndex;
}
public function getScopeEndIndex(): int
{
return $this->scopeEndIndex;
}
public function isGlobalNamespace(): bool
{
return '' === $this->getFullName();
}
}
@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis
{
public const TYPE_CLASS = 1; // "classy" could be class, interface or trait
public const TYPE_FUNCTION = 2;
public const TYPE_CONSTANT = 3;
/**
* The fully qualified use namespace.
*/
private string $fullName;
/**
* The short version of use namespace or the alias name in case of aliased use statements.
*/
private string $shortName;
/**
* Is the use statement being aliased?
*/
private bool $isAliased;
/**
* The start index of the namespace declaration in the analyzed Tokens.
*/
private int $startIndex;
/**
* The end index of the namespace declaration in the analyzed Tokens.
*/
private int $endIndex;
/**
* The type of import: class, function or constant.
*/
private int $type;
public function __construct(string $fullName, string $shortName, bool $isAliased, int $startIndex, int $endIndex, int $type)
{
$this->fullName = $fullName;
$this->shortName = $shortName;
$this->isAliased = $isAliased;
$this->startIndex = $startIndex;
$this->endIndex = $endIndex;
$this->type = $type;
}
public function getFullName(): string
{
return $this->fullName;
}
public function getShortName(): string
{
return $this->shortName;
}
public function isAliased(): bool
{
return $this->isAliased;
}
public function getStartIndex(): int
{
return $this->startIndex;
}
public function getEndIndex(): int
{
return $this->endIndex;
}
public function getType(): int
{
return $this->type;
}
public function isClass(): bool
{
return self::TYPE_CLASS === $this->type;
}
public function isFunction(): bool
{
return self::TYPE_FUNCTION === $this->type;
}
public function isConstant(): bool
{
return self::TYPE_CONSTANT === $this->type;
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
interface StartEndTokenAwareAnalysis
{
/**
* The start index of the analyzed subject inside of the Tokens.
*/
public function getStartIndex(): int;
/**
* The end index of the analyzed subject inside of the Tokens.
*/
public function getEndIndex(): int;
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class SwitchAnalysis extends AbstractControlCaseStructuresAnalysis
{
/**
* @var list<CaseAnalysis>
*/
private array $cases;
private ?DefaultAnalysis $defaultAnalysis;
/**
* @param list<CaseAnalysis> $cases
*/
public function __construct(int $index, int $open, int $close, array $cases, ?DefaultAnalysis $defaultAnalysis)
{
parent::__construct($index, $open, $close);
$this->cases = $cases;
$this->defaultAnalysis = $defaultAnalysis;
}
/**
* @return list<CaseAnalysis>
*/
public function getCases(): array
{
return $this->cases;
}
public function getDefaultAnalysis(): ?DefaultAnalysis
{
return $this->defaultAnalysis;
}
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
/**
* @internal
*/
final class TypeAnalysis implements StartEndTokenAwareAnalysis
{
/**
* This list contains soft and hard reserved types that can be used or will be used by PHP at some point.
*
* More info:
*
* @see https://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.types
* @see https://php.net/manual/en/reserved.other-reserved-words.php
* @see https://php.net/manual/en/language.pseudo-types.php
*
* @var list<string>
*/
private static array $reservedTypes = [
'array',
'bool',
'callable',
'float',
'int',
'iterable',
'mixed',
'never',
'numeric',
'object',
'resource',
'self',
'string',
'void',
];
private string $name;
private int $startIndex;
private int $endIndex;
private bool $nullable;
public function __construct(string $name, int $startIndex, int $endIndex)
{
$this->name = $name;
$this->nullable = false;
if (str_starts_with($name, '?')) {
$this->name = substr($name, 1);
$this->nullable = true;
}
$this->startIndex = $startIndex;
$this->endIndex = $endIndex;
}
public function getName(): string
{
return $this->name;
}
public function getStartIndex(): int
{
return $this->startIndex;
}
public function getEndIndex(): int
{
return $this->endIndex;
}
public function isReservedType(): bool
{
return \in_array($this->name, self::$reservedTypes, true);
}
public function isNullable(): bool
{
return $this->nullable;
}
}
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
* @author Vladimir Reznichenko <kalessil@gmail.com>
*
* @internal
*/
final class ArgumentsAnalyzer
{
/**
* Count amount of parameters in a function/method reference.
*/
public function countArguments(Tokens $tokens, int $openParenthesis, int $closeParenthesis): int
{
return \count($this->getArguments($tokens, $openParenthesis, $closeParenthesis));
}
/**
* Returns start and end token indices of arguments.
*
* Returns an array with each key being the first token of an
* argument and the value the last. Including non-function tokens
* such as comments and white space tokens, but without the separation
* tokens like '(', ',' and ')'.
*
* @return array<int, int>
*/
public function getArguments(Tokens $tokens, int $openParenthesis, int $closeParenthesis): array
{
$arguments = [];
$firstSensibleToken = $tokens->getNextMeaningfulToken($openParenthesis);
if ($tokens[$firstSensibleToken]->equals(')')) {
return $arguments;
}
$paramContentIndex = $openParenthesis + 1;
$argumentsStart = $paramContentIndex;
for (; $paramContentIndex < $closeParenthesis; ++$paramContentIndex) {
$token = $tokens[$paramContentIndex];
// skip nested (), [], {} constructs
$blockDefinitionProbe = Tokens::detectBlockType($token);
if (null !== $blockDefinitionProbe && true === $blockDefinitionProbe['isStart']) {
$paramContentIndex = $tokens->findBlockEnd($blockDefinitionProbe['type'], $paramContentIndex);
continue;
}
// if comma matched, increase arguments counter
if ($token->equals(',')) {
if ($tokens->getNextMeaningfulToken($paramContentIndex) === $closeParenthesis) {
break; // trailing ',' in function call (PHP 7.3)
}
$arguments[$argumentsStart] = $paramContentIndex - 1;
$argumentsStart = $paramContentIndex + 1;
}
}
$arguments[$argumentsStart] = $paramContentIndex - 1;
return $arguments;
}
public function getArgumentInfo(Tokens $tokens, int $argumentStart, int $argumentEnd): ArgumentAnalysis
{
static $skipTypes = null;
if (null === $skipTypes) {
$skipTypes = [T_ELLIPSIS, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE];
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
$skipTypes[] = T_READONLY;
}
}
$info = [
'default' => null,
'name' => null,
'name_index' => null,
'type' => null,
'type_index_start' => null,
'type_index_end' => null,
];
$sawName = false;
for ($index = $argumentStart; $index <= $argumentEnd; ++$index) {
$token = $tokens[$index];
if (\defined('T_ATTRIBUTE') && $token->isGivenKind(T_ATTRIBUTE)) {
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
continue;
}
if (
$token->isComment()
|| $token->isWhitespace()
|| $token->isGivenKind($skipTypes)
|| $token->equals('&')
) {
continue;
}
if ($token->isGivenKind(T_VARIABLE)) {
$sawName = true;
$info['name_index'] = $index;
$info['name'] = $token->getContent();
continue;
}
if ($token->equals('=')) {
continue;
}
if ($sawName) {
$info['default'] .= $token->getContent();
} else {
$info['type_index_start'] = ($info['type_index_start'] > 0) ? $info['type_index_start'] : $index;
$info['type_index_end'] = $index;
$info['type'] .= $token->getContent();
}
}
return new ArgumentAnalysis(
$info['name'],
$info['name_index'],
$info['default'],
$info['type'] ? new TypeAnalysis($info['type'], $info['type_index_start'], $info['type_index_end']) : null
);
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class AttributeAnalyzer
{
private const TOKEN_KINDS_NOT_ALLOWED_IN_ATTRIBUTE = [
';',
'{',
[T_ATTRIBUTE],
[T_FUNCTION],
[T_OPEN_TAG],
[T_OPEN_TAG_WITH_ECHO],
[T_PRIVATE],
[T_PROTECTED],
[T_PUBLIC],
[T_RETURN],
[T_VARIABLE],
[CT::T_ATTRIBUTE_CLOSE],
];
/**
* Check if given index is an attribute declaration.
*/
public static function isAttribute(Tokens $tokens, int $index): bool
{
if (
!\defined('T_ATTRIBUTE') // attributes not available, PHP version lower than 8.0
|| !$tokens[$index]->isGivenKind(T_STRING) // checked token is not a string
|| !$tokens->isAnyTokenKindsFound([T_ATTRIBUTE]) // no attributes in the tokens collection
) {
return false;
}
$attributeStartIndex = $tokens->getPrevTokenOfKind($index, self::TOKEN_KINDS_NOT_ALLOWED_IN_ATTRIBUTE);
if (!$tokens[$attributeStartIndex]->isGivenKind(T_ATTRIBUTE)) {
return false;
}
// now, between attribute start and the attribute candidate index cannot be more "(" than ")"
$count = 0;
for ($i = $attributeStartIndex + 1; $i < $index; ++$i) {
if ($tokens[$i]->equals('(')) {
++$count;
} elseif ($tokens[$i]->equals(')')) {
--$count;
}
}
return 0 === $count;
}
}
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Kuba Werłos <werlos@gmail.com>
*
* @internal
*/
final class BlocksAnalyzer
{
public function isBlock(Tokens $tokens, ?int $openIndex, ?int $closeIndex): bool
{
if (null === $openIndex || null === $closeIndex) {
return false;
}
if (!$tokens->offsetExists($openIndex)) {
return false;
}
if (!$tokens->offsetExists($closeIndex)) {
return false;
}
$blockType = $this->getBlockType($tokens[$openIndex]);
if (null === $blockType) {
return false;
}
return $closeIndex === $tokens->findBlockEnd($blockType, $openIndex);
}
/**
* @return Tokens::BLOCK_TYPE_*
*/
private function getBlockType(Token $token): ?int
{
foreach (Tokens::getBlockEdgeDefinitions() as $blockType => $definition) {
if ($token->equals($definition['start'])) {
return $blockType;
}
}
return null;
}
}
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class ClassyAnalyzer
{
public function isClassyInvocation(Tokens $tokens, int $index): bool
{
$token = $tokens[$index];
if (!$token->isGivenKind(T_STRING)) {
throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $tokens[$index]->getName()));
}
if (\in_array(strtolower($token->getContent()), ['bool', 'float', 'int', 'iterable', 'object', 'parent', 'self', 'string', 'void', 'null', 'false', 'never'], true)) {
return false;
}
$next = $tokens->getNextMeaningfulToken($index);
$nextToken = $tokens[$next];
if ($nextToken->isGivenKind(T_NS_SEPARATOR)) {
return false;
}
if ($nextToken->isGivenKind([T_DOUBLE_COLON, T_ELLIPSIS, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, T_VARIABLE])) {
return true;
}
$prev = $tokens->getPrevMeaningfulToken($index);
while ($tokens[$prev]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) {
$prev = $tokens->getPrevMeaningfulToken($prev);
}
$prevToken = $tokens[$prev];
if ($prevToken->isGivenKind([T_EXTENDS, T_INSTANCEOF, T_INSTEADOF, T_IMPLEMENTS, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, CT::T_TYPE_COLON, CT::T_USE_TRAIT])) {
return true;
}
if (AttributeAnalyzer::isAttribute($tokens, $index)) {
return true;
}
// `Foo & $bar` could be:
// - function reference parameter: function baz(Foo & $bar) {}
// - bit operator: $x = Foo & $bar;
if ($nextToken->equals('&') && $tokens[$tokens->getNextMeaningfulToken($next)]->isGivenKind(T_VARIABLE)) {
$checkIndex = $tokens->getPrevTokenOfKind($prev + 1, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]);
return $tokens[$checkIndex]->isGivenKind(T_FUNCTION);
}
if (!$prevToken->equals(',')) {
return false;
}
do {
$prev = $tokens->getPrevMeaningfulToken($prev);
} while ($tokens[$prev]->equalsAny([',', [T_NS_SEPARATOR], [T_STRING], [CT::T_NAMESPACE_OPERATOR]]));
return $tokens[$prev]->isGivenKind([T_IMPLEMENTS, CT::T_USE_TRAIT]);
}
}
@@ -0,0 +1,317 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Kuba Werłos <werlos@gmail.com>
*
* @internal
*/
final class CommentsAnalyzer
{
private const TYPE_HASH = 1;
private const TYPE_DOUBLE_SLASH = 2;
private const TYPE_SLASH_ASTERISK = 3;
public function isHeaderComment(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->isGivenKind([T_COMMENT, T_DOC_COMMENT])) {
throw new \InvalidArgumentException('Given index must point to a comment.');
}
if (null === $tokens->getNextMeaningfulToken($index)) {
return false;
}
$prevIndex = $tokens->getPrevNonWhitespace($index);
if ($tokens[$prevIndex]->equals(';')) {
$braceCloseIndex = $tokens->getPrevMeaningfulToken($prevIndex);
if (!$tokens[$braceCloseIndex]->equals(')')) {
return false;
}
$braceOpenIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceCloseIndex);
$declareIndex = $tokens->getPrevMeaningfulToken($braceOpenIndex);
if (!$tokens[$declareIndex]->isGivenKind(T_DECLARE)) {
return false;
}
$prevIndex = $tokens->getPrevNonWhitespace($declareIndex);
}
return $tokens[$prevIndex]->isGivenKind(T_OPEN_TAG);
}
/**
* Check if comment at given index precedes structural element.
*
* @see https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#3-definitions
*/
public function isBeforeStructuralElement(Tokens $tokens, int $index): bool
{
$token = $tokens[$index];
if (!$token->isGivenKind([T_COMMENT, T_DOC_COMMENT])) {
throw new \InvalidArgumentException('Given index must point to a comment.');
}
$nextIndex = $index;
do {
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
// @TODO: drop condition when PHP 8.0+ is required
if (\defined('T_ATTRIBUTE')) {
while (null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_ATTRIBUTE)) {
$nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $nextIndex);
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
}
}
} while (null !== $nextIndex && $tokens[$nextIndex]->equals('('));
if (null === $nextIndex || $tokens[$nextIndex]->equals('}')) {
return false;
}
if ($this->isStructuralElement($tokens, $nextIndex)) {
return true;
}
if ($this->isValidControl($tokens, $token, $nextIndex)) {
return true;
}
if ($this->isValidVariable($tokens, $nextIndex)) {
return true;
}
if ($this->isValidLanguageConstruct($tokens, $token, $nextIndex)) {
return true;
}
if ($tokens[$nextIndex]->isGivenKind(CT::T_USE_TRAIT)) {
return true;
}
return false;
}
/**
* Return array of indices that are part of a comment started at given index.
*
* @param int $index T_COMMENT index
*
* @return list<int>
*/
public function getCommentBlockIndices(Tokens $tokens, int $index): array
{
if (!$tokens[$index]->isGivenKind(T_COMMENT)) {
throw new \InvalidArgumentException('Given index must point to a comment.');
}
$commentType = $this->getCommentType($tokens[$index]->getContent());
$indices = [$index];
if (self::TYPE_SLASH_ASTERISK === $commentType) {
return $indices;
}
$count = \count($tokens);
++$index;
for (; $index < $count; ++$index) {
if ($tokens[$index]->isComment()) {
if ($commentType === $this->getCommentType($tokens[$index]->getContent())) {
$indices[] = $index;
continue;
}
break;
}
if (!$tokens[$index]->isWhitespace() || $this->getLineBreakCount($tokens, $index, $index + 1) > 1) {
break;
}
}
return $indices;
}
/**
* @see https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md#3-definitions
*/
private function isStructuralElement(Tokens $tokens, int $index): bool
{
static $skip;
if (null === $skip) {
$skip = [
T_PRIVATE,
T_PROTECTED,
T_PUBLIC,
T_VAR,
T_FUNCTION,
T_ABSTRACT,
T_CONST,
T_NAMESPACE,
T_REQUIRE,
T_REQUIRE_ONCE,
T_INCLUDE,
T_INCLUDE_ONCE,
T_FINAL,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
];
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
$skip[] = T_READONLY;
}
}
$token = $tokens[$index];
if ($token->isClassy() || $token->isGivenKind($skip)) {
return true;
}
if ($token->isGivenKind(T_STATIC)) {
return !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON);
}
return false;
}
/**
* Checks control structures (for, foreach, if, switch, while) for correct docblock usage.
*
* @param Token $docsToken docs Token
* @param int $controlIndex index of control structure Token
*/
private function isValidControl(Tokens $tokens, Token $docsToken, int $controlIndex): bool
{
static $controlStructures = [
T_FOR,
T_FOREACH,
T_IF,
T_SWITCH,
T_WHILE,
];
if (!$tokens[$controlIndex]->isGivenKind($controlStructures)) {
return false;
}
$index = $tokens->getNextMeaningfulToken($controlIndex);
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$docsContent = $docsToken->getContent();
for ($index = $index + 1; $index < $endIndex; ++$index) {
$token = $tokens[$index];
if (
$token->isGivenKind(T_VARIABLE)
&& str_contains($docsContent, $token->getContent())
) {
return true;
}
}
return false;
}
/**
* Checks variable assignments through `list()`, `print()` etc. calls for correct docblock usage.
*
* @param Token $docsToken docs Token
* @param int $languageConstructIndex index of variable Token
*/
private function isValidLanguageConstruct(Tokens $tokens, Token $docsToken, int $languageConstructIndex): bool
{
static $languageStructures = [
T_LIST,
T_PRINT,
T_ECHO,
CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN,
];
if (!$tokens[$languageConstructIndex]->isGivenKind($languageStructures)) {
return false;
}
$endKind = $tokens[$languageConstructIndex]->isGivenKind(CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN)
? [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE]
: ')';
$endIndex = $tokens->getNextTokenOfKind($languageConstructIndex, [$endKind]);
$docsContent = $docsToken->getContent();
for ($index = $languageConstructIndex + 1; $index < $endIndex; ++$index) {
$token = $tokens[$index];
if ($token->isGivenKind(T_VARIABLE) && str_contains($docsContent, $token->getContent())) {
return true;
}
}
return false;
}
/**
* Checks variable assignments for correct docblock usage.
*
* @param int $index index of variable Token
*/
private function isValidVariable(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->isGivenKind(T_VARIABLE)) {
return false;
}
$nextIndex = $tokens->getNextMeaningfulToken($index);
return $tokens[$nextIndex]->equals('=');
}
private function getCommentType(string $content): int
{
if (str_starts_with($content, '#')) {
return self::TYPE_HASH;
}
if ('*' === $content[1]) {
return self::TYPE_SLASH_ASTERISK;
}
return self::TYPE_DOUBLE_SLASH;
}
private function getLineBreakCount(Tokens $tokens, int $whiteStart, int $whiteEnd): int
{
$lineCount = 0;
for ($i = $whiteStart; $i < $whiteEnd; ++$i) {
$lineCount += Preg::matchAll('/\R/u', $tokens[$i]->getContent());
}
return $lineCount;
}
}
@@ -0,0 +1,310 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\AbstractControlCaseStructuresAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\CaseAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\DefaultAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\EnumAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\MatchAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis;
use PhpCsFixer\Tokenizer\Tokens;
final class ControlCaseStructuresAnalyzer
{
/**
* @param list<int> $types Token types of interest of which analyzes must be returned
*
* @return \Generator<int, AbstractControlCaseStructuresAnalysis>
*/
public static function findControlStructures(Tokens $tokens, array $types): \Generator
{
if (\count($types) < 1) {
return; // quick skip
}
$typesWithCaseOrDefault = self::getTypesWithCaseOrDefault();
foreach ($types as $type) {
if (!\in_array($type, $typesWithCaseOrDefault, true)) {
throw new \InvalidArgumentException(sprintf('Unexpected type "%d".', $type));
}
}
if (!$tokens->isAnyTokenKindsFound($types)) {
return; // quick skip
}
$depth = -1;
/**
* @var list<array{
* kind: int|null,
* index: int,
* brace_count: int,
* cases: list<array{index: int, open: int}>,
* default: array{index: int, open: int}|null,
* alternative_syntax: bool,
* }> $stack
*/
$stack = [];
$isTypeOfInterest = false;
foreach ($tokens as $index => $token) {
if ($token->isGivenKind($typesWithCaseOrDefault)) {
++$depth;
$stack[$depth] = [
'kind' => $token->getId(),
'index' => $index,
'brace_count' => 0,
'cases' => [],
'default' => null,
'alternative_syntax' => false,
];
$isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
if ($token->isGivenKind(T_SWITCH)) {
$index = $tokens->getNextMeaningfulToken($index);
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index);
$stack[$depth]['alternative_syntax'] = $tokens[$stack[$depth]['open']]->equals(':');
} elseif (\defined('T_MATCH') && $token->isGivenKind(T_MATCH)) { // @TODO: drop condition when PHP 8.0+ is required
$index = $tokens->getNextMeaningfulToken($index);
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index);
} elseif (\defined('T_ENUM') && $token->isGivenKind(T_ENUM)) {
$stack[$depth]['open'] = $tokens->getNextTokenOfKind($index, ['{']);
}
continue;
}
if ($depth < 0) {
continue;
}
if ($token->equals('{')) {
++$stack[$depth]['brace_count'];
continue;
}
if ($token->equals('}')) {
--$stack[$depth]['brace_count'];
if (0 === $stack[$depth]['brace_count']) {
if ($stack[$depth]['alternative_syntax']) {
continue;
}
if ($isTypeOfInterest) {
$stack[$depth]['end'] = $index;
yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]);
}
array_pop($stack);
--$depth;
if ($depth < -1) { // @phpstan-ignore-line
throw new \RuntimeException('Analysis depth count failure.');
}
if (isset($stack[$depth]['kind'])) {
$isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
}
}
continue;
}
if ($tokens[$index]->isGivenKind(T_ENDSWITCH)) {
if (!$stack[$depth]['alternative_syntax']) {
throw new \RuntimeException('Analysis syntax failure, unexpected "T_ENDSWITCH".');
}
if (T_SWITCH !== $stack[$depth]['kind']) {
throw new \RuntimeException('Analysis type failure, unexpected "T_ENDSWITCH".');
}
if (0 !== $stack[$depth]['brace_count']) {
throw new \RuntimeException('Analysis count failure, unexpected "T_ENDSWITCH".');
}
$index = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
if ($isTypeOfInterest) {
$stack[$depth]['end'] = $index;
yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]);
}
array_pop($stack);
--$depth;
if ($depth < -1) { // @phpstan-ignore-line
throw new \RuntimeException('Analysis depth count failure ("T_ENDSWITCH").');
}
if (isset($stack[$depth]['kind'])) {
$isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
}
}
if (!$isTypeOfInterest) {
continue; // don't bother to analyze stuff that caller is not interested in
}
if ($token->isGivenKind(T_CASE)) {
$stack[$depth]['cases'][] = ['index' => $index, 'open' => self::findCaseOpen($tokens, $stack[$depth]['kind'], $index)];
} elseif ($token->isGivenKind(T_DEFAULT)) {
if (null !== $stack[$depth]['default']) {
throw new \RuntimeException('Analysis multiple "default" found.');
}
$stack[$depth]['default'] = ['index' => $index, 'open' => self::findDefaultOpen($tokens, $stack[$depth]['kind'], $index)];
}
}
}
/**
* @param array{
* kind: int,
* index: int,
* open: int,
* end: int,
* cases: list<array{index: int, open: int}>,
* default: null|array{index: int, open: int},
* } $analysis
*/
private static function buildControlCaseStructureAnalysis(array $analysis): AbstractControlCaseStructuresAnalysis
{
$default = null === $analysis['default']
? null
: new DefaultAnalysis($analysis['default']['index'], $analysis['default']['open'])
;
$cases = [];
foreach ($analysis['cases'] as $case) {
$cases[$case['index']] = new CaseAnalysis($case['index'], $case['open']);
}
sort($cases);
if (T_SWITCH === $analysis['kind']) {
return new SwitchAnalysis(
$analysis['index'],
$analysis['open'],
$analysis['end'],
$cases,
$default
);
}
if (\defined('T_ENUM') && T_ENUM === $analysis['kind']) {
return new EnumAnalysis(
$analysis['index'],
$analysis['open'],
$analysis['end'],
$cases
);
}
if (\defined('T_MATCH') && T_MATCH === $analysis['kind']) { // @TODO: drop condition when PHP 8.0+ is required
return new MatchAnalysis(
$analysis['index'],
$analysis['open'],
$analysis['end'],
$default
);
}
throw new \InvalidArgumentException(sprintf('Unexpected type "%d".', $analysis['kind']));
}
private static function findCaseOpen(Tokens $tokens, int $kind, int $index): int
{
if (T_SWITCH === $kind) {
$ternariesCount = 0;
do {
if ($tokens[$index]->equalsAny(['(', '{'])) { // skip constructs
$type = Tokens::detectBlockType($tokens[$index]);
$index = $tokens->findBlockEnd($type['type'], $index);
continue;
}
if ($tokens[$index]->equals('?')) {
++$ternariesCount;
continue;
}
if ($tokens[$index]->equalsAny([':', ';'])) {
if (0 === $ternariesCount) {
break;
}
--$ternariesCount;
}
} while (++$index);
return $index;
}
if (\defined('T_ENUM') && T_ENUM === $kind) {
return $tokens->getNextTokenOfKind($index, ['=', ';']);
}
throw new \InvalidArgumentException(sprintf('Unexpected case for type "%d".', $kind));
}
private static function findDefaultOpen(Tokens $tokens, int $kind, int $index): int
{
if (T_SWITCH === $kind) {
return $tokens->getNextTokenOfKind($index, [':', ';']);
}
if (\defined('T_MATCH') && T_MATCH === $kind) { // @TODO: drop condition when PHP 8.0+ is required
return $tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]);
}
throw new \InvalidArgumentException(sprintf('Unexpected default for type "%d".', $kind));
}
/**
* @return list<int>
*/
private static function getTypesWithCaseOrDefault(): array
{
$supportedTypes = [T_SWITCH];
if (\defined('T_MATCH')) { // @TODO: drop condition when PHP 8.0+ is required
$supportedTypes[] = T_MATCH;
}
if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
$supportedTypes[] = T_ENUM;
}
return $supportedTypes;
}
}
@@ -0,0 +1,269 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class FunctionsAnalyzer
{
/**
* @var array{tokens: string, imports: list<NamespaceUseAnalysis>, declarations: list<int>}
*/
private array $functionsAnalysis = ['tokens' => '', 'imports' => [], 'declarations' => []];
/**
* Important: risky because of the limited (file) scope of the tool.
*/
public function isGlobalFunctionCall(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->isGivenKind(T_STRING)) {
return false;
}
$nextIndex = $tokens->getNextMeaningfulToken($index);
if (!$tokens[$nextIndex]->equals('(')) {
return false;
}
$previousIsNamespaceSeparator = false;
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
$previousIsNamespaceSeparator = true;
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
}
$possibleKind = array_merge([T_DOUBLE_COLON, T_FUNCTION, CT::T_NAMESPACE_OPERATOR, T_NEW, CT::T_RETURN_REF, T_STRING], Token::getObjectOperatorKinds());
// @TODO: drop condition when PHP 8.0+ is required
if (\defined('T_ATTRIBUTE')) {
$possibleKind[] = T_ATTRIBUTE;
}
if ($tokens[$prevIndex]->isGivenKind($possibleKind)) {
return false;
}
if ($previousIsNamespaceSeparator) {
return true;
}
if ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) {
return false;
}
if ($tokens->isChanged() || $tokens->getCodeHash() !== $this->functionsAnalysis['tokens']) {
$this->buildFunctionsAnalysis($tokens);
}
// figure out in which namespace we are
$namespaceAnalyzer = new NamespacesAnalyzer();
$declarations = $namespaceAnalyzer->getDeclarations($tokens);
$scopeStartIndex = 0;
$scopeEndIndex = \count($tokens) - 1;
$inGlobalNamespace = false;
foreach ($declarations as $declaration) {
$scopeStartIndex = $declaration->getScopeStartIndex();
$scopeEndIndex = $declaration->getScopeEndIndex();
if ($index >= $scopeStartIndex && $index <= $scopeEndIndex) {
$inGlobalNamespace = $declaration->isGlobalNamespace();
break;
}
}
$call = strtolower($tokens[$index]->getContent());
// check if the call is to a function declared in the same namespace as the call is done,
// if the call is already in the global namespace than declared functions are in the same
// global namespace and don't need checking
if (!$inGlobalNamespace) {
/** @var int $functionNameIndex */
foreach ($this->functionsAnalysis['declarations'] as $functionNameIndex) {
if ($functionNameIndex < $scopeStartIndex || $functionNameIndex > $scopeEndIndex) {
continue;
}
if (strtolower($tokens[$functionNameIndex]->getContent()) === $call) {
return false;
}
}
}
/** @var NamespaceUseAnalysis $functionUse */
foreach ($this->functionsAnalysis['imports'] as $functionUse) {
if ($functionUse->getStartIndex() < $scopeStartIndex || $functionUse->getEndIndex() > $scopeEndIndex) {
continue;
}
if ($call !== strtolower($functionUse->getShortName())) {
continue;
}
// global import like `use function \str_repeat;`
return $functionUse->getShortName() === ltrim($functionUse->getFullName(), '\\');
}
if (AttributeAnalyzer::isAttribute($tokens, $index)) {
return false;
}
return true;
}
/**
* @return array<string, ArgumentAnalysis>
*/
public function getFunctionArguments(Tokens $tokens, int $functionIndex): array
{
$argumentsStart = $tokens->getNextTokenOfKind($functionIndex, ['(']);
$argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart);
$argumentAnalyzer = new ArgumentsAnalyzer();
$arguments = [];
foreach ($argumentAnalyzer->getArguments($tokens, $argumentsStart, $argumentsEnd) as $start => $end) {
$argumentInfo = $argumentAnalyzer->getArgumentInfo($tokens, $start, $end);
$arguments[$argumentInfo->getName()] = $argumentInfo;
}
return $arguments;
}
public function getFunctionReturnType(Tokens $tokens, int $methodIndex): ?TypeAnalysis
{
$argumentsStart = $tokens->getNextTokenOfKind($methodIndex, ['(']);
$argumentsEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStart);
$typeColonIndex = $tokens->getNextMeaningfulToken($argumentsEnd);
if (!$tokens[$typeColonIndex]->isGivenKind(CT::T_TYPE_COLON)) {
return null;
}
$type = '';
$typeStartIndex = $tokens->getNextMeaningfulToken($typeColonIndex);
$typeEndIndex = $typeStartIndex;
$functionBodyStart = $tokens->getNextTokenOfKind($typeColonIndex, ['{', ';', [T_DOUBLE_ARROW]]);
for ($i = $typeStartIndex; $i < $functionBodyStart; ++$i) {
if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) {
continue;
}
$type .= $tokens[$i]->getContent();
$typeEndIndex = $i;
}
return new TypeAnalysis($type, $typeStartIndex, $typeEndIndex);
}
public function isTheSameClassCall(Tokens $tokens, int $index): bool
{
if (!$tokens->offsetExists($index)) {
return false;
}
$operatorIndex = $tokens->getPrevMeaningfulToken($index);
if (null === $operatorIndex) {
return false;
}
if (!$tokens[$operatorIndex]->isObjectOperator() && !$tokens[$operatorIndex]->isGivenKind(T_DOUBLE_COLON)) {
return false;
}
$referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex);
if (null === $referenceIndex) {
return false;
}
return $tokens[$referenceIndex]->equalsAny([[T_VARIABLE, '$this'], [T_STRING, 'self'], [T_STATIC, 'static']], false);
}
private function buildFunctionsAnalysis(Tokens $tokens): void
{
$this->functionsAnalysis = [
'tokens' => $tokens->getCodeHash(),
'imports' => [],
'declarations' => [],
];
// find declarations
if ($tokens->isTokenKindFound(T_FUNCTION)) {
$end = \count($tokens);
for ($i = 0; $i < $end; ++$i) {
// skip classy, we are looking for functions not methods
if ($tokens[$i]->isGivenKind(Token::getClassyTokenKinds())) {
$i = $tokens->getNextTokenOfKind($i, ['(', '{']);
if ($tokens[$i]->equals('(')) { // anonymous class
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $i);
$i = $tokens->getNextTokenOfKind($i, ['{']);
}
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i);
continue;
}
if (!$tokens[$i]->isGivenKind(T_FUNCTION)) {
continue;
}
$i = $tokens->getNextMeaningfulToken($i);
if ($tokens[$i]->isGivenKind(CT::T_RETURN_REF)) {
$i = $tokens->getNextMeaningfulToken($i);
}
if (!$tokens[$i]->isGivenKind(T_STRING)) {
continue;
}
$this->functionsAnalysis['declarations'][] = $i;
}
}
// find imported functions
$namespaceUsesAnalyzer = new NamespaceUsesAnalyzer();
if ($tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) {
$declarations = $namespaceUsesAnalyzer->getDeclarationsFromTokens($tokens);
foreach ($declarations as $declaration) {
if ($declaration->isFunction()) {
$this->functionsAnalysis['imports'][] = $declaration;
}
}
}
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class GotoLabelAnalyzer
{
public function belongsToGoToLabel(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->equals(':')) {
return false;
}
$prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_STRING)) {
return false;
}
$prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($prevMeaningfulTokenIndex);
return $tokens[$prevMeaningfulTokenIndex]->equalsAny([':', ';', '{', '}', [T_OPEN_TAG]]);
}
}
@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;
/**
* @internal
*/
final class NamespaceUsesAnalyzer
{
/**
* @return list<NamespaceUseAnalysis>
*/
public function getDeclarationsFromTokens(Tokens $tokens): array
{
$tokenAnalyzer = new TokensAnalyzer($tokens);
$useIndices = $tokenAnalyzer->getImportUseIndexes();
return $this->getDeclarations($tokens, $useIndices);
}
/**
* @return list<NamespaceUseAnalysis>
*/
public function getDeclarationsInNamespace(Tokens $tokens, NamespaceAnalysis $namespace): array
{
$namespaceUses = [];
foreach ($this->getDeclarationsFromTokens($tokens) as $namespaceUse) {
if ($namespaceUse->getStartIndex() >= $namespace->getScopeStartIndex() && $namespaceUse->getStartIndex() <= $namespace->getScopeEndIndex()) {
$namespaceUses[] = $namespaceUse;
}
}
return $namespaceUses;
}
/**
* @param list<int> $useIndices
*
* @return list<NamespaceUseAnalysis>
*/
private function getDeclarations(Tokens $tokens, array $useIndices): array
{
$uses = [];
foreach ($useIndices as $index) {
$endIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
$analysis = $this->parseDeclaration($tokens, $index, $endIndex);
if (null !== $analysis) {
$uses[] = $analysis;
}
}
return $uses;
}
private function parseDeclaration(Tokens $tokens, int $startIndex, int $endIndex): ?NamespaceUseAnalysis
{
$fullName = $shortName = '';
$aliased = false;
$type = NamespaceUseAnalysis::TYPE_CLASS;
for ($i = $startIndex; $i <= $endIndex; ++$i) {
$token = $tokens[$i];
if ($token->equals(',') || $token->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) {
// do not touch group use declarations until the logic of this is added (for example: `use some\a\{ClassD};`)
// ignore multiple use statements that should be split into few separate statements (for example: `use BarB, BarC as C;`)
return null;
}
if ($token->isGivenKind(CT::T_FUNCTION_IMPORT)) {
$type = NamespaceUseAnalysis::TYPE_FUNCTION;
} elseif ($token->isGivenKind(CT::T_CONST_IMPORT)) {
$type = NamespaceUseAnalysis::TYPE_CONSTANT;
}
if ($token->isWhitespace() || $token->isComment() || $token->isGivenKind(T_USE)) {
continue;
}
if ($token->isGivenKind(T_STRING)) {
$shortName = $token->getContent();
if (!$aliased) {
$fullName .= $shortName;
}
} elseif ($token->isGivenKind(T_NS_SEPARATOR)) {
$fullName .= $token->getContent();
} elseif ($token->isGivenKind(T_AS)) {
$aliased = true;
}
}
return new NamespaceUseAnalysis(
trim($fullName),
$shortName,
$aliased,
$startIndex,
$endIndex,
$type
);
}
}
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class NamespacesAnalyzer
{
/**
* @return list<NamespaceAnalysis>
*/
public function getDeclarations(Tokens $tokens): array
{
$namespaces = [];
for ($index = 1, $count = \count($tokens); $index < $count; ++$index) {
$token = $tokens[$index];
if (!$token->isGivenKind(T_NAMESPACE)) {
continue;
}
$declarationEndIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
$namespace = trim($tokens->generatePartialCode($index + 1, $declarationEndIndex - 1));
$declarationParts = explode('\\', $namespace);
$shortName = end($declarationParts);
if ($tokens[$declarationEndIndex]->equals('{')) {
$scopeEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $declarationEndIndex);
} else {
$scopeEndIndex = $tokens->getNextTokenOfKind($declarationEndIndex, [[T_NAMESPACE]]);
if (null === $scopeEndIndex) {
$scopeEndIndex = \count($tokens);
}
--$scopeEndIndex;
}
$namespaces[] = new NamespaceAnalysis(
$namespace,
$shortName,
$index,
$declarationEndIndex,
$index,
$scopeEndIndex
);
// Continue the analysis after the end of this namespace to find the next one
$index = $scopeEndIndex;
}
if (0 === \count($namespaces)) {
$namespaces[] = new NamespaceAnalysis('', '', 0, 0, 0, \count($tokens) - 1);
}
return $namespaces;
}
public function getNamespaceAt(Tokens $tokens, int $index): NamespaceAnalysis
{
if (!$tokens->offsetExists($index)) {
throw new \InvalidArgumentException(sprintf('Token index %d does not exist.', $index));
}
foreach ($this->getDeclarations($tokens) as $namespace) {
if ($namespace->getScopeStartIndex() <= $index && $namespace->getScopeEndIndex() >= $index) {
return $namespace;
}
}
throw new \LogicException(sprintf('Unable to get the namespace at index %d.', $index));
}
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class RangeAnalyzer
{
private function __construct()
{
// cannot create instance of util. class
}
/**
* Meaningful compare of tokens within ranges.
*
* @param array{start: int, end: int} $range1
* @param array{start: int, end: int} $range2
*/
public static function rangeEqualsRange(Tokens $tokens, array $range1, array $range2): bool
{
$leftStart = $range1['start'];
$leftEnd = $range1['end'];
if ($tokens[$leftStart]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
$leftStart = $tokens->getNextMeaningfulToken($leftStart);
}
while ($tokens[$leftStart]->equals('(') && $tokens[$leftEnd]->equals(')')) {
$leftStart = $tokens->getNextMeaningfulToken($leftStart);
$leftEnd = $tokens->getPrevMeaningfulToken($leftEnd);
}
$rightStart = $range2['start'];
$rightEnd = $range2['end'];
if ($tokens[$rightStart]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
$rightStart = $tokens->getNextMeaningfulToken($rightStart);
}
while ($tokens[$rightStart]->equals('(') && $tokens[$rightEnd]->equals(')')) {
$rightStart = $tokens->getNextMeaningfulToken($rightStart);
$rightEnd = $tokens->getPrevMeaningfulToken($rightEnd);
}
$arrayOpenTypes = ['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]];
$arrayCloseTypes = [']', [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE]];
while (true) {
$leftToken = $tokens[$leftStart];
$rightToken = $tokens[$rightStart];
if (
!$leftToken->equals($rightToken)
&& !($leftToken->equalsAny($arrayOpenTypes) && $rightToken->equalsAny($arrayOpenTypes))
&& !($leftToken->equalsAny($arrayCloseTypes) && $rightToken->equalsAny($arrayCloseTypes))
) {
return false;
}
$leftStart = $tokens->getNextMeaningfulToken($leftStart);
$rightStart = $tokens->getNextMeaningfulToken($rightStart);
$reachedLeftEnd = null === $leftStart || $leftStart > $leftEnd; // reached end left or moved over
$reachedRightEnd = null === $rightStart || $rightStart > $rightEnd; // reached end right or moved over
if (!$reachedLeftEnd && !$reachedRightEnd) {
continue;
}
return $reachedLeftEnd && $reachedRightEnd;
}
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Kuba Werłos <werlos@gmail.com>
*
* @internal
*/
final class ReferenceAnalyzer
{
public function isReference(Tokens $tokens, int $index): bool
{
if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) {
return true;
}
if (!$tokens[$index]->equals('&')) {
return false;
}
/** @var int $index */
$index = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$index]->equalsAny(['=', [T_AS], [T_CALLABLE], [T_DOUBLE_ARROW], [CT::T_ARRAY_TYPEHINT]])) {
return true;
}
if ($tokens[$index]->isGivenKind(T_STRING)) {
$index = $tokens->getPrevMeaningfulToken($index);
}
return $tokens[$index]->equalsAny(['(', ',', [T_NS_SEPARATOR], [CT::T_NULLABLE_TYPE]]);
}
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Analyzer;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class WhitespacesAnalyzer
{
public static function detectIndent(Tokens $tokens, int $index): string
{
while (true) {
$whitespaceIndex = $tokens->getPrevTokenOfKind($index, [[T_WHITESPACE]]);
if (null === $whitespaceIndex) {
return '';
}
$whitespaceToken = $tokens[$whitespaceIndex];
if (str_contains($whitespaceToken->getContent(), "\n")) {
break;
}
$prevToken = $tokens[$whitespaceIndex - 1];
if ($prevToken->isGivenKind([T_OPEN_TAG, T_COMMENT]) && "\n" === substr($prevToken->getContent(), -1)) {
break;
}
$index = $whitespaceIndex;
}
$explodedContent = explode("\n", $whitespaceToken->getContent());
return end($explodedContent);
}
}