Missing dependancies
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user