PK ]NpVZn n logo.mdnu ٘ The logo is © 2010+ Sensio Labs.
Original resolution can be found at https://github.com/PHP-CS-Fixer/logo .
PK ]NpVr r feature-or-bug.rstnu ٘ ==========================
Is it a feature or a bug ?
==========================
Sometimes it's a bit tricky to define if given change proposal or change request is adding new feature or fixing existing issue. This document is providing more clarity about categorisation we use.
Bug
---
Example of bugs:
- crash during application or rule execution
- wrong changes are applied during "fixing codebase" process
- issue with generated report
Feature
-------
Example of features:
- introduction of new rule
- enhancement of existing rule to cover more cases (for example adding support for newly introduced PHP syntax)
- introduction of new ruleset
- update of existing ruleset (for example adjusting it to match newest style of given community or adding newly implemented rule that was supposed to be followed by style of given community, yet not implemented as a rule before)
PK ]NpV>% README.mdnu ٘
PHP Coding Standards Fixer
==========================
The PHP Coding Standards Fixer (PHP CS Fixer) tool fixes your code to follow standards;
whether you want to follow PHP coding standards as defined in the PSR-1, PSR-2, etc.,
or other community driven ones like the Symfony one.
You can **also** define your (team's) style through configuration.
It can modernize your code (like converting the ``pow`` function to the ``**`` operator on PHP 5.6)
and (micro) optimize it.
If you are already using a linter to identify coding standards problems in your
code, you know that fixing them by hand is tedious, especially on large
projects. This tool does not only detect them, but also fixes them for you.
## Supported PHP Versions
* PHP 7.4
* PHP 8.0 (except PHP 8.0.0 due to [bug in PHP tokenizer](https://bugs.php.net/bug.php?id=80462))
* PHP 8.1
* PHP 8.2
> **Note**
> Each new PHP version requires a huge effort to support the new syntax.
> That's why the latest PHP version might not be supported yet. If you need it,
> please, consider supporting the project in any convenient way, for example
> with code contribution or reviewing existing PRs. To run PHP CS Fixer on yet
> unsupported versions "at your own risk" - leverage the
> [PHP_CS_FIXER_IGNORE_ENV](./doc/usage.rst#environment-options).
## Documentation
### Installation
The recommended way to install PHP CS Fixer is to use [Composer](https://getcomposer.org/download/)
in a dedicated `composer.json` file in your project, for example in the
`tools/php-cs-fixer` directory:
```console
mkdir -p tools/php-cs-fixer
composer require --working-dir=tools/php-cs-fixer friendsofphp/php-cs-fixer
```
For more details and other installation methods, see
[installation instructions](./doc/installation.rst).
### Usage
Assuming you installed PHP CS Fixer as instructed above, you can run the
following command to fix the files PHP files in the `src` directory:
```console
tools/php-cs-fixer/vendor/bin/php-cs-fixer fix src
```
See [usage](./doc/usage.rst), list of [built-in rules](./doc/rules/index.rst), list of [rule sets](./doc/ruleSets/index.rst)
and [configuration file](./doc/config.rst) documentation for more details.
If you need to apply code styles that are not supported by the tool, you can
[create custom rules](./doc/custom_rules.rst).
## Editor Integration
Dedicated plugins exist for:
* [NetBeans](https://plugins.netbeans.apache.org/catalogue/?id=36)
* [PhpStorm](https://www.jetbrains.com/help/phpstorm/using-php-cs-fixer.html)
* [Sublime Text](https://github.com/benmatselby/sublime-phpcs)
* [Vim](https://github.com/stephpy/vim-php-cs-fixer)
* [VS Code](https://github.com/junstyle/vscode-php-cs-fixer)
## Community
The PHP CS Fixer is maintained on GitHub at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.
Bug reports and ideas about new features are welcome there.
You can reach us at https://gitter.im/PHP-CS-Fixer/Lobby about the project,
configuration, possible improvements, ideas and questions, please visit us!
## Contribute
The tool comes with quite a few built-in fixers, but everyone is more than
welcome to [contribute](CONTRIBUTING.md) more of them.
PK ]NpVzᤶz z CONTRIBUTING.mdnu ٘ # Contributions Are Welcome!
If you need any help, don't hesitate to ask the community on [Gitter](https://gitter.im/PHP-CS-Fixer/Lobby).
## Quick Guide
### Fixer
A *fixer* is a class that tries to fix one code style issue (a ``Fixer`` class
must implement ``FixerInterface``).
### Config
A *config* knows about the code style rules and the files and directories that
must be scanned by the tool when run in the directory of your project. It is
useful for projects that follow a well-known directory structures (like for
Symfony projects for instance).
### How-To
* [Fork](https://help.github.com/articles/fork-a-repo/) the repo.
* [Checkout](https://git-scm.com/docs/git-checkout) the branch you want to make changes on:
* If you are fixing a bug or typo, improving tests or for any small tweak: the lowest branch where the changes can be applied. Once your Pull Request is accepted, the changes will get merged up to highest branches.
* `master` in other cases (new feature, deprecation, or backwards compatibility breaking changes). Note that most of the time, `master` represents the next minor release of PHP CS Fixer, so Pull Requests that break backwards compatibility might be postponed.
* Install dependencies: `composer install`.
* Create a new branch, e.g. `feature-foo` or `bugfix-bar`.
* Make changes.
* If you are adding functionality or fixing a bug - add a test! Prefer adding new test cases over modifying existing ones.
* Make sure there is no wrong file permissions in the repository: `./dev-tools/check_file_permissions.sh`.
* Make sure there is no trailing spaces in the code: `./dev-tools/check_trailing_spaces.sh`.
* Update documentation: `php dev-tools/doc.php`. This requires the highest version of PHP supported by PHP CS Fixer. If it is not installed on your system, you can run it in a Docker container instead: `docker run -it --rm --user="$(id -u):$(id -g)" -w="/app" --volume="$(pwd):/app" php:7.4-cli php dev-tools/doc.php`.
* Install dev tools: `dev-tools/install.sh`
* Run static analysis using PHPStan: `php -d memory_limit=256M dev-tools/vendor/bin/phpstan analyse`
* Check if tests pass: `vendor/bin/phpunit`.
* Fix project itself: `php php-cs-fixer fix`.
## Working With Docker
This project provides a Docker setup that allows working on it using any of the supported PHP versions.
To use it, you first need to install:
* [Docker](https://docs.docker.com/get-docker/)
* [Docker Compose](https://docs.docker.com/compose/install/)
Make sure the versions installed support [Compose file format 3.8](https://docs.docker.com/compose/compose-file/).
Next, copy [`docker-compose.override.yaml.dist`](./docker-compose.override.yaml.dist) to `docker-compose.override.yaml`
and edit it to your needs. The relevant parameters that might require some tweaking have comments to help you.
You can then build the images:
```console
docker-compose build --parallel
```
Now you can run commands needed to work on the project. For example, say you want to run PHPUnit tests on PHP 7.4:
```console
docker-compose run php-7.4 vendor/bin/phpunit
```
Sometimes it can be more convenient to have a shell inside the container:
```console
docker-compose run php-7.4 sh
/app vendor/bin/phpunit
```
The images come with an [`xdebug` script](github.com/julienfalque/xdebug/) that allows running any PHP command with
Xdebug enabled to help debug problems.
```console
docker-compose run php-7.4 xdebug vendor/bin/phpunit
```
If you're using PhpStorm, you need to create a [server](https://www.jetbrains.com/help/phpstorm/servers.html) with a
name that matches the `PHP_IDE_CONFIG` environment variable defined in the Docker Compose configuration files, which is
`php-cs-fixer` by default.
All images use port 9003 for debug connections.
## Opening a [Pull Request](https://help.github.com/articles/about-pull-requests/)
You can do some things to increase the chance that your Pull Request is accepted the first time:
* Submit one Pull Request per fix or feature.
* If your changes are not up to date, [rebase](https://git-scm.com/docs/git-rebase) your branch onto the parent branch.
* Follow the conventions used in the project.
* Remember about tests and documentation.
* Don't bump version.
## Making New Fixers
There is a [cookbook](doc/cookbook_fixers.rst) with basic instructions on how to build a new fixer. Consider reading it
before opening a PR.
## Project's Standards
* [PSR-1: Basic Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
* [PSR-2: Coding Style Guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
* [PSR-4: Autoloading Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md)
* [PSR-5: PHPDoc (draft)](https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md)
* [Symfony Coding Standards](https://symfony.com/doc/current/contributing/code/standards.html)
PK ]NpV/Ba% % " src/AbstractNoUselessElseFixer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer;
use PhpCsFixer\Tokenizer\Tokens;
abstract class AbstractNoUselessElseFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// should be run before NoWhitespaceInBlankLineFixer, NoExtraBlankLinesFixer, BracesFixer and after NoEmptyStatementFixer.
return 39;
}
protected function isSuperfluousElse(Tokens $tokens, int $index): bool
{
$previousBlockStart = $index;
do {
// Check if all 'if', 'else if ' and 'elseif' blocks above this 'else' always end,
// if so this 'else' is overcomplete.
[$previousBlockStart, $previousBlockEnd] = $this->getPreviousBlock($tokens, $previousBlockStart);
// short 'if' detection
$previous = $previousBlockEnd;
if ($tokens[$previous]->equals('}')) {
$previous = $tokens->getPrevMeaningfulToken($previous);
}
if (
!$tokens[$previous]->equals(';') // 'if' block doesn't end with semicolon, keep 'else'
|| $tokens[$tokens->getPrevMeaningfulToken($previous)]->equals('{') // empty 'if' block, keep 'else'
) {
return false;
}
$candidateIndex = $tokens->getPrevTokenOfKind(
$previous,
[
';',
[T_BREAK],
[T_CLOSE_TAG],
[T_CONTINUE],
[T_EXIT],
[T_GOTO],
[T_IF],
[T_RETURN],
[T_THROW],
]
);
if (null === $candidateIndex || $tokens[$candidateIndex]->equalsAny([';', [T_CLOSE_TAG], [T_IF]])) {
return false;
}
if ($tokens[$candidateIndex]->isGivenKind(T_THROW)) {
$previousIndex = $tokens->getPrevMeaningfulToken($candidateIndex);
if (!$tokens[$previousIndex]->equalsAny([';', '{'])) {
return false;
}
}
if ($this->isInConditional($tokens, $candidateIndex, $previousBlockStart)
|| $this->isInConditionWithoutBraces($tokens, $candidateIndex, $previousBlockStart)
) {
return false;
}
// implicit continue, i.e. delete candidate
} while (!$tokens[$previousBlockStart]->isGivenKind(T_IF));
return true;
}
/**
* Return the first and last token index of the previous block.
*
* [0] First is either T_IF, T_ELSE or T_ELSEIF
* [1] Last is either '}' or ';' / T_CLOSE_TAG for short notation blocks
*
* @param int $index T_IF, T_ELSE, T_ELSEIF
*
* @return int[]
*/
private function getPreviousBlock(Tokens $tokens, int $index): array
{
$close = $previous = $tokens->getPrevMeaningfulToken($index);
// short 'if' detection
if ($tokens[$close]->equals('}')) {
$previous = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $close);
}
$open = $tokens->getPrevTokenOfKind($previous, [[T_IF], [T_ELSE], [T_ELSEIF]]);
if ($tokens[$open]->isGivenKind(T_IF)) {
$elseCandidate = $tokens->getPrevMeaningfulToken($open);
if ($tokens[$elseCandidate]->isGivenKind(T_ELSE)) {
$open = $elseCandidate;
}
}
return [$open, $close];
}
/**
* @param int $index Index of the token to check
* @param int $lowerLimitIndex Lower limit index. Since the token to check will always be in a conditional we must stop checking at this index
*/
private function isInConditional(Tokens $tokens, int $index, int $lowerLimitIndex): bool
{
$candidateIndex = $tokens->getPrevTokenOfKind($index, [')', ';', ':']);
if ($tokens[$candidateIndex]->equals(':')) {
return true;
}
if (!$tokens[$candidateIndex]->equals(')')) {
return false; // token is ';' or close tag
}
// token is always ')' here.
// If it is part of the condition the token is always in, return false.
// If it is not it is a nested condition so return true
$open = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $candidateIndex);
return $tokens->getPrevMeaningfulToken($open) > $lowerLimitIndex;
}
/**
* For internal use only, as it is not perfect.
*
* Returns if the token at given index is part of an if/elseif/else statement
* without {}. Assumes not passing the last `;`/close tag of the statement, not
* out of range index, etc.
*
* @param int $index Index of the token to check
*/
private function isInConditionWithoutBraces(Tokens $tokens, int $index, int $lowerLimitIndex): bool
{
do {
if ($tokens[$index]->isComment() || $tokens[$index]->isWhitespace()) {
$index = $tokens->getPrevMeaningfulToken($index);
}
$token = $tokens[$index];
if ($token->isGivenKind([T_IF, T_ELSEIF, T_ELSE])) {
return true;
}
if ($token->equals(';')) {
return false;
}
if ($token->equals('{')) {
$index = $tokens->getPrevMeaningfulToken($index);
// OK if belongs to: for, do, while, foreach
// Not OK if belongs to: if, else, elseif
if ($tokens[$index]->isGivenKind(T_DO)) {
--$index;
continue;
}
if (!$tokens[$index]->equals(')')) {
return false; // like `else {`
}
$index = $tokens->findBlockStart(
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
$index
);
$index = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$index]->isGivenKind([T_IF, T_ELSEIF])) {
return false;
}
} elseif ($token->equals(')')) {
$type = Tokens::detectBlockType($token);
$index = $tokens->findBlockStart(
$type['type'],
$index
);
$index = $tokens->getPrevMeaningfulToken($index);
} else {
--$index;
}
} while ($index > $lowerLimitIndex);
return false;
}
}
PK ]NpVGI- src/ToolInfoInterface.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer;
/**
* @internal
*/
interface ToolInfoInterface
{
/**
* @return array{name: string, version: string, dist: array{reference?: string}}
*/
public function getComposerInstallationDetails(): array;
public function getComposerVersion(): string;
public function getVersion(): string;
public function isInstalledAsPhar(): bool;
public function isInstalledByComposer(): bool;
public function getPharDownloadUri(string $version): string;
}
PK ]NpV>ʌd d src/Tokenizer/TokensAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
use PhpCsFixer\Tokenizer\Analyzer\AttributeAnalyzer;
use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer;
/**
* Analyzer of Tokens collection.
*
* Its role is to provide the ability to analyze collection.
*
* @author Dariusz Rumiński
* @author Gregor Harlan
*
* @internal
*/
final class TokensAnalyzer
{
/**
* Tokens collection instance.
*/
private Tokens $tokens;
private ?GotoLabelAnalyzer $gotoLabelAnalyzer = null;
public function __construct(Tokens $tokens)
{
$this->tokens = $tokens;
}
/**
* Get indices of methods and properties in classy code (classes, interfaces and traits).
*
* @return array
*/
public function getClassyElements(): array
{
$elements = [];
for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) {
if ($this->tokens[$index]->isClassy()) {
[$index, $newElements] = $this->findClassyElements($index, $index);
$elements += $newElements;
}
}
ksort($elements);
return $elements;
}
/**
* Get indices of modifiers of a classy code (classes, interfaces and traits).
*
* @return array{
* final: int|null,
* abstract: int|null,
* readonly: int|null
* }
*/
public function getClassyModifiers(int $index): array
{
if (!$this->tokens[$index]->isClassy()) {
throw new \InvalidArgumentException(sprintf('Not an "classy" at given index %d.', $index));
}
$readOnlyPossible = \defined('T_READONLY'); // @TODO: drop condition when PHP 8.2+ is required
$modifiers = ['final' => null, 'abstract' => null, 'readonly' => null];
while (true) {
$index = $this->tokens->getPrevMeaningfulToken($index);
if ($this->tokens[$index]->isGivenKind(T_FINAL)) {
$modifiers['final'] = $index;
} elseif ($this->tokens[$index]->isGivenKind(T_ABSTRACT)) {
$modifiers['abstract'] = $index;
} elseif ($readOnlyPossible && $this->tokens[$index]->isGivenKind(T_READONLY)) {
$modifiers['readonly'] = $index;
} else { // no need to skip attributes as it is not possible on PHP8.2
break;
}
}
return $modifiers;
}
/**
* Get indices of namespace uses.
*
* @param bool $perNamespace Return namespace uses per namespace
*
* @return ($perNamespace is true ? array> : list)
*/
public function getImportUseIndexes(bool $perNamespace = false): array
{
$tokens = $this->tokens;
$uses = [];
$namespaceIndex = 0;
for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) {
$token = $tokens[$index];
if ($token->isGivenKind(T_NAMESPACE)) {
$nextTokenIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
$nextToken = $tokens[$nextTokenIndex];
if ($nextToken->equals('{')) {
$index = $nextTokenIndex;
}
if ($perNamespace) {
++$namespaceIndex;
}
continue;
}
if ($token->isGivenKind(T_USE)) {
$uses[$namespaceIndex][] = $index;
}
}
if (!$perNamespace && isset($uses[$namespaceIndex])) {
return $uses[$namespaceIndex];
}
return $uses;
}
/**
* Check if there is an array at given index.
*/
public function isArray(int $index): bool
{
return $this->tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]);
}
/**
* Check if the array at index is multiline.
*
* This only checks the root-level of the array.
*/
public function isArrayMultiLine(int $index): bool
{
if (!$this->isArray($index)) {
throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index));
}
$tokens = $this->tokens;
// Skip only when it's an array, for short arrays we need the brace for correct
// level counting
if ($tokens[$index]->isGivenKind(T_ARRAY)) {
$index = $tokens->getNextMeaningfulToken($index);
}
return $this->isBlockMultiline($tokens, $index);
}
public function isBlockMultiline(Tokens $tokens, int $index): bool
{
$blockType = Tokens::detectBlockType($tokens[$index]);
if (null === $blockType || !$blockType['isStart']) {
throw new \InvalidArgumentException(sprintf('Not an block start at given index %d.', $index));
}
$endIndex = $tokens->findBlockEnd($blockType['type'], $index);
for (++$index; $index < $endIndex; ++$index) {
$token = $tokens[$index];
$blockType = Tokens::detectBlockType($token);
if (null !== $blockType && $blockType['isStart']) {
$index = $tokens->findBlockEnd($blockType['type'], $index);
continue;
}
if (
$token->isWhitespace()
&& !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC)
&& str_contains($token->getContent(), "\n")
) {
return true;
}
}
return false;
}
/**
* @param int $index Index of the T_FUNCTION token
*
* @return array{visibility: null|T_PRIVATE|T_PROTECTED|T_PUBLIC, static: bool, abstract: bool, final: bool}
*/
public function getMethodAttributes(int $index): array
{
if (!$this->tokens[$index]->isGivenKind(T_FUNCTION)) {
throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got "%s".', $index, $this->tokens[$index]->getName()));
}
$attributes = [
'visibility' => null,
'static' => false,
'abstract' => false,
'final' => false,
];
for ($i = $index; $i >= 0; --$i) {
$i = $this->tokens->getPrevMeaningfulToken($i);
$token = $this->tokens[$i];
if ($token->isGivenKind(T_STATIC)) {
$attributes['static'] = true;
continue;
}
if ($token->isGivenKind(T_FINAL)) {
$attributes['final'] = true;
continue;
}
if ($token->isGivenKind(T_ABSTRACT)) {
$attributes['abstract'] = true;
continue;
}
// visibility
if ($token->isGivenKind(T_PRIVATE)) {
$attributes['visibility'] = T_PRIVATE;
continue;
}
if ($token->isGivenKind(T_PROTECTED)) {
$attributes['visibility'] = T_PROTECTED;
continue;
}
if ($token->isGivenKind(T_PUBLIC)) {
$attributes['visibility'] = T_PUBLIC;
continue;
}
// found a meaningful token that is not part of
// the function signature; stop looking
break;
}
return $attributes;
}
/**
* Check if there is an anonymous class under given index.
*/
public function isAnonymousClass(int $index): bool
{
if (!$this->tokens[$index]->isClassy()) {
throw new \LogicException(sprintf('No classy token at given index %d.', $index));
}
if (!$this->tokens[$index]->isGivenKind(T_CLASS)) {
return false;
}
$index = $this->tokens->getPrevMeaningfulToken($index);
while ($this->tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
$index = $this->tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
$index = $this->tokens->getPrevMeaningfulToken($index);
}
return $this->tokens[$index]->isGivenKind(T_NEW);
}
/**
* Check if the function under given index is a lambda.
*/
public function isLambda(int $index): bool
{
if (!$this->tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) {
throw new \LogicException(sprintf('No T_FUNCTION or T_FN at given index %d, got "%s".', $index, $this->tokens[$index]->getName()));
}
$startParenthesisIndex = $this->tokens->getNextMeaningfulToken($index);
$startParenthesisToken = $this->tokens[$startParenthesisIndex];
// skip & for `function & () {}` syntax
if ($startParenthesisToken->isGivenKind(CT::T_RETURN_REF)) {
$startParenthesisIndex = $this->tokens->getNextMeaningfulToken($startParenthesisIndex);
$startParenthesisToken = $this->tokens[$startParenthesisIndex];
}
return $startParenthesisToken->equals('(');
}
/**
* Check if the T_STRING under given index is a constant invocation.
*/
public function isConstantInvocation(int $index): bool
{
if (!$this->tokens[$index]->isGivenKind(T_STRING)) {
throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $this->tokens[$index]->getName()));
}
$nextIndex = $this->tokens->getNextMeaningfulToken($index);
if (
$this->tokens[$nextIndex]->equalsAny(['(', '{'])
|| $this->tokens[$nextIndex]->isGivenKind([T_AS, T_DOUBLE_COLON, T_ELLIPSIS, T_NS_SEPARATOR, CT::T_RETURN_REF, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION, T_VARIABLE])
) {
return false;
}
$prevIndex = $this->tokens->getPrevMeaningfulToken($index);
if ($this->tokens[$prevIndex]->isGivenKind([T_AS, T_CLASS, T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_GOTO, CT::T_GROUP_IMPORT_BRACE_OPEN, T_INTERFACE, T_TRAIT, CT::T_TYPE_COLON, CT::T_TYPE_ALTERNATION, CT::T_TYPE_INTERSECTION]) || $this->tokens[$prevIndex]->isObjectOperator()) {
return false;
}
while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING])) {
$prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
}
if ($this->tokens[$prevIndex]->isGivenKind([CT::T_CONST_IMPORT, T_EXTENDS, CT::T_FUNCTION_IMPORT, T_IMPLEMENTS, T_INSTANCEOF, T_INSTEADOF, T_NAMESPACE, T_NEW, CT::T_NULLABLE_TYPE, CT::T_TYPE_COLON, T_USE, CT::T_USE_TRAIT])) {
return false;
}
// `FOO & $bar` could be:
// - function reference parameter: function baz(Foo & $bar) {}
// - bit operator: $x = FOO & $bar;
if ($this->tokens[$nextIndex]->equals('&') && $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(T_VARIABLE)) {
$checkIndex = $this->tokens->getPrevTokenOfKind($prevIndex, [';', '{', '}', [T_FUNCTION], [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]]);
if ($this->tokens[$checkIndex]->isGivenKind(T_FUNCTION)) {
return false;
}
}
// check for `extends`/`implements`/`use` list
if ($this->tokens[$prevIndex]->equals(',')) {
$checkIndex = $prevIndex;
while ($this->tokens[$checkIndex]->equalsAny([',', [T_AS], [CT::T_NAMESPACE_OPERATOR], [T_NS_SEPARATOR], [T_STRING]])) {
$checkIndex = $this->tokens->getPrevMeaningfulToken($checkIndex);
}
if ($this->tokens[$checkIndex]->isGivenKind([T_EXTENDS, CT::T_GROUP_IMPORT_BRACE_OPEN, T_IMPLEMENTS, T_USE, CT::T_USE_TRAIT])) {
return false;
}
}
// check for array in double quoted string: `"..$foo[bar].."`
if ($this->tokens[$prevIndex]->equals('[') && $this->tokens[$nextIndex]->equals(']')) {
$checkToken = $this->tokens[$this->tokens->getNextMeaningfulToken($nextIndex)];
if ($checkToken->equals('"') || $checkToken->isGivenKind([T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES, T_ENCAPSED_AND_WHITESPACE, T_VARIABLE])) {
return false;
}
}
// check for attribute: `#[Foo]`
if (AttributeAnalyzer::isAttribute($this->tokens, $index)) {
return false;
}
// check for goto label
if ($this->tokens[$nextIndex]->equals(':')) {
if (null === $this->gotoLabelAnalyzer) {
$this->gotoLabelAnalyzer = new GotoLabelAnalyzer();
}
if ($this->gotoLabelAnalyzer->belongsToGoToLabel($this->tokens, $nextIndex)) {
return false;
}
}
// check for non-capturing catches
while ($this->tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_NS_SEPARATOR, T_STRING, CT::T_TYPE_ALTERNATION])) {
$prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
}
if ($this->tokens[$prevIndex]->equals('(')) {
$prevPrevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
if ($this->tokens[$prevPrevIndex]->isGivenKind(T_CATCH)) {
return false;
}
}
return true;
}
/**
* Checks if there is a unary successor operator under given index.
*/
public function isUnarySuccessorOperator(int $index): bool
{
static $allowedPrevToken = [
']',
[T_STRING],
[T_VARIABLE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
[CT::T_DYNAMIC_PROP_BRACE_CLOSE],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
];
$tokens = $this->tokens;
$token = $tokens[$index];
if (!$token->isGivenKind([T_INC, T_DEC])) {
return false;
}
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
return $prevToken->equalsAny($allowedPrevToken);
}
/**
* Checks if there is a unary predecessor operator under given index.
*/
public function isUnaryPredecessorOperator(int $index): bool
{
static $potentialSuccessorOperator = [T_INC, T_DEC];
static $potentialBinaryOperator = ['+', '-', '&', [CT::T_RETURN_REF]];
static $otherOperators;
if (null === $otherOperators) {
$otherOperators = ['!', '~', '@', [T_ELLIPSIS]];
}
static $disallowedPrevTokens;
if (null === $disallowedPrevTokens) {
$disallowedPrevTokens = [
']',
'}',
')',
'"',
'`',
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
[CT::T_DYNAMIC_PROP_BRACE_CLOSE],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
[T_CLASS_C],
[T_CONSTANT_ENCAPSED_STRING],
[T_DEC],
[T_DIR],
[T_DNUMBER],
[T_FILE],
[T_FUNC_C],
[T_INC],
[T_LINE],
[T_LNUMBER],
[T_METHOD_C],
[T_NS_C],
[T_STRING],
[T_TRAIT_C],
[T_VARIABLE],
];
}
$tokens = $this->tokens;
$token = $tokens[$index];
if ($token->isGivenKind($potentialSuccessorOperator)) {
return !$this->isUnarySuccessorOperator($index);
}
if ($token->equalsAny($otherOperators)) {
return true;
}
if (!$token->equalsAny($potentialBinaryOperator)) {
return false;
}
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
if (!$prevToken->equalsAny($disallowedPrevTokens)) {
return true;
}
if (!$token->equals('&') || !$prevToken->isGivenKind(T_STRING)) {
return false;
}
static $searchTokens = [
';',
'{',
'}',
[T_FUNCTION],
[T_OPEN_TAG],
[T_OPEN_TAG_WITH_ECHO],
];
$prevToken = $tokens[$tokens->getPrevTokenOfKind($index, $searchTokens)];
return $prevToken->isGivenKind(T_FUNCTION);
}
/**
* Checks if there is a binary operator under given index.
*/
public function isBinaryOperator(int $index): bool
{
static $nonArrayOperators = [
'=' => true,
'*' => true,
'/' => true,
'%' => true,
'<' => true,
'>' => true,
'|' => true,
'^' => true,
'.' => true,
];
static $potentialUnaryNonArrayOperators = [
'+' => true,
'-' => true,
'&' => true,
];
static $arrayOperators;
if (null === $arrayOperators) {
$arrayOperators = [
T_AND_EQUAL => true, // &=
T_BOOLEAN_AND => true, // &&
T_BOOLEAN_OR => true, // ||
T_CONCAT_EQUAL => true, // .=
T_DIV_EQUAL => true, // /=
T_DOUBLE_ARROW => true, // =>
T_IS_EQUAL => true, // ==
T_IS_GREATER_OR_EQUAL => true, // >=
T_IS_IDENTICAL => true, // ===
T_IS_NOT_EQUAL => true, // !=, <>
T_IS_NOT_IDENTICAL => true, // !==
T_IS_SMALLER_OR_EQUAL => true, // <=
T_LOGICAL_AND => true, // and
T_LOGICAL_OR => true, // or
T_LOGICAL_XOR => true, // xor
T_MINUS_EQUAL => true, // -=
T_MOD_EQUAL => true, // %=
T_MUL_EQUAL => true, // *=
T_OR_EQUAL => true, // |=
T_PLUS_EQUAL => true, // +=
T_POW => true, // **
T_POW_EQUAL => true, // **=
T_SL => true, // <<
T_SL_EQUAL => true, // <<=
T_SR => true, // >>
T_SR_EQUAL => true, // >>=
T_XOR_EQUAL => true, // ^=
T_SPACESHIP => true, // <=>
T_COALESCE => true, // ??
T_COALESCE_EQUAL => true, // ??=
];
}
$tokens = $this->tokens;
$token = $tokens[$index];
if ($token->isGivenKind([T_INLINE_HTML, T_ENCAPSED_AND_WHITESPACE, CT::T_TYPE_INTERSECTION])) {
return false;
}
if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) {
return !$this->isUnaryPredecessorOperator($index);
}
if ($token->isArray()) {
return isset($arrayOperators[$token->getId()]);
}
if (isset($nonArrayOperators[$token->getContent()])) {
return true;
}
return false;
}
/**
* Check if `T_WHILE` token at given index is `do { ... } while ();` syntax
* and not `while () { ...}`.
*/
public function isWhilePartOfDoWhile(int $index): bool
{
$tokens = $this->tokens;
$token = $tokens[$index];
if (!$token->isGivenKind(T_WHILE)) {
throw new \LogicException(sprintf('No T_WHILE at given index %d, got "%s".', $index, $token->getName()));
}
$endIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$endIndex]->equals('}')) {
return false;
}
$startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex);
$beforeStartIndex = $tokens->getPrevMeaningfulToken($startIndex);
return $tokens[$beforeStartIndex]->isGivenKind(T_DO);
}
public function isSuperGlobal(int $index): bool
{
static $superNames = [
'$_COOKIE' => true,
'$_ENV' => true,
'$_FILES' => true,
'$_GET' => true,
'$_POST' => true,
'$_REQUEST' => true,
'$_SERVER' => true,
'$_SESSION' => true,
'$GLOBALS' => true,
];
$token = $this->tokens[$index];
if (!$token->isGivenKind(T_VARIABLE)) {
return false;
}
return isset($superNames[strtoupper($token->getContent())]);
}
/**
* Find classy elements.
*
* Searches in tokens from the classy (start) index till the end (index) of the classy.
* Returns an array; first value is the index until the method has analysed (int), second the found classy elements (array).
*
* @param int $classIndex classy index
*
* @return array{int, array}
*/
private function findClassyElements(int $classIndex, int $index): array
{
$elements = [];
$curlyBracesLevel = 0;
$bracesLevel = 0;
++$index; // skip the classy index itself
for ($count = \count($this->tokens); $index < $count; ++$index) {
$token = $this->tokens[$index];
if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)) {
continue;
}
if ($token->isGivenKind(T_CLASS)) { // anonymous class in class
// check for nested anonymous classes inside the new call of an anonymous class,
// for example `new class(function (){new class(function (){new class(function (){}){};}){};}){};` etc.
// if class(XYZ) {} skip till `(` as XYZ might contain functions etc.
$nestedClassIndex = $index;
$index = $this->tokens->getNextMeaningfulToken($index);
if ($this->tokens[$index]->equals('(')) {
++$index; // move after `(`
for ($nestedBracesLevel = 1; $index < $count; ++$index) {
$token = $this->tokens[$index];
if ($token->equals('(')) {
++$nestedBracesLevel;
continue;
}
if ($token->equals(')')) {
--$nestedBracesLevel;
if (0 === $nestedBracesLevel) {
[$index, $newElements] = $this->findClassyElements($nestedClassIndex, $index);
$elements += $newElements;
break;
}
continue;
}
if ($token->isGivenKind(T_CLASS)) { // anonymous class in class
[$index, $newElements] = $this->findClassyElements($index, $index);
$elements += $newElements;
}
}
} else {
[$index, $newElements] = $this->findClassyElements($nestedClassIndex, $nestedClassIndex);
$elements += $newElements;
}
continue;
}
if ($token->equals('(')) {
++$bracesLevel;
continue;
}
if ($token->equals(')')) {
--$bracesLevel;
continue;
}
if ($token->equals('{')) {
++$curlyBracesLevel;
continue;
}
if ($token->equals('}')) {
--$curlyBracesLevel;
if (0 === $curlyBracesLevel) {
break;
}
continue;
}
if (1 !== $curlyBracesLevel || !$token->isArray()) {
continue;
}
if (0 === $bracesLevel && $token->isGivenKind(T_VARIABLE)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'property',
];
continue;
}
if ($token->isGivenKind(T_FUNCTION)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'method',
];
} elseif ($token->isGivenKind(T_CONST)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'const',
];
} elseif ($token->isGivenKind(CT::T_USE_TRAIT)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'trait_import',
];
} elseif ($token->isGivenKind(T_CASE)) {
$elements[$index] = [
'classIndex' => $classIndex,
'token' => $token,
'type' => 'case',
];
}
}
return [$index, $elements];
}
}
PK ]NpVW
) src/Tokenizer/AbstractTypeTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
/**
* @author Dariusz Rumiński
*
* @internal
*/
abstract class AbstractTypeTransformer extends AbstractTransformer
{
private const TYPE_END_TOKENS = [')', [T_CALLABLE], [T_NS_SEPARATOR], [T_STATIC], [T_STRING], [CT::T_ARRAY_TYPEHINT]];
private const TYPE_TOKENS = [
'|', '&', '(',
...self::TYPE_END_TOKENS,
[CT::T_TYPE_ALTERNATION], [CT::T_TYPE_INTERSECTION], // some siblings may already be transformed
[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], // technically these can be inside of type tokens array
];
abstract protected function replaceToken(Tokens $tokens, int $index): void;
/**
* @param array{0: int, 1: string}|string $originalToken
*/
protected function doProcess(Tokens $tokens, int $index, $originalToken): void
{
if (!$tokens[$index]->equals($originalToken)) {
return;
}
if (!$this->isPartOfType($tokens, $index)) {
return;
}
$this->replaceToken($tokens, $index);
}
private function isPartOfType(Tokens $tokens, int $index): bool
{
// return types and non-capturing catches
$typeColonIndex = $tokens->getTokenNotOfKindSibling($index, -1, self::TYPE_TOKENS);
if ($tokens[$typeColonIndex]->isGivenKind([T_CATCH, CT::T_TYPE_COLON])) {
return true;
}
// for parameter there will be splat operator or variable after the type ("&" is ambiguous and can be reference or bitwise and)
$afterTypeIndex = $tokens->getTokenNotOfKindSibling($index, 1, self::TYPE_TOKENS);
if ($tokens[$afterTypeIndex]->isGivenKind(T_ELLIPSIS)) {
return true;
}
if (!$tokens[$afterTypeIndex]->isGivenKind(T_VARIABLE)) {
return false;
}
$beforeVariableIndex = $tokens->getPrevMeaningfulToken($afterTypeIndex);
if ($tokens[$beforeVariableIndex]->equals('&')) {
$prevIndex = $tokens->getPrevTokenOfKind($index, ['{', '}', ';', [T_FN], [T_FUNCTION]]);
return null !== $prevIndex && $tokens[$prevIndex]->isGivenKind([T_FN, T_FUNCTION]);
}
return $tokens[$beforeVariableIndex]->equalsAny(self::TYPE_END_TOKENS);
}
}
PK ]NpVk src/Tokenizer/CodeHasher.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
/**
* @author Dariusz Rumiński
*
* @internal
*/
final class CodeHasher
{
private function __construct()
{
// cannot create instance of util. class
}
/**
* Calculate hash for code.
*/
public static function calculateCodeHash(string $code): string
{
return md5($code);
}
}
PK ]NpV].'&
&
src/Tokenizer/CT.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
/**
* @author Dariusz Rumiński
*/
final class CT
{
public const T_ARRAY_INDEX_CURLY_BRACE_CLOSE = 10001;
public const T_ARRAY_INDEX_CURLY_BRACE_OPEN = 10002;
public const T_ARRAY_SQUARE_BRACE_CLOSE = 10003;
public const T_ARRAY_SQUARE_BRACE_OPEN = 10004;
public const T_ARRAY_TYPEHINT = 10005;
public const T_BRACE_CLASS_INSTANTIATION_CLOSE = 10006;
public const T_BRACE_CLASS_INSTANTIATION_OPEN = 10007;
public const T_CLASS_CONSTANT = 10008;
public const T_CONST_IMPORT = 10009;
public const T_CURLY_CLOSE = 10010;
public const T_DESTRUCTURING_SQUARE_BRACE_CLOSE = 10011;
public const T_DESTRUCTURING_SQUARE_BRACE_OPEN = 10012;
public const T_DOLLAR_CLOSE_CURLY_BRACES = 10013;
public const T_DYNAMIC_PROP_BRACE_CLOSE = 10014;
public const T_DYNAMIC_PROP_BRACE_OPEN = 10015;
public const T_DYNAMIC_VAR_BRACE_CLOSE = 10016;
public const T_DYNAMIC_VAR_BRACE_OPEN = 10017;
public const T_FUNCTION_IMPORT = 10018;
public const T_GROUP_IMPORT_BRACE_CLOSE = 10019;
public const T_GROUP_IMPORT_BRACE_OPEN = 10020;
public const T_NAMESPACE_OPERATOR = 10021;
public const T_NULLABLE_TYPE = 10022;
public const T_RETURN_REF = 10023;
public const T_TYPE_ALTERNATION = 10024;
public const T_TYPE_COLON = 10025;
public const T_USE_LAMBDA = 10026;
public const T_USE_TRAIT = 10027;
public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC = 10028;
public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED = 10029;
public const T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE = 10030;
public const T_ATTRIBUTE_CLOSE = 10031;
public const T_NAMED_ARGUMENT_NAME = 10032;
public const T_NAMED_ARGUMENT_COLON = 10033;
public const T_FIRST_CLASS_CALLABLE = 10034;
public const T_TYPE_INTERSECTION = 10035;
public const T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN = 10036;
public const T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE = 10037;
private function __construct()
{
}
/**
* Get name for custom token.
*
* @param int $value custom token value
*/
public static function getName(int $value): string
{
if (!self::has($value)) {
throw new \InvalidArgumentException(sprintf('No custom token was found for "%s".', $value));
}
$tokens = self::getMapById();
return 'CT::'.$tokens[$value];
}
/**
* Check if given custom token exists.
*
* @param int $value custom token value
*/
public static function has(int $value): bool
{
$tokens = self::getMapById();
return isset($tokens[$value]);
}
/**
* @return array
*/
private static function getMapById(): array
{
static $constants;
if (null === $constants) {
$reflection = new \ReflectionClass(__CLASS__);
$constants = array_flip($reflection->getConstants());
}
return $constants;
}
}
PK ]NpV֎6R R 2 src/Tokenizer/Transformer/AttributeTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transforms attribute related Tokens.
*
* @internal
*/
final class AttributeTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// must run before all other transformers that might touch attributes
return 200;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 8_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$tokens[$index]->isGivenKind(T_ATTRIBUTE)) {
return;
}
$level = 1;
do {
++$index;
if ($tokens[$index]->equals('[')) {
++$level;
} elseif ($tokens[$index]->equals(']')) {
--$level;
}
} while (0 < $level);
$tokens[$index] = new Token([CT::T_ATTRIBUTE_CLOSE, ']']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_ATTRIBUTE_CLOSE,
];
}
}
PK ]NpVܐm , src/Tokenizer/Transformer/UseTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform T_USE into:
* - CT::T_USE_TRAIT for imports,
* - CT::T_USE_LAMBDA for lambda variable uses.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class UseTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// Should run after CurlyBraceTransformer and before TypeColonTransformer
return -5;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_03_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($token->isGivenKind(T_USE) && $this->isUseForLambda($tokens, $index)) {
$tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]);
return;
}
// Only search inside class/trait body for `T_USE` for traits.
// Cannot import traits inside interfaces or anywhere else
$classTypes = [T_TRAIT];
if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
$classTypes[] = T_ENUM;
}
if ($token->isGivenKind(T_CLASS)) {
if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_DOUBLE_COLON)) {
return;
}
} elseif (!$token->isGivenKind($classTypes)) {
return;
}
$index = $tokens->getNextTokenOfKind($index, ['{']);
$innerLimit = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
while ($index < $innerLimit) {
$token = $tokens[++$index];
if (!$token->isGivenKind(T_USE)) {
continue;
}
if ($this->isUseForLambda($tokens, $index)) {
$tokens[$index] = new Token([CT::T_USE_LAMBDA, $token->getContent()]);
} else {
$tokens[$index] = new Token([CT::T_USE_TRAIT, $token->getContent()]);
}
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_USE_TRAIT, CT::T_USE_LAMBDA];
}
/**
* Check if token under given index is `use` statement for lambda function.
*/
private function isUseForLambda(Tokens $tokens, int $index): bool
{
$nextToken = $tokens[$tokens->getNextMeaningfulToken($index)];
// test `function () use ($foo) {}` case
return $nextToken->equals('(');
}
}
PK ]NpV$K 4 src/Tokenizer/Transformer/SquareBraceTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform discriminate overloaded square braces tokens.
*
* Performed transformations:
* - in `[1, 2, 3]` into CT::T_ARRAY_SQUARE_BRACE_OPEN and CT::T_ARRAY_SQUARE_BRACE_CLOSE,
* - in `[$a, &$b, [$c]] = array(1, 2, array(3))` into CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN and CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class SquareBraceTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// must run after CurlyBraceTransformer and AttributeTransformer
return -1;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
// Short array syntax was introduced in PHP 5.4, but the fixer is smart
// enough to handle it even before 5.4.
// Same for array destructing syntax sugar `[` introduced in PHP 7.1.
return 5_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($this->isArrayDestructing($tokens, $index)) {
$this->transformIntoDestructuringSquareBrace($tokens, $index);
return;
}
if ($this->isShortArray($tokens, $index)) {
$this->transformIntoArraySquareBrace($tokens, $index);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_ARRAY_SQUARE_BRACE_OPEN,
CT::T_ARRAY_SQUARE_BRACE_CLOSE,
CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN,
CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE,
];
}
private function transformIntoArraySquareBrace(Tokens $tokens, int $index): void
{
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index);
$tokens[$index] = new Token([CT::T_ARRAY_SQUARE_BRACE_OPEN, '[']);
$tokens[$endIndex] = new Token([CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']']);
}
private function transformIntoDestructuringSquareBrace(Tokens $tokens, int $index): void
{
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index);
$tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']);
$tokens[$endIndex] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']);
$previousMeaningfulIndex = $index;
$index = $tokens->getNextMeaningfulToken($index);
while ($index < $endIndex) {
if ($tokens[$index]->equals('[') && $tokens[$previousMeaningfulIndex]->equalsAny([[CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN], ','])) {
$tokens[$tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index)] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']']);
$tokens[$index] = new Token([CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '[']);
}
$previousMeaningfulIndex = $index;
$index = $tokens->getNextMeaningfulToken($index);
}
}
/**
* Check if token under given index is short array opening.
*/
private function isShortArray(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->equals('[')) {
return false;
}
static $disallowedPrevTokens = [
')',
']',
'}',
'"',
[T_CONSTANT_ENCAPSED_STRING],
[T_STRING],
[T_STRING_VARNAME],
[T_VARIABLE],
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[CT::T_DYNAMIC_PROP_BRACE_CLOSE],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
];
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
if ($prevToken->equalsAny($disallowedPrevTokens)) {
return false;
}
$nextToken = $tokens[$tokens->getNextMeaningfulToken($index)];
if ($nextToken->equals(']')) {
return true;
}
return !$this->isArrayDestructing($tokens, $index);
}
private function isArrayDestructing(Tokens $tokens, int $index): bool
{
if (!$tokens[$index]->equals('[')) {
return false;
}
static $disallowedPrevTokens = [
')',
']',
'"',
[T_CONSTANT_ENCAPSED_STRING],
[T_STRING],
[T_STRING_VARNAME],
[T_VARIABLE],
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[CT::T_DYNAMIC_PROP_BRACE_CLOSE],
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
];
$prevIndex = $tokens->getPrevMeaningfulToken($index);
$prevToken = $tokens[$prevIndex];
if ($prevToken->equalsAny($disallowedPrevTokens)) {
return false;
}
if ($prevToken->isGivenKind(T_AS)) {
return true;
}
if ($prevToken->isGivenKind(T_DOUBLE_ARROW)) {
$variableIndex = $tokens->getPrevMeaningfulToken($prevIndex);
if (!$tokens[$variableIndex]->isGivenKind(T_VARIABLE)) {
return false;
}
$prevVariableIndex = $tokens->getPrevMeaningfulToken($variableIndex);
if ($tokens[$prevVariableIndex]->isGivenKind(T_AS)) {
return true;
}
}
$type = Tokens::detectBlockType($tokens[$index]);
$end = $tokens->findBlockEnd($type['type'], $index);
$nextToken = $tokens[$tokens->getNextMeaningfulToken($end)];
return $nextToken->equals('=');
}
}
PK ]NpVW 5 src/Tokenizer/Transformer/NullableTypeTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `?` operator into CT::T_NULLABLE_TYPE in `function foo(?Bar $b) {}`.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class NullableTypeTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after TypeColonTransformer
return -20;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 7_01_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals('?')) {
return;
}
static $types;
if (null === $types) {
$types = [
'(',
',',
[CT::T_TYPE_COLON],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE],
[CT::T_ATTRIBUTE_CLOSE],
[T_PRIVATE],
[T_PROTECTED],
[T_PUBLIC],
[T_VAR],
[T_STATIC],
];
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
$types[] = [T_READONLY];
}
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$prevIndex]->equalsAny($types)) {
$tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_NULLABLE_TYPE];
}
}
PK ]NpVT M src/Tokenizer/Transformer/DisjunctiveNormalFormTypeParenthesisTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform DNF parentheses into CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN and CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE.
*
* @see https://wiki.php.net/rfc/dnf_types
*
* @internal
*/
final class DisjunctiveNormalFormTypeParenthesisTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after TypeAlternationTransformer
return -16;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 8_02_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($token->equals('(') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(CT::T_TYPE_ALTERNATION)) {
$openIndex = $index;
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
} elseif ($token->equals(')') && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(CT::T_TYPE_ALTERNATION)) {
$openIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$closeIndex = $index;
} else {
return;
}
$tokens[$openIndex] = new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN, '(']);
$tokens[$closeIndex] = new Token([CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE, ')']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_OPEN,
CT::T_DISJUNCTIVE_NORMAL_FORM_TYPE_PARENTHESIS_CLOSE,
];
}
}
PK ]NpVֿ ; src/Tokenizer/Transformer/FirstClassCallableTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @internal
*/
final class FirstClassCallableTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 8_01_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (
$token->isGivenKind(T_ELLIPSIS)
&& $tokens[$tokens->getPrevMeaningfulToken($index)]->equals('(')
&& $tokens[$tokens->getNextMeaningfulToken($index)]->equals(')')
) {
$tokens[$index] = new Token([CT::T_FIRST_CLASS_CALLABLE, '...']);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_FIRST_CLASS_CALLABLE,
];
}
}
PK ]NpVu / src/Tokenizer/Transformer/ImportTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform const/function import tokens.
*
* Performed transformations:
* - T_CONST into CT::T_CONST_IMPORT
* - T_FUNCTION into CT::T_FUNCTION_IMPORT
*
* @author Gregor Harlan
*
* @internal
*/
final class ImportTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// Should run after CurlyBraceTransformer and ReturnRefTransformer
return -1;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_06_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isGivenKind([T_CONST, T_FUNCTION])) {
return;
}
$prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
if (!$prevToken->isGivenKind(T_USE)) {
$nextToken = $tokens[$tokens->getNextTokenOfKind($index, ['=', '(', [CT::T_RETURN_REF], [CT::T_GROUP_IMPORT_BRACE_CLOSE]])];
if (!$nextToken->isGivenKind(CT::T_GROUP_IMPORT_BRACE_CLOSE)) {
return;
}
}
$tokens[$index] = new Token([
$token->isGivenKind(T_FUNCTION) ? CT::T_FUNCTION_IMPORT : CT::T_CONST_IMPORT,
$token->getContent(),
]);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_CONST_IMPORT, CT::T_FUNCTION_IMPORT];
}
}
PK ]NpVݨF 6 src/Tokenizer/Transformer/NamedArgumentTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform named argument tokens.
*
* @internal
*/
final class NamedArgumentTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after TypeColonTransformer
return -15;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 8_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$tokens[$index]->equals(':')) {
return;
}
$stringIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$stringIndex]->isGivenKind(T_STRING)) {
return;
}
$preStringIndex = $tokens->getPrevMeaningfulToken($stringIndex);
// if equals any [';', '{', '}', [T_OPEN_TAG]] than it is a goto label
// if equals ')' than likely it is a type colon, but sure not a name argument
// if equals '?' than it is part of ternary statement
if (!$tokens[$preStringIndex]->equalsAny([',', '('])) {
return;
}
$tokens[$stringIndex] = new Token([CT::T_NAMED_ARGUMENT_NAME, $tokens[$stringIndex]->getContent()]);
$tokens[$index] = new Token([CT::T_NAMED_ARGUMENT_COLON, ':']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_NAMED_ARGUMENT_COLON,
CT::T_NAMED_ARGUMENT_NAME,
];
}
}
PK ]NpV7xP 6 src/Tokenizer/Transformer/ArrayTypehintTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `array` typehint from T_ARRAY into CT::T_ARRAY_TYPEHINT.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class ArrayTypehintTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isGivenKind(T_ARRAY)) {
return;
}
$nextIndex = $tokens->getNextMeaningfulToken($index);
$nextToken = $tokens[$nextIndex];
if (!$nextToken->equals('(')) {
$tokens[$index] = new Token([CT::T_ARRAY_TYPEHINT, $token->getContent()]);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_ARRAY_TYPEHINT];
}
}
PK ]NpV2vN : src/Tokenizer/Transformer/WhitespacyCommentTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Move trailing whitespaces from comments and docs into following T_WHITESPACE token.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class WhitespacyCommentTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isComment()) {
return;
}
$content = $token->getContent();
$trimmedContent = rtrim($content);
// nothing trimmed, nothing to do
if ($content === $trimmedContent) {
return;
}
$whitespaces = substr($content, \strlen($trimmedContent));
$tokens[$index] = new Token([$token->getId(), $trimmedContent]);
if (isset($tokens[$index + 1]) && $tokens[$index + 1]->isWhitespace()) {
$tokens[$index + 1] = new Token([T_WHITESPACE, $whitespaces.$tokens[$index + 1]->getContent()]);
} else {
$tokens->insertAt($index + 1, new Token([T_WHITESPACE, $whitespaces]));
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [];
}
}
PK ]NpV#yz z : src/Tokenizer/Transformer/NamespaceOperatorTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `namespace` operator from T_NAMESPACE into CT::T_NAMESPACE_OPERATOR.
*
* @author Gregor Harlan
*
* @internal
*/
final class NamespaceOperatorTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_03_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isGivenKind(T_NAMESPACE)) {
return;
}
$nextIndex = $tokens->getNextMeaningfulToken($index);
if ($tokens[$nextIndex]->isGivenKind(T_NS_SEPARATOR)) {
$tokens[$index] = new Token([CT::T_NAMESPACE_OPERATOR, $token->getContent()]);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_NAMESPACE_OPERATOR];
}
}
PK ]NpV~6 6 8 src/Tokenizer/Transformer/TypeAlternationTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTypeTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `|` operator into CT::T_TYPE_ALTERNATION in `function foo(Type1 | Type2 $x) {`
* or `} catch (ExceptionType1 | ExceptionType2 $e) {`.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class TypeAlternationTransformer extends AbstractTypeTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after ArrayTypehintTransformer, TypeColonTransformer and AttributeTransformer
return -15;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 7_01_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
$this->doProcess($tokens, $index, '|');
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_TYPE_ALTERNATION];
}
protected function replaceToken(Tokens $tokens, int $index): void
{
$tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']);
}
}
PK ]NpV@C* * 9 src/Tokenizer/Transformer/TypeIntersectionTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTypeTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `&` operator into CT::T_TYPE_INTERSECTION in `function foo(Type1 & Type2 $x) {`
* or `} catch (ExceptionType1 & ExceptionType2 $e) {`.
*
* @internal
*/
final class TypeIntersectionTransformer extends AbstractTypeTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after ArrayTypehintTransformer, TypeColonTransformer and AttributeTransformer
return -15;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 8_01_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
$this->doProcess($tokens, $index, [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, '&']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_TYPE_INTERSECTION];
}
protected function replaceToken(Tokens $tokens, int $index): void
{
$tokens[$index] = new Token([CT::T_TYPE_INTERSECTION, '&']);
}
}
PK ]NpVB 3 src/Tokenizer/Transformer/CurlyBraceTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform discriminate overloaded curly braces tokens.
*
* Performed transformations:
* - closing `}` for T_CURLY_OPEN into CT::T_CURLY_CLOSE,
* - closing `}` for T_DOLLAR_OPEN_CURLY_BRACES into CT::T_DOLLAR_CLOSE_CURLY_BRACES,
* - in `$foo->{$bar}` into CT::T_DYNAMIC_PROP_BRACE_OPEN and CT::T_DYNAMIC_PROP_BRACE_CLOSE,
* - in `${$foo}` into CT::T_DYNAMIC_VAR_BRACE_OPEN and CT::T_DYNAMIC_VAR_BRACE_CLOSE,
* - in `$array{$index}` into CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN and CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
* - in `use some\a\{ClassA, ClassB, ClassC as C}` into CT::T_GROUP_IMPORT_BRACE_OPEN, CT::T_GROUP_IMPORT_BRACE_CLOSE.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class CurlyBraceTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
$this->transformIntoCurlyCloseBrace($tokens, $token, $index);
$this->transformIntoDollarCloseBrace($tokens, $token, $index);
$this->transformIntoDynamicPropBraces($tokens, $token, $index);
$this->transformIntoDynamicVarBraces($tokens, $token, $index);
$this->transformIntoCurlyIndexBraces($tokens, $token, $index);
$this->transformIntoGroupUseBraces($tokens, $token, $index);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_CURLY_CLOSE,
CT::T_DOLLAR_CLOSE_CURLY_BRACES,
CT::T_DYNAMIC_PROP_BRACE_OPEN,
CT::T_DYNAMIC_PROP_BRACE_CLOSE,
CT::T_DYNAMIC_VAR_BRACE_OPEN,
CT::T_DYNAMIC_VAR_BRACE_CLOSE,
CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN,
CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE,
CT::T_GROUP_IMPORT_BRACE_OPEN,
CT::T_GROUP_IMPORT_BRACE_CLOSE,
];
}
/**
* Transform closing `}` for T_CURLY_OPEN into CT::T_CURLY_CLOSE.
*
* This should be done at very beginning of curly braces transformations.
*/
private function transformIntoCurlyCloseBrace(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isGivenKind(T_CURLY_OPEN)) {
return;
}
$level = 1;
do {
++$index;
if ($tokens[$index]->equals('{') || $tokens[$index]->isGivenKind(T_CURLY_OPEN)) { // we count all kind of {
++$level;
} elseif ($tokens[$index]->equals('}')) { // we count all kind of }
--$level;
}
} while (0 < $level);
$tokens[$index] = new Token([CT::T_CURLY_CLOSE, '}']);
}
private function transformIntoDollarCloseBrace(Tokens $tokens, Token $token, int $index): void
{
if ($token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) {
$nextIndex = $tokens->getNextTokenOfKind($index, ['}']);
$tokens[$nextIndex] = new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']);
}
}
private function transformIntoDynamicPropBraces(Tokens $tokens, Token $token, int $index): void
{
if (!$token->isObjectOperator()) {
return;
}
if (!$tokens[$index + 1]->equals('{')) {
return;
}
$openIndex = $index + 1;
$closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $openIndex);
$tokens[$openIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_OPEN, '{']);
$tokens[$closeIndex] = new Token([CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}']);
}
private function transformIntoDynamicVarBraces(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals('$')) {
return;
}
$openIndex = $tokens->getNextMeaningfulToken($index);
if (null === $openIndex) {
return;
}
$openToken = $tokens[$openIndex];
if (!$openToken->equals('{')) {
return;
}
$closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $openIndex);
$tokens[$openIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_OPEN, '{']);
$tokens[$closeIndex] = new Token([CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}']);
}
private function transformIntoCurlyIndexBraces(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals('{')) {
return;
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevIndex]->equalsAny([
[T_STRING],
[T_VARIABLE],
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
']',
')',
])) {
return;
}
if (
$tokens[$prevIndex]->isGivenKind(T_STRING)
&& !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isObjectOperator()
) {
return;
}
if (
$tokens[$prevIndex]->equals(')')
&& !$tokens[$tokens->getPrevMeaningfulToken(
$tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $prevIndex)
)]->isGivenKind(T_ARRAY)
) {
return;
}
$closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index);
$tokens[$index] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']);
$tokens[$closeIndex] = new Token([CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}']);
}
private function transformIntoGroupUseBraces(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals('{')) {
return;
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
return;
}
$closeIndex = $this->naivelyFindCurlyBlockEnd($tokens, $index);
$tokens[$index] = new Token([CT::T_GROUP_IMPORT_BRACE_OPEN, '{']);
$tokens[$closeIndex] = new Token([CT::T_GROUP_IMPORT_BRACE_CLOSE, '}']);
}
/**
* We do not want to rely on `$tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index)` here,
* as it relies on block types that are assuming that `}` tokens are already transformed to Custom Tokens that are allowing to distinguish different block types.
* As we are just about to transform `{` and `}` into Custom Tokens by this transformer, thus we need to compare those tokens manually by content without using `Tokens::findBlockEnd`.
*/
private function naivelyFindCurlyBlockEnd(Tokens $tokens, int $startIndex): int
{
if (!$tokens->offsetExists($startIndex)) {
throw new \OutOfBoundsException(sprintf('Unavailable index: "%s".', $startIndex));
}
if ('{' !== $tokens[$startIndex]->getContent()) {
throw new \InvalidArgumentException(sprintf('Wrong start index: "%s".', $startIndex));
}
$blockLevel = 1;
$endIndex = $tokens->count() - 1;
for ($index = $startIndex + 1; $index !== $endIndex; ++$index) {
$token = $tokens[$index];
if ('{' === $token->getContent()) {
++$blockLevel;
continue;
}
if ('}' === $token->getContent()) {
--$blockLevel;
if (0 === $blockLevel) {
if (!$token->equals('}')) {
throw new \UnexpectedValueException(sprintf('Detected block end for index: "%s" was already transformed into other token type: "%s".', $startIndex, $token->getName()));
}
return $index;
}
}
}
throw new \UnexpectedValueException(sprintf('Missing block end for index: "%s".', $startIndex));
}
}
PK ]NpVW0H 2 src/Tokenizer/Transformer/ReturnRefTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `&` operator into CT::T_RETURN_REF in `function & foo() {}`.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class ReturnRefTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($token->equals('&') && $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind([T_FUNCTION, T_FN])) {
$tokens[$index] = new Token([CT::T_RETURN_REF, '&']);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_RETURN_REF];
}
}
PK ]NpV!G
@ src/Tokenizer/Transformer/BraceClassInstantiationTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform braced class instantiation braces in `(new Foo())` into CT::T_BRACE_CLASS_INSTANTIATION_OPEN
* and CT::T_BRACE_CLASS_INSTANTIATION_CLOSE.
*
* @author Sebastiaans Stok
*
* @internal
*/
final class BraceClassInstantiationTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// must run after CurlyBraceTransformer and SquareBraceTransformer
return -2;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$tokens[$index]->equals('(') || !$tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_NEW)) {
return;
}
if ($tokens[$tokens->getPrevMeaningfulToken($index)]->equalsAny([
']',
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[T_ARRAY],
[T_CLASS],
[T_ELSEIF],
[T_FOR],
[T_FOREACH],
[T_IF],
[T_STATIC],
[T_STRING],
[T_SWITCH],
[T_VARIABLE],
[T_WHILE],
])) {
return;
}
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
$tokens[$index] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '(']);
$tokens[$closeIndex] = new Token([CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')']);
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, CT::T_BRACE_CLASS_INSTANTIATION_CLOSE];
}
}
PK ]NpVS 6 src/Tokenizer/Transformer/ClassConstantTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `class` class' constant from T_CLASS into CT::T_CLASS_CONSTANT.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class ClassConstantTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 5_05_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equalsAny([
[T_CLASS, 'class'],
[T_STRING, 'class'],
], false)) {
return;
}
$prevIndex = $tokens->getPrevMeaningfulToken($index);
$prevToken = $tokens[$prevIndex];
if ($prevToken->isGivenKind(T_DOUBLE_COLON)) {
$tokens[$index] = new Token([CT::T_CLASS_CONSTANT, $token->getContent()]);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_CLASS_CONSTANT];
}
}
PK ]NpV 2 src/Tokenizer/Transformer/TypeColonTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform `:` operator into CT::T_TYPE_COLON in `function foo() : int {}`.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class TypeColonTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
// needs to run after ReturnRefTransformer and UseTransformer
// and before TypeAlternationTransformer
return -10;
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 7_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$token->equals(':')) {
return;
}
$endIndex = $tokens->getPrevMeaningfulToken($index);
if (
\defined('T_ENUM') // @TODO: drop condition when PHP 8.1+ is required
&& $tokens[$tokens->getPrevMeaningfulToken($endIndex)]->isGivenKind(T_ENUM)
) {
$tokens[$index] = new Token([CT::T_TYPE_COLON, ':']);
return;
}
if (!$tokens[$endIndex]->equals(')')) {
return;
}
$startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex);
$prevIndex = $tokens->getPrevMeaningfulToken($startIndex);
$prevToken = $tokens[$prevIndex];
// if this could be a function name we need to take one more step
if ($prevToken->isGivenKind(T_STRING)) {
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
$prevToken = $tokens[$prevIndex];
}
if ($prevToken->isGivenKind([T_FUNCTION, CT::T_RETURN_REF, CT::T_USE_LAMBDA, T_FN])) {
$tokens[$index] = new Token([CT::T_TYPE_COLON, ':']);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [CT::T_TYPE_COLON];
}
}
PK ]NpV Z
6 src/Tokenizer/Transformer/NameQualifiedTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transform NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED and T_NAME_RELATIVE into T_NAMESPACE T_NS_SEPARATOR T_STRING.
*
* @internal
*/
final class NameQualifiedTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
return 1; // must run before NamespaceOperatorTransformer
}
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 8_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if ($token->isGivenKind([T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED])) {
$this->transformQualified($tokens, $token, $index);
} elseif ($token->isGivenKind(T_NAME_RELATIVE)) {
$this->transformRelative($tokens, $token, $index);
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [];
}
private function transformQualified(Tokens $tokens, Token $token, int $index): void
{
$parts = explode('\\', $token->getContent());
$newTokens = [];
if ('' === $parts[0]) {
$newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
array_shift($parts);
}
foreach ($parts as $part) {
$newTokens[] = new Token([T_STRING, $part]);
$newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
}
array_pop($newTokens);
$tokens->overrideRange($index, $index, $newTokens);
}
private function transformRelative(Tokens $tokens, Token $token, int $index): void
{
$parts = explode('\\', $token->getContent());
$newTokens = [
new Token([T_NAMESPACE, array_shift($parts)]),
new Token([T_NS_SEPARATOR, '\\']),
];
foreach ($parts as $part) {
$newTokens[] = new Token([T_STRING, $part]);
$newTokens[] = new Token([T_NS_SEPARATOR, '\\']);
}
array_pop($newTokens);
$tokens->overrideRange($index, $index, $newTokens);
}
}
PK ]NpVm = src/Tokenizer/Transformer/ConstructorPromotionTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer\Transformer;
use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Transforms for Constructor Property Promotion.
*
* Transform T_PUBLIC, T_PROTECTED and T_PRIVATE of Constructor Property Promotion into custom tokens.
*
* @internal
*/
final class ConstructorPromotionTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getRequiredPhpVersionId(): int
{
return 8_00_00;
}
/**
* {@inheritdoc}
*/
public function process(Tokens $tokens, Token $token, int $index): void
{
if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
return;
}
$index = $tokens->getNextMeaningfulToken($index);
if (!$tokens[$index]->isGivenKind(T_STRING) || '__construct' !== strtolower($tokens[$index]->getContent())) {
return;
}
/** @var int $openIndex */
$openIndex = $tokens->getNextMeaningfulToken($index); // we are @ '(' now
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
for ($index = $openIndex; $index < $closeIndex; ++$index) {
if ($tokens[$index]->isGivenKind(T_PUBLIC)) {
$tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $tokens[$index]->getContent()]);
} elseif ($tokens[$index]->isGivenKind(T_PROTECTED)) {
$tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $tokens[$index]->getContent()]);
} elseif ($tokens[$index]->isGivenKind(T_PRIVATE)) {
$tokens[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $tokens[$index]->getContent()]);
}
}
}
/**
* {@inheritdoc}
*/
public function getCustomTokens(): array
{
return [
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
];
}
}
PK ]NpV( 0 src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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
*/
public function getDeclarationsFromTokens(Tokens $tokens): array
{
$tokenAnalyzer = new TokensAnalyzer($tokens);
$useIndices = $tokenAnalyzer->getImportUseIndexes();
return $this->getDeclarations($tokens, $useIndices);
}
/**
* @return list
*/
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 $useIndices
*
* @return list
*/
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
);
}
}
PK ]NpV@Q
/ src/Tokenizer/Analyzer/DataProviderAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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\Tokens;
/**
* @author Kuba Werłos
*
* @internal
*/
final class DataProviderAnalyzer
{
/**
* @return array indices of data provider definitions
*/
public function getDataProviders(Tokens $tokens, int $startIndex, int $endIndex): array
{
$methods = $this->getMethods($tokens, $startIndex, $endIndex);
$dataProviders = [];
foreach ($methods as $methodIndex) {
$docCommentIndex = $tokens->getTokenNotOfKindSibling(
$methodIndex,
-1,
[[T_ABSTRACT], [T_COMMENT], [T_FINAL], [T_FUNCTION], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_STATIC], [T_WHITESPACE]]
);
if (!$tokens[$docCommentIndex]->isGivenKind(T_DOC_COMMENT)) {
continue;
}
Preg::matchAll('/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/', $tokens[$docCommentIndex]->getContent(), $matches);
/** @var array $matches */
$matches = $matches[1];
foreach ($matches as $dataProviderName) {
$dataProviders[$dataProviderName][] = $docCommentIndex;
}
}
$dataProviderDefinitions = [];
foreach ($dataProviders as $dataProviderName => $dataProviderUsages) {
$lowercaseDataProviderName = strtolower($dataProviderName);
if (!\array_key_exists($lowercaseDataProviderName, $methods)) {
continue;
}
$dataProviderDefinitions[$methods[$lowercaseDataProviderName]] = $methods[$lowercaseDataProviderName];
}
ksort($dataProviderDefinitions);
return array_values($dataProviderDefinitions);
}
/**
* @return array
*/
private function getMethods(Tokens $tokens, int $startIndex, int $endIndex): array
{
$functions = [];
for ($index = $startIndex; $index < $endIndex; ++$index) {
if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
continue;
}
$functionNameIndex = $tokens->getNextNonWhitespace($index);
if (!$tokens[$functionNameIndex]->isGivenKind(T_STRING)) {
continue;
}
$functions[strtolower($tokens[$functionNameIndex]->getContent())] = $functionNameIndex;
}
return $functions;
}
}
PK ]NpVLGrj# j# , src/Tokenizer/Analyzer/FunctionsAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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, declarations: list}
*/
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 ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) {
return false;
}
if ($previousIsNamespaceSeparator) {
return true;
}
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
*/
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;
}
}
}
}
}
PK ]NpV & , src/Tokenizer/Analyzer/ArgumentsAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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
* @author Vladimir Reznichenko
*
* @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
*/
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
);
}
}
PK ]NpVBjus s ( src/Tokenizer/Analyzer/RangeAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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;
}
}
}
PK ]NpV6l] . src/Tokenizer/Analyzer/WhitespacesAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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);
}
}
PK ]NpV/
) src/Tokenizer/Analyzer/ClassyAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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]);
}
}
PK ]NpVͪJн$ $ + src/Tokenizer/Analyzer/CommentsAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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
*
* @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
*/
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;
}
}
PK ]NpV , src/Tokenizer/Analyzer/GotoLabelAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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]]);
}
}
PK ]NpV%k k , src/Tokenizer/Analyzer/AttributeAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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;
}
}
PK ]NpV!}a ) ) 8 src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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 $types Token types of interest of which analyzes must be returned
*
* @return \Generator
*/
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,
* 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,
* 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
*/
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;
}
}
PK ]NpVGR
R
4 src/Tokenizer/Analyzer/AlternativeSyntaxAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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(':');
}
}
PK ]NpVr , src/Tokenizer/Analyzer/ReferenceAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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
*
* @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]]);
}
}
PK ]NpV) ) 0 src/Tokenizer/Analyzer/Analysis/CaseAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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
*
* @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;
}
}
PK ]NpVR5 5 1 src/Tokenizer/Analyzer/Analysis/MatchAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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;
}
}
PK ]NpVl 3 src/Tokenizer/Analyzer/Analysis/DefaultAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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;
}
}
PK ]NpVqqT > src/Tokenizer/Analyzer/Analysis/StartEndTokenAwareAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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;
}
PK ]NpVxF~ ~ 8 src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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;
}
}
PK ]NpV[X X 4 src/Tokenizer/Analyzer/Analysis/ArgumentAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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;
}
}
PK ]NpVVq5 5 0 src/Tokenizer/Analyzer/Analysis/TypeAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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
*/
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;
}
}
PK ]NpVdp p 0 src/Tokenizer/Analyzer/Analysis/EnumAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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
*/
private array $cases;
/**
* @param list $cases
*/
public function __construct(int $index, int $open, int $close, array $cases)
{
parent::__construct($index, $open, $close);
$this->cases = $cases;
}
/**
* @return list
*/
public function getCases(): array
{
return $this->cases;
}
}
PK ]NpVs s 5 src/Tokenizer/Analyzer/Analysis/NamespaceAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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();
}
}
PK ]NpV@g g 2 src/Tokenizer/Analyzer/Analysis/SwitchAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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
*/
private array $cases;
private ?DefaultAnalysis $defaultAnalysis;
/**
* @param list $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
*/
public function getCases(): array
{
return $this->cases;
}
public function getDefaultAnalysis(): ?DefaultAnalysis
{
return $this->defaultAnalysis;
}
}
PK ]NpVɍv I src/Tokenizer/Analyzer/Analysis/AbstractControlCaseStructuresAnalysis.phpnu ٘
* Dariusz Rumiński
*
* 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;
}
}
PK ]NpVBMJ
- src/Tokenizer/Analyzer/NamespacesAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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
*/
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));
}
}
PK ]NpVZ ) src/Tokenizer/Analyzer/BlocksAnalyzer.phpnu ٘
* Dariusz Rumiński
*
* 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
*
* @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;
}
}
PK ]NpVZd % src/Tokenizer/AbstractTransformer.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
use PhpCsFixer\Utils;
/**
* @author Dariusz Rumiński
*
* @internal
*/
abstract class AbstractTransformer implements TransformerInterface
{
/**
* {@inheritdoc}
*/
public function getName(): string
{
$nameParts = explode('\\', static::class);
$name = substr(end($nameParts), 0, -\strlen('Transformer'));
return Utils::camelCaseToUnderscore($name);
}
/**
* {@inheritdoc}
*/
public function getPriority(): int
{
return 0;
}
/**
* {@inheritdoc}
*/
abstract public function getCustomTokens(): array;
}
PK ]NpV/r
src/Tokenizer/Transformers.phpnu ٘
* Dariusz Rumiński
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Tokenizer;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
/**
* Collection of Transformer classes.
*
* @author Dariusz Rumiński
*
* @internal
*/
final class Transformers
{
/**
* The registered transformers.
*
* @var list
*/
private array $items = [];
/**
* Register built in Transformers.
*/
private function __construct()
{
$this->registerBuiltInTransformers();
usort($this->items, static function (TransformerInterface $a, TransformerInterface $b): int {
return $b->getPriority() <=> $a->getPriority();
});
}
public static function createSingleton(): self
{
static $instance = null;
if (!$instance) {
$instance = new self();
}
return $instance;
}
/**
* Transform given Tokens collection through all Transformer classes.
*
* @param Tokens $tokens Tokens collection
*/
public function transform(Tokens $tokens): void
{
foreach ($this->items as $transformer) {
foreach ($tokens as $index => $token) {
$transformer->process($tokens, $token, $index);
}
}
}
/**
* @param TransformerInterface $transformer Transformer
*/
private function registerTransformer(TransformerInterface $transformer): void
{
if (\PHP_VERSION_ID >= $transformer->getRequiredPhpVersionId()) {
$this->items[] = $transformer;
}
}
private function registerBuiltInTransformers(): void
{
static $registered = false;
if ($registered) {
return;
}
$registered = true;
foreach ($this->findBuiltInTransformers() as $transformer) {
$this->registerTransformer($transformer);
}
}
/**
* @return \Generator
*/
private function findBuiltInTransformers(): iterable
{
/** @var SplFileInfo $file */
foreach (Finder::create()->files()->in(__DIR__.'/Transformer') as $file) {
$relativeNamespace = $file->getRelativePath();
$class = __NAMESPACE__.'\\Transformer\\'.($relativeNamespace ? $relativeNamespace.'\\' : '').$file->getBasename('.php');
yield new $class();
}
}
}
PK ]NpVʖ &