<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Fixer\DoctrineAnnotation;

use Doctrine\Common\Annotations\DocLexer;
use PhpCsFixer\AbstractDoctrineAnnotationFixer;
use PhpCsFixer\Doctrine\Annotation\Token;
use PhpCsFixer\Doctrine\Annotation\Tokens;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Preg;

/**
 * Fixes spaces around commas and assignment operators in Doctrine annotations.
 */
final class DoctrineAnnotationSpacesFixer extends AbstractDoctrineAnnotationFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Fixes spaces in Doctrine annotations.',
            [
                new CodeSample(
                    "<?php\n/**\n * @Foo ( )\n */\nclass Bar {}\n\n/**\n * @Foo(\"bar\" ,\"baz\")\n */\nclass Bar2 {}\n\n/**\n * @Foo(foo = \"foo\", bar = {\"foo\":\"foo\", \"bar\"=\"bar\"})\n */\nclass Bar3 {}\n"
                ),
            ],
            'There must not be any space around parentheses; commas must be preceded by no space and followed by one space; there must be no space around named arguments assignment operator; there must be one space around array assignment operator.'
        );
    }

    public function configure(array $configuration = null)
    {
        parent::configure($configuration);

        if (!$this->configuration['around_argument_assignments']) {
            foreach ([
                'before_argument_assignments',
                'after_argument_assignments',
            ] as $newOption) {
                if (!\array_key_exists($newOption, $configuration)) {
                    $this->configuration[$newOption] = null;
                }
            }
        }

        if (!$this->configuration['around_array_assignments']) {
            foreach ([
                'before_array_assignments_equals',
                'after_array_assignments_equals',
                'before_array_assignments_colon',
                'after_array_assignments_colon',
            ] as $newOption) {
                if (!\array_key_exists($newOption, $configuration)) {
                    $this->configuration[$newOption] = null;
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolver(array_merge(
            parent::createConfigurationDefinition()->getOptions(),
            [
                (new FixerOptionBuilder('around_parentheses', 'Whether to fix spaces around parentheses.'))
                    ->setAllowedTypes(['bool'])
                    ->setDefault(true)
                    ->getOption(),
                (new FixerOptionBuilder('around_commas', 'Whether to fix spaces around commas.'))
                    ->setAllowedTypes(['bool'])
                    ->setDefault(true)
                    ->getOption(),
                (new FixerOptionBuilder('around_argument_assignments', 'Whether to fix spaces around argument assignment operator.'))
                    ->setAllowedTypes(['bool'])
                    ->setDefault(true)
                    ->setDeprecationMessage('Use options `before_argument_assignments` and `after_argument_assignments` instead.')
                    ->getOption(),
                (new FixerOptionBuilder('before_argument_assignments', 'Whether to add, remove or ignore spaces before argument assignment operator.'))
                    ->setAllowedTypes(['null', 'bool'])
                    ->setDefault(false)
                    ->getOption(),
                (new FixerOptionBuilder('after_argument_assignments', 'Whether to add, remove or ignore spaces after argument assignment operator.'))
                    ->setAllowedTypes(['null', 'bool'])
                    ->setDefault(false)
                    ->getOption(),
                (new FixerOptionBuilder('around_array_assignments', 'Whether to fix spaces around array assignment operators.'))
                    ->setAllowedTypes(['bool'])
                    ->setDefault(true)
                    ->setDeprecationMessage('Use options `before_array_assignments_equals`, `after_array_assignments_equals`, `before_array_assignments_colon` and `after_array_assignments_colon` instead.')
                    ->getOption(),
                (new FixerOptionBuilder('before_array_assignments_equals', 'Whether to add, remove or ignore spaces before array `=` assignment operator.'))
                    ->setAllowedTypes(['null', 'bool'])
                    ->setDefault(true)
                    ->getOption(),
                (new FixerOptionBuilder('after_array_assignments_equals', 'Whether to add, remove or ignore spaces after array assignment `=` operator.'))
                    ->setAllowedTypes(['null', 'bool'])
                    ->setDefault(true)
                    ->getOption(),
                (new FixerOptionBuilder('before_array_assignments_colon', 'Whether to add, remove or ignore spaces before array `:` assignment operator.'))
                    ->setAllowedTypes(['null', 'bool'])
                    ->setDefault(true)
                    ->getOption(),
                (new FixerOptionBuilder('after_array_assignments_colon', 'Whether to add, remove or ignore spaces after array assignment `:` operator.'))
                    ->setAllowedTypes(['null', 'bool'])
                    ->setDefault(true)
                    ->getOption(),
            ]
        ));
    }

    /**
     * {@inheritdoc}
     */
    protected function fixAnnotations(Tokens $tokens)
    {
        if ($this->configuration['around_parentheses']) {
            $this->fixSpacesAroundParentheses($tokens);
        }

        if ($this->configuration['around_commas']) {
            $this->fixSpacesAroundCommas($tokens);
        }

        if (
            null !== $this->configuration['before_argument_assignments']
            || null !== $this->configuration['after_argument_assignments']
            || null !== $this->configuration['before_array_assignments_equals']
            || null !== $this->configuration['after_array_assignments_equals']
            || null !== $this->configuration['before_array_assignments_colon']
            || null !== $this->configuration['after_array_assignments_colon']
        ) {
            $this->fixAroundAssignments($tokens);
        }
    }

    /**
     * @param Tokens $tokens
     */
    private function fixSpacesAroundParentheses(Tokens $tokens)
    {
        $inAnnotationUntilIndex = null;

        foreach ($tokens as $index => $token) {
            if (null !== $inAnnotationUntilIndex) {
                if ($index === $inAnnotationUntilIndex) {
                    $inAnnotationUntilIndex = null;

                    continue;
                }
            } elseif ($tokens[$index]->isType(DocLexer::T_AT)) {
                $endIndex = $tokens->getAnnotationEnd($index);
                if (null !== $endIndex) {
                    $inAnnotationUntilIndex = $endIndex + 1;
                }

                continue;
            }

            if (null === $inAnnotationUntilIndex) {
                continue;
            }

            if (!$token->isType([DocLexer::T_OPEN_PARENTHESIS, DocLexer::T_CLOSE_PARENTHESIS])) {
                continue;
            }

            if ($token->isType(DocLexer::T_OPEN_PARENTHESIS)) {
                $token = $tokens[$index - 1];
                if ($token->isType(DocLexer::T_NONE)) {
                    $token->clear();
                }

                $token = $tokens[$index + 1];
            } else {
                $token = $tokens[$index - 1];
            }

            if ($token->isType(DocLexer::T_NONE)) {
                if (false !== strpos($token->getContent(), "\n")) {
                    continue;
                }

                $token->clear();
            }
        }
    }

    /**
     * @param Tokens $tokens
     */
    private function fixSpacesAroundCommas(Tokens $tokens)
    {
        $inAnnotationUntilIndex = null;

        foreach ($tokens as $index => $token) {
            if (null !== $inAnnotationUntilIndex) {
                if ($index === $inAnnotationUntilIndex) {
                    $inAnnotationUntilIndex = null;

                    continue;
                }
            } elseif ($tokens[$index]->isType(DocLexer::T_AT)) {
                $endIndex = $tokens->getAnnotationEnd($index);
                if (null !== $endIndex) {
                    $inAnnotationUntilIndex = $endIndex;
                }

                continue;
            }

            if (null === $inAnnotationUntilIndex) {
                continue;
            }

            if (!$token->isType(DocLexer::T_COMMA)) {
                continue;
            }

            $token = $tokens[$index - 1];
            if ($token->isType(DocLexer::T_NONE)) {
                $token->clear();
            }

            if ($index < \count($tokens) - 1 && !Preg::match('/^\s/', $tokens[$index + 1]->getContent())) {
                $tokens->insertAt($index + 1, new Token(DocLexer::T_NONE, ' '));
            }
        }
    }

    /**
     * @param Tokens $tokens
     */
    private function fixAroundAssignments(Tokens $tokens)
    {
        $beforeArguments = $this->configuration['before_argument_assignments'];
        $afterArguments = $this->configuration['after_argument_assignments'];
        $beforeArraysEquals = $this->configuration['before_array_assignments_equals'];
        $afterArraysEquals = $this->configuration['after_array_assignments_equals'];
        $beforeArraysColon = $this->configuration['before_array_assignments_colon'];
        $afterArraysColon = $this->configuration['after_array_assignments_colon'];

        $scopes = [];
        foreach ($tokens as $index => $token) {
            $endScopeType = end($scopes);
            if (false !== $endScopeType && $token->isType($endScopeType)) {
                array_pop($scopes);

                continue;
            }

            if ($tokens[$index]->isType(DocLexer::T_AT)) {
                $scopes[] = DocLexer::T_CLOSE_PARENTHESIS;

                continue;
            }

            if ($tokens[$index]->isType(DocLexer::T_OPEN_CURLY_BRACES)) {
                $scopes[] = DocLexer::T_CLOSE_CURLY_BRACES;

                continue;
            }

            if (DocLexer::T_CLOSE_PARENTHESIS === $endScopeType && $token->isType(DocLexer::T_EQUALS)) {
                $this->updateSpacesAfter($tokens, $index, $afterArguments);
                $this->updateSpacesBefore($tokens, $index, $beforeArguments);

                continue;
            }

            if (DocLexer::T_CLOSE_CURLY_BRACES === $endScopeType) {
                if ($token->isType(DocLexer::T_EQUALS)) {
                    $this->updateSpacesAfter($tokens, $index, $afterArraysEquals);
                    $this->updateSpacesBefore($tokens, $index, $beforeArraysEquals);

                    continue;
                }

                if ($token->isType(DocLexer::T_COLON)) {
                    $this->updateSpacesAfter($tokens, $index, $afterArraysColon);
                    $this->updateSpacesBefore($tokens, $index, $beforeArraysColon);
                }
            }
        }
    }

    /**
     * @param Tokens    $tokens
     * @param int       $index
     * @param null|bool $insert
     */
    private function updateSpacesAfter(Tokens $tokens, $index, $insert)
    {
        $this->updateSpacesAt($tokens, $index + 1, $index + 1, $insert);
    }

    /**
     * @param Tokens    $tokens
     * @param int       $index
     * @param null|bool $insert
     */
    private function updateSpacesBefore(Tokens $tokens, $index, $insert)
    {
        $this->updateSpacesAt($tokens, $index - 1, $index, $insert);
    }

    /**
     * @param Tokens    $tokens
     * @param int       $index
     * @param int       $insertIndex
     * @param null|bool $insert
     */
    private function updateSpacesAt(Tokens $tokens, $index, $insertIndex, $insert)
    {
        if (null === $insert) {
            return;
        }

        $token = $tokens[$index];
        if ($insert) {
            if (!$token->isType(DocLexer::T_NONE)) {
                $tokens->insertAt($insertIndex, $token = new Token());
            }

            $token->setContent(' ');
        } elseif ($token->isType(DocLexer::T_NONE)) {
            $token->clear();
        }
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Tokenizer\Transformer;

use PhpCsFixer\Tokenizer\AbstractTransformer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * Transform `array` typehint from T_ARRAY into CT::T_ARRAY_TYPEHINT.
 *
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * @internal
 */
final class ArrayTypehintTransformer extends AbstractTransformer
{
    /**
     * {@inheritdoc}
     */
    public function getCustomTokens()
    {
        return [CT::T_ARRAY_TYPEHINT];
    }

    /**
     * {@inheritdoc}
     */
    public function getRequiredPhpVersionId()
    {
        return 50000;
    }

    /**
     * {@inheritdoc}
     */
    public function process(Tokens $tokens, Token $token, $index)
    {
        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()]);
        }
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Fixer\ControlStructure;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * Fixer for rules defined in PSR2 ¶5.2.
 *
 * @author SpacePossum
 */
final class SwitchCaseSemicolonToColonFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'A case should be followed by a colon and not a semicolon.',
            [
                new CodeSample(
                    '<?php
    switch ($a) {
        case 1;
            break;
        default;
            break;
    }
'
                ),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isAnyTokenKindsFound([T_CASE, T_DEFAULT]);
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        foreach ($tokens as $index => $token) {
            if ($token->isGivenKind([T_CASE, T_DEFAULT])) {
                $this->fixSwitchCase($tokens, $index);
            }
        }
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     */
    protected function fixSwitchCase(Tokens $tokens, $index)
    {
        $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);

        if ($tokens[$index]->equals(';')) {
            $tokens[$index] = new Token(':');
        }
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Fixer\ReturnNotation;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\VersionSpecification;
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Graham Campbell <graham@alt-three.com>
 */
final class SimplifiedNullReturnFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'A return statement wishing to return `void` should not return `null`.',
            [
                new CodeSample("<?php return null;\n"),
                new VersionSpecificCodeSample(
                    <<<'EOT'
<?php
function foo() { return null; }
function bar(): int { return null; }
function baz(): ?int { return null; }
function xyz(): void { return null; }

EOT
                    ,
                    new VersionSpecification(70100)
                ),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // should be run before NoUselessReturnFixer
        return -17;
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isTokenKindFound(T_RETURN);
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        foreach ($tokens as $index => $token) {
            if (!$token->isGivenKind(T_RETURN)) {
                continue;
            }

            if ($this->needFixing($tokens, $index)) {
                $this->clear($tokens, $index);
            }
        }
    }

    /**
     * Clear the return statement located at a given index.
     *
     * @param Tokens $tokens
     * @param int    $index
     */
    private function clear(Tokens $tokens, $index)
    {
        while (!$tokens[++$index]->equals(';')) {
            if ($this->shouldClearToken($tokens, $index)) {
                $tokens->clearAt($index);
            }
        }
    }

    /**
     * Does the return statement located at a given index need fixing?
     *
     * @param Tokens $tokens
     * @param int    $index
     *
     * @return bool
     */
    private function needFixing(Tokens $tokens, $index)
    {
        if ($this->isStrictOrNullableReturnTypeFunction($tokens, $index)) {
            return false;
        }

        $content = '';
        while (!$tokens[$index]->equals(';')) {
            $index = $tokens->getNextMeaningfulToken($index);
            $content .= $tokens[$index]->getContent();
        }

        $content = ltrim($content, '(');
        $content = rtrim($content, ');');

        return 'null' === strtolower($content);
    }

    /**
     * Is the return within a function with a non-void or nullable return type?
     *
     * @param Tokens $tokens
     * @param int    $returnIndex Current return token index
     *
     * @return bool
     */
    private function isStrictOrNullableReturnTypeFunction(Tokens $tokens, $returnIndex)
    {
        $functionIndex = $returnIndex;
        do {
            $functionIndex = $tokens->getPrevTokenOfKind($functionIndex, [[T_FUNCTION]]);
            if (null === $functionIndex) {
                return false;
            }
            $openingCurlyBraceIndex = $tokens->getNextTokenOfKind($functionIndex, ['{']);
            $closingCurlyBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openingCurlyBraceIndex);
        } while ($closingCurlyBraceIndex < $returnIndex);

        $possibleVoidIndex = $tokens->getPrevMeaningfulToken($openingCurlyBraceIndex);
        $isStrictReturnType = $tokens[$possibleVoidIndex]->isGivenKind(T_STRING) && 'void' !== $tokens[$possibleVoidIndex]->getContent();

        $nullableTypeIndex = $tokens->getNextTokenOfKind($functionIndex, [[CT::T_NULLABLE_TYPE]]);
        $isNullableReturnType = null !== $nullableTypeIndex && $nullableTypeIndex < $openingCurlyBraceIndex;

        return $isStrictReturnType || $isNullableReturnType;
    }

    /**
     * Should we clear the specific token?
     *
     * If the token is a comment, or is whitespace that is immediately before a
     * comment, then we'll leave it alone.
     *
     * @param Tokens $tokens
     * @param int    $index
     *
     * @return bool
     */
    private function shouldClearToken(Tokens $tokens, $index)
    {
        $token = $tokens[$index];

        return !$token->isComment() && !($token->isWhitespace() && $tokens[$index + 1]->isComment());
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Fixer\PhpUnit;

use Composer\Semver\Comparator;

/**
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * @internal
 */
final class PhpUnitTargetVersion
{
    const VERSION_3_0 = '3.0';
    const VERSION_3_2 = '3.2';
    const VERSION_3_5 = '3.5';
    const VERSION_4_3 = '4.3';
    const VERSION_4_8 = '4.8';
    const VERSION_5_0 = '5.0';
    const VERSION_5_2 = '5.2';
    const VERSION_5_4 = '5.4';
    const VERSION_5_5 = '5.5';
    const VERSION_5_6 = '5.6';
    const VERSION_5_7 = '5.7';
    const VERSION_6_0 = '6.0';
    const VERSION_NEWEST = 'newest';

    private function __construct()
    {
    }

    /**
     * @param string $candidate
     * @param string $target
     *
     * @return bool
     */
    public static function fulfills($candidate, $target)
    {
        if (self::VERSION_NEWEST === $target) {
            throw new \LogicException(sprintf('Parameter `target` shall not be provided as `%s`, determine proper target for tested PHPUnit feature instead.', self::VERSION_NEWEST));
        }

        if (self::VERSION_NEWEST === $candidate) {
            return true;
        }

        return Comparator::greaterThanOrEqualTo($candidate, $target);
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\DocBlock;

use PhpCsFixer\Preg;

/**
 * This represents an entire annotation from a docblock.
 *
 * @author Graham Campbell <graham@alt-three.com>
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 */
class Annotation
{
    /**
     * Regex to match any types, shall be used with `x` modifier.
     *
     * @internal
     */
    const REGEX_TYPES = '
    # <simple> is any non-array, non-generic, non-alternated type, eg `int` or `\Foo`
    # <array> is array of <simple>, eg `int[]` or `\Foo[]`
    # <generic> is generic collection type, like `array<string, int>`, `Collection<Item>` and more complex like `Collection<int, \null|SubCollection<string>>`
    # <type> is <simple>, <array> or <generic> type, like `int`, `bool[]` or `Collection<ItemKey, ItemVal>`
    # <types> is one or more types alternated via `|`, like `int|bool[]|Collection<ItemKey, ItemVal>`
    (?<types>
        (?<type>
            (?<array>
                (?&simple)\[\]
            )
            |
            (?<simple>
                [@$?]?[\\\\\w]+
            )
            |
            (?<generic>
                (?&simple)
                <
                    (?:(?&types),\s*)?(?:(?&types)|(?&generic))
                >
            )
        )
        (?:
            \|
            (?:(?&simple)|(?&array)|(?&generic))
        )*
    )
    ';

    /**
     * All the annotation tag names with types.
     *
     * @var string[]
     */
    private static $tags = [
        'method',
        'param',
        'property',
        'property-read',
        'property-write',
        'return',
        'throws',
        'type',
        'var',
    ];

    /**
     * The lines that make up the annotation.
     *
     * @var Line[]
     */
    private $lines;

    /**
     * The position of the first line of the annotation in the docblock.
     *
     * @var int
     */
    private $start;

    /**
     * The position of the last line of the annotation in the docblock.
     *
     * @var int
     */
    private $end;

    /**
     * The associated tag.
     *
     * @var null|Tag
     */
    private $tag;

    /**
     * Lazy loaded, cached types content.
     *
     * @var null|string
     */
    private $typesContent;

    /**
     * The cached types.
     *
     * @var null|string[]
     */
    private $types;

    /**
     * Create a new line instance.
     *
     * @param Line[] $lines
     */
    public function __construct(array $lines)
    {
        $this->lines = array_values($lines);

        $keys = array_keys($lines);

        $this->start = $keys[0];
        $this->end = end($keys);
    }

    /**
     * Get the string representation of object.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->getContent();
    }

    /**
     * Get all the annotation tag names with types.
     *
     * @return string[]
     */
    public static function getTagsWithTypes()
    {
        return self::$tags;
    }

    /**
     * Get the start position of this annotation.
     *
     * @return int
     */
    public function getStart()
    {
        return $this->start;
    }

    /**
     * Get the end position of this annotation.
     *
     * @return int
     */
    public function getEnd()
    {
        return $this->end;
    }

    /**
     * Get the associated tag.
     *
     * @return Tag
     */
    public function getTag()
    {
        if (null === $this->tag) {
            $this->tag = new Tag($this->lines[0]);
        }

        return $this->tag;
    }

    /**
     * Get the types associated with this annotation.
     *
     * @return string[]
     */
    public function getTypes()
    {
        if (null === $this->types) {
            $this->types = [];

            $content = $this->getTypesContent();

            while ('' !== $content && false !== $content) {
                Preg::match(
                    '{^'.self::REGEX_TYPES.'$}x',
                    $content,
                    $matches
                );

                $this->types[] = $matches['type'];
                $content = substr($content, \strlen($matches['type']) + 1);
            }
        }

        return $this->types;
    }

    /**
     * Set the types associated with this annotation.
     *
     * @param string[] $types
     */
    public function setTypes(array $types)
    {
        $pattern = '/'.preg_quote($this->getTypesContent(), '/').'/';

        $this->lines[0]->setContent(Preg::replace($pattern, implode('|', $types), $this->lines[0]->getContent(), 1));

        $this->clearCache();
    }

    /**
     * Get the normalized types associated with this annotation, so they can easily be compared.
     *
     * @return string[]
     */
    public function getNormalizedTypes()
    {
        $normalized = array_map(static function ($type) {
            return strtolower($type);
        }, $this->getTypes());

        sort($normalized);

        return $normalized;
    }

    /**
     * Remove this annotation by removing all its lines.
     */
    public function remove()
    {
        foreach ($this->lines as $line) {
            $line->remove();
        }

        $this->clearCache();
    }

    /**
     * Get the annotation content.
     *
     * @return string
     */
    public function getContent()
    {
        return implode('', $this->lines);
    }

    public function supportTypes()
    {
        return \in_array($this->getTag()->getName(), self::$tags, true);
    }

    /**
     * Get the current types content.
     *
     * Be careful modifying the underlying line as that won't flush the cache.
     *
     * @return string
     */
    private function getTypesContent()
    {
        if (null === $this->typesContent) {
            $name = $this->getTag()->getName();

            if (!$this->supportTypes()) {
                throw new \RuntimeException('This tag does not support types.');
            }

            $matchingResult = Preg::match(
                '{^(?:\s*\*|/\*\*)\s*@'.$name.'\s+'.self::REGEX_TYPES.'(?:[ \t].*)?$}sx',
                $this->lines[0]->getContent(),
                $matches
            );

            $this->typesContent = 1 === $matchingResult
                ? $matches['types']
                : '';
        }

        return $this->typesContent;
    }

    private function clearCache()
    {
        $this->types = null;
        $this->typesContent = null;
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Fixer\ControlStructure;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * Fixer for rules defined in PSR2 ¶5.1.
 *
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 */
final class ElseifFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'The keyword `elseif` should be used instead of `else if` so that all control keywords look like single words.',
            [new CodeSample("<?php\nif (\$a) {\n} else if (\$b) {\n}\n")]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isAllTokenKindsFound([T_IF, T_ELSE]);
    }

    /**
     * Replace all `else if` (T_ELSE T_IF) with `elseif` (T_ELSEIF).
     *
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        foreach ($tokens as $index => $token) {
            if (!$token->isGivenKind(T_ELSE)) {
                continue;
            }

            $ifTokenIndex = $tokens->getNextMeaningfulToken($index);

            // if next meaningful token is not T_IF - continue searching, this is not the case for fixing
            if (!$tokens[$ifTokenIndex]->isGivenKind(T_IF)) {
                continue;
            }

            // if next meaningful token is T_IF, but uses an alternative syntax - this is not the case for fixing neither
            $conditionEndBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $tokens->getNextMeaningfulToken($ifTokenIndex));
            $afterConditionIndex = $tokens->getNextMeaningfulToken($conditionEndBraceIndex);
            if ($tokens[$afterConditionIndex]->equals(':')) {
                continue;
            }

            // now we have T_ELSE following by T_IF with no alternative syntax so we could fix this
            // 1. clear whitespaces between T_ELSE and T_IF
            $tokens->clearAt($index + 1);

            // 2. change token from T_ELSE into T_ELSEIF
            $tokens[$index] = new Token([T_ELSEIF, 'elseif']);

            // 3. clear succeeding T_IF
            $tokens->clearAt($ifTokenIndex);

            $beforeIfTokenIndex = $tokens->getPrevNonWhitespace($ifTokenIndex);

            // 4. clear extra whitespace after T_IF in T_COMMENT,T_WHITESPACE?,T_IF,T_WHITESPACE sequence
            if ($tokens[$beforeIfTokenIndex]->isComment() && $tokens[$ifTokenIndex + 1]->isWhitespace()) {
                $tokens->clearAt($ifTokenIndex + 1);
            }
        }
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Tokenizer\Analyzer;

use PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 * @author Vladimir Reznichenko <kalessil@gmail.com>
 *
 * @internal
 */
final class ArgumentsAnalyzer
{
    /**
     * Count amount of parameters in a function/method reference.
     *
     * @param Tokens $tokens
     * @param int    $openParenthesis
     * @param int    $closeParenthesis
     *
     * @return int
     */
    public function countArguments(Tokens $tokens, $openParenthesis, $closeParenthesis)
    {
        return \count($this->getArguments($tokens, $openParenthesis, $closeParenthesis));
    }

    /**
     * Returns start and end token indexes 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 ')'.
     *
     * @param Tokens $tokens
     * @param int    $openParenthesis
     * @param int    $closeParenthesis
     *
     * @return array<int, int>
     */
    public function getArguments(Tokens $tokens, $openParenthesis, $closeParenthesis)
    {
        $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;
    }

    /**
     * @param Tokens $tokens
     * @param int    $argumentStart
     * @param int    $argumentEnd
     *
     * @return ArgumentAnalysis
     */
    public function getArgumentInfo(Tokens $tokens, $argumentStart, $argumentEnd)
    {
        $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 ($token->isComment() || $token->isWhitespace() || $token->isGivenKind(T_ELLIPSIS) || $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
        );
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Fixer\ControlStructure;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;

/**
 * @author Bram Gotink <bram@gotink.me>
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 * @author SpacePossum
 */
final class YodaStyleFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    /**
     * @var array<int|string, Token>
     */
    private $candidatesMap;

    /**
     * @var array<int|string, null|bool>
     */
    private $candidateTypesConfiguration;

    /**
     * @var array<int|string>
     */
    private $candidateTypes;

    /**
     * {@inheritdoc}
     */
    public function configure(array $configuration = null)
    {
        parent::configure($configuration);

        $this->resolveConfiguration();
    }

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Write conditions in Yoda style (`true`), non-Yoda style (`false`) or ignore those conditions (`null`) based on configuration.',
            [
                new CodeSample(
                    '<?php
    if ($a === null) {
        echo "null";
    }
'
                ),
                new CodeSample(
                    '<?php
    $b = $c != 1;  // equal
    $a = 1 === $b; // identical
    $c = $c > 3;   // less than
',
                    [
                        'equal' => true,
                        'identical' => false,
                        'less_and_greater' => null,
                    ]
                ),
                new CodeSample(
                    '<?php
return $foo === count($bar);
',
                    [
                        'always_move_variable' => true,
                    ]
                ),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isAnyTokenKindsFound($this->candidateTypes);
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $this->fixTokens($tokens);
    }

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolver([
            (new FixerOptionBuilder('equal', 'Style for equal (`==`, `!=`) statements.'))
                ->setAllowedTypes(['bool', 'null'])
                ->setDefault(true)
                ->getOption(),
            (new FixerOptionBuilder('identical', 'Style for identical (`===`, `!==`) statements.'))
                ->setAllowedTypes(['bool', 'null'])
                ->setDefault(true)
                ->getOption(),
            (new FixerOptionBuilder('less_and_greater', 'Style for less and greater than (`<`, `<=`, `>`, `>=`) statements.'))
                ->setAllowedTypes(['bool', 'null'])
                ->setDefault(null)
                ->getOption(),
            (new FixerOptionBuilder('always_move_variable', 'Whether variables should always be on non assignable side when applying Yoda style.'))
                ->setAllowedTypes(['bool'])
                ->setDefault(false)
                ->getOption(),
        ]);
    }

    /**
     * Finds the end of the right-hand side of the comparison at the given
     * index.
     *
     * The right-hand side ends when an operator with a lower precedence is
     * encountered or when the block level for `()`, `{}` or `[]` goes below
     * zero.
     *
     * @param Tokens $tokens The token list
     * @param int    $index  The index of the comparison
     *
     * @return int The last index of the right-hand side of the comparison
     */
    private function findComparisonEnd(Tokens $tokens, $index)
    {
        ++$index;
        $count = \count($tokens);
        while ($index < $count) {
            $token = $tokens[$index];
            if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
                ++$index;

                continue;
            }

            if ($this->isOfLowerPrecedence($token)) {
                break;
            }

            $block = Tokens::detectBlockType($token);
            if (null === $block) {
                ++$index;

                continue;
            }

            if (!$block['isStart']) {
                break;
            }

            $index = $tokens->findBlockEnd($block['type'], $index) + 1;
        }

        $prev = $tokens->getPrevMeaningfulToken($index);

        return $tokens[$prev]->isGivenKind(T_CLOSE_TAG) ? $tokens->getPrevMeaningfulToken($prev) : $prev;
    }

    /**
     * Finds the start of the left-hand side of the comparison at the given
     * index.
     *
     * The left-hand side ends when an operator with a lower precedence is
     * encountered or when the block level for `()`, `{}` or `[]` goes below
     * zero.
     *
     * @param Tokens $tokens The token list
     * @param int    $index  The index of the comparison
     *
     * @return int The first index of the left-hand side of the comparison
     */
    private function findComparisonStart(Tokens $tokens, $index)
    {
        --$index;
        $nonBlockFound = false;

        while (0 <= $index) {
            $token = $tokens[$index];
            if ($token->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
                --$index;

                continue;
            }

            if ($this->isOfLowerPrecedence($token)) {
                break;
            }

            $block = Tokens::detectBlockType($token);
            if (null === $block) {
                --$index;
                $nonBlockFound = true;

                continue;
            }

            if (
                $block['isStart']
                || ($nonBlockFound && Tokens::BLOCK_TYPE_CURLY_BRACE === $block['type']) // closing of structure not related to the comparison
            ) {
                break;
            }

            $index = $tokens->findBlockStart($block['type'], $index) - 1;
        }

        return $tokens->getNextMeaningfulToken($index);
    }

    /**
     * @param Tokens $tokens
     *
     * @return Tokens
     */
    private function fixTokens(Tokens $tokens)
    {
        for ($i = \count($tokens) - 1; $i > 1; --$i) {
            if ($tokens[$i]->isGivenKind($this->candidateTypes)) {
                $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getId()];
            } elseif (
                ($tokens[$i]->equals('<') && \in_array('<', $this->candidateTypes, true))
                || ($tokens[$i]->equals('>') && \in_array('>', $this->candidateTypes, true))
            ) {
                $yoda = $this->candidateTypesConfiguration[$tokens[$i]->getContent()];
            } else {
                continue;
            }

            $fixableCompareInfo = $this->getCompareFixableInfo($tokens, $i, $yoda);
            if (null === $fixableCompareInfo) {
                continue;
            }

            $i = $this->fixTokensCompare(
                $tokens,
                $fixableCompareInfo['left']['start'],
                $fixableCompareInfo['left']['end'],
                $i,
                $fixableCompareInfo['right']['start'],
                $fixableCompareInfo['right']['end']
            );
        }

        return $tokens;
    }

    /**
     * Fixes the comparison at the given index.
     *
     * A comparison is considered fixed when
     * - both sides are a variable (e.g. $a === $b)
     * - neither side is a variable (e.g. self::CONST === 3)
     * - only the right-hand side is a variable (e.g. 3 === self::$var)
     *
     * If the left-hand side and right-hand side of the given comparison are
     * swapped, this function runs recursively on the previous left-hand-side.
     *
     * @param Tokens $tokens
     * @param int    $startLeft
     * @param int    $endLeft
     * @param int    $compareOperatorIndex
     * @param int    $startRight
     * @param int    $endRight
     *
     * @return int a upper bound for all non-fixed comparisons
     */
    private function fixTokensCompare(
        Tokens $tokens,
        $startLeft,
        $endLeft,
        $compareOperatorIndex,
        $startRight,
        $endRight
    ) {
        $type = $tokens[$compareOperatorIndex]->getId();
        $content = $tokens[$compareOperatorIndex]->getContent();
        if (\array_key_exists($type, $this->candidatesMap)) {
            $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$type];
        } elseif (\array_key_exists($content, $this->candidatesMap)) {
            $tokens[$compareOperatorIndex] = clone $this->candidatesMap[$content];
        }

        $right = $this->fixTokensComparePart($tokens, $startRight, $endRight);
        $left = $this->fixTokensComparePart($tokens, $startLeft, $endLeft);

        for ($i = $startRight; $i <= $endRight; ++$i) {
            $tokens->clearAt($i);
        }

        for ($i = $startLeft; $i <= $endLeft; ++$i) {
            $tokens->clearAt($i);
        }

        $tokens->insertAt($startRight, $left);
        $tokens->insertAt($startLeft, $right);

        return $startLeft;
    }

    /**
     * @param Tokens $tokens
     * @param int    $start
     * @param int    $end
     *
     * @return Tokens
     */
    private function fixTokensComparePart(Tokens $tokens, $start, $end)
    {
        $newTokens = $tokens->generatePartialCode($start, $end);
        $newTokens = $this->fixTokens(Tokens::fromCode(sprintf('<?php %s;', $newTokens)));
        $newTokens->clearAt(\count($newTokens) - 1);
        $newTokens->clearAt(0);
        $newTokens->clearEmptyTokens();

        return $newTokens;
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     * @param bool   $yoda
     *
     * @return null|array
     */
    private function getCompareFixableInfo(Tokens $tokens, $index, $yoda)
    {
        $left = $this->getLeftSideCompareFixableInfo($tokens, $index);
        $right = $this->getRightSideCompareFixableInfo($tokens, $index);

        if ($yoda) {
            $expectedAssignableSide = $right;
            $expectedValueSide = $left;
        } else {
            if ($tokens[$tokens->getNextMeaningfulToken($right['end'])]->equals('=')) {
                return null;
            }

            $expectedAssignableSide = $left;
            $expectedValueSide = $right;
        }

        if (
            // variable cannot be moved to expected side
            !(
                !$this->isVariable($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end'], false)
                && !$this->isListStatement($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end'])
                && $this->isVariable($tokens, $expectedValueSide['start'], $expectedValueSide['end'], false)
            )
            // variable cannot be moved to expected side (strict mode)
            && !(
                $this->configuration['always_move_variable']
                && !$this->isVariable($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end'], true)
                && !$this->isListStatement($tokens, $expectedAssignableSide['start'], $expectedAssignableSide['end'])
                && $this->isVariable($tokens, $expectedValueSide['start'], $expectedValueSide['end'], true)
            )
        ) {
            return null;
        }

        return [
            'left' => $left,
            'right' => $right,
        ];
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     *
     * @return array
     */
    private function getLeftSideCompareFixableInfo(Tokens $tokens, $index)
    {
        return [
            'start' => $this->findComparisonStart($tokens, $index),
            'end' => $tokens->getPrevMeaningfulToken($index),
        ];
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     *
     * @return array
     */
    private function getRightSideCompareFixableInfo(Tokens $tokens, $index)
    {
        return [
            'start' => $tokens->getNextMeaningfulToken($index),
            'end' => $this->findComparisonEnd($tokens, $index),
        ];
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     * @param int    $end
     *
     * @return bool
     */
    private function isListStatement(Tokens $tokens, $index, $end)
    {
        for ($i = $index; $i <= $end; ++$i) {
            if ($tokens[$i]->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks whether the given token has a lower precedence than `T_IS_EQUAL`
     * or `T_IS_IDENTICAL`.
     *
     * @param Token $token The token to check
     *
     * @return bool Whether the token has a lower precedence
     */
    private function isOfLowerPrecedence(Token $token)
    {
        static $tokens;

        if (null === $tokens) {
            $tokens = [
                T_AND_EQUAL,    // &=
                T_BOOLEAN_AND,  // &&
                T_BOOLEAN_OR,   // ||
                T_CASE,         // case
                T_CONCAT_EQUAL, // .=
                T_DIV_EQUAL,    // /=
                T_DOUBLE_ARROW, // =>
                T_GOTO,         // goto
                T_LOGICAL_AND,  // and
                T_LOGICAL_OR,   // or
                T_LOGICAL_XOR,  // xor
                T_MINUS_EQUAL,  // -=
                T_MUL_EQUAL,    // *=
                T_OR_EQUAL,     // |=
                T_PLUS_EQUAL,   // +=
                T_RETURN,       // return
                T_SL_EQUAL,     // <<
                T_SR_EQUAL,     // >>=
                T_THROW,        // throw
                T_XOR_EQUAL,    // ^=
                T_ECHO,
                T_PRINT,
                T_OPEN_TAG,
                T_OPEN_TAG_WITH_ECHO,
            ];

            if (\defined('T_POW_EQUAL')) {
                $tokens[] = T_POW_EQUAL; // **=
            }

            if (\defined('T_COALESCE')) {
                $tokens[] = T_COALESCE; // ??
            }
        }

        static $otherTokens = [
            // bitwise and, or, xor
            '&', '|', '^',
            // ternary operators
            '?', ':',
            // assignment
            '=',
            // end of PHP statement
            ',', ';',
        ];

        return $token->isGivenKind($tokens) || $token->equalsAny($otherTokens);
    }

    /**
     * Checks whether the tokens between the given start and end describe a
     * variable.
     *
     * @param Tokens $tokens The token list
     * @param int    $start  The first index of the possible variable
     * @param int    $end    The last index of the possible variable
     * @param bool   $strict Enable strict variable detection
     *
     * @return bool Whether the tokens describe a variable
     */
    private function isVariable(Tokens $tokens, $start, $end, $strict)
    {
        $tokenAnalyzer = new TokensAnalyzer($tokens);

        if ($start === $end) {
            return $tokens[$start]->isGivenKind(T_VARIABLE);
        }

        if ($strict) {
            if ($tokens[$start]->equals('(')) {
                return false;
            }

            for ($index = $start; $index <= $end; ++$index) {
                if (
                    $tokens[$index]->isCast()
                    || $tokens[$index]->isGivenKind(T_INSTANCEOF)
                    || $tokens[$index]->equals('!')
                    || $tokenAnalyzer->isBinaryOperator($index)
                ) {
                    return false;
                }
            }
        }

        $index = $start;

        // handle multiple braces around statement ((($a === 1)))
        while (
            $tokens[$index]->equals('(')
            && $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index) === $end
        ) {
            $index = $tokens->getNextMeaningfulToken($index);
            $end = $tokens->getPrevMeaningfulToken($end);
        }

        $expectString = false;
        while ($index <= $end) {
            $current = $tokens[$index];
            if ($current->isComment() || $current->isWhitespace() || $tokens->isEmptyAt($index)) {
                ++$index;

                continue;
            }

            // check if this is the last token
            if ($index === $end) {
                return $current->isGivenKind($expectString ? T_STRING : T_VARIABLE);
            }

            if ($current->isGivenKind([T_LIST, CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE])) {
                return false;
            }

            $nextIndex = $tokens->getNextMeaningfulToken($index);
            $next = $tokens[$nextIndex];

            // self:: or ClassName::
            if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_DOUBLE_COLON)) {
                $index = $tokens->getNextMeaningfulToken($nextIndex);

                continue;
            }

            // \ClassName
            if ($current->isGivenKind(T_NS_SEPARATOR) && $next->isGivenKind(T_STRING)) {
                $index = $nextIndex;

                continue;
            }

            // ClassName\
            if ($current->isGivenKind(T_STRING) && $next->isGivenKind(T_NS_SEPARATOR)) {
                $index = $nextIndex;

                continue;
            }

            // $a-> or a-> (as in $b->a->c)
            if ($current->isGivenKind([T_STRING, T_VARIABLE]) && $next->isGivenKind(T_OBJECT_OPERATOR)) {
                $index = $tokens->getNextMeaningfulToken($nextIndex);
                $expectString = true;

                continue;
            }

            // $a[...], a[...] (as in $c->a[$b]), $a{...} or a{...} (as in $c->a{$b})
            if (
                $current->isGivenKind($expectString ? T_STRING : T_VARIABLE)
                && $next->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']])
            ) {
                $index = $tokens->findBlockEnd(
                    $next->equals('[') ? Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE : Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE,
                    $nextIndex
                );

                if ($index === $end) {
                    return true;
                }

                $index = $tokens->getNextMeaningfulToken($index);

                if (!$tokens[$index]->equalsAny([[T_OBJECT_OPERATOR, '->'], '[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{']])) {
                    return false;
                }

                $index = $tokens->getNextMeaningfulToken($index);
                $expectString = true;

                continue;
            }

            // $a(...) or $a->b(...)
            if ($strict && $current->isGivenKind([T_STRING, T_VARIABLE]) && $next->equals('(')) {
                return false;
            }

            // {...} (as in $a->{$b})
            if ($expectString && $current->isGivenKind(CT::T_DYNAMIC_PROP_BRACE_OPEN)) {
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_DYNAMIC_PROP_BRACE, $index);
                if ($index === $end) {
                    return true;
                }

                $index = $tokens->getNextMeaningfulToken($index);

                if (!$tokens[$index]->isGivenKind(T_OBJECT_OPERATOR)) {
                    return false;
                }

                $index = $tokens->getNextMeaningfulToken($index);
                $expectString = true;

                continue;
            }

            break;
        }

        return !$this->isConstant($tokens, $start, $end);
    }

    private function isConstant(Tokens $tokens, $index, $end)
    {
        $expectNumberOnly = false;
        $expectNothing = false;

        for (; $index <= $end; ++$index) {
            $token = $tokens[$index];

            if ($token->isComment() || $token->isWhitespace()) {
                if ($expectNothing) {
                    return false;
                }

                continue;
            }

            if ($expectNumberOnly && !$token->isGivenKind([T_LNUMBER, T_DNUMBER])) {
                return false;
            }

            if ($token->equals('-')) {
                $expectNumberOnly = true;

                continue;
            }

            if (
                $token->isGivenKind([T_LNUMBER, T_DNUMBER, T_CONSTANT_ENCAPSED_STRING])
                || $token->equalsAny([[T_STRING, 'true'], [T_STRING, 'false'], [T_STRING, 'null']])
            ) {
                $expectNothing = true;

                continue;
            }

            return false;
        }

        return true;
    }

    private function resolveConfiguration()
    {
        $candidateTypes = [];
        $this->candidatesMap = [];

        if (null !== $this->configuration['equal']) {
            // `==`, `!=` and `<>`
            $candidateTypes[T_IS_EQUAL] = $this->configuration['equal'];
            $candidateTypes[T_IS_NOT_EQUAL] = $this->configuration['equal'];
        }

        if (null !== $this->configuration['identical']) {
            // `===` and `!==`
            $candidateTypes[T_IS_IDENTICAL] = $this->configuration['identical'];
            $candidateTypes[T_IS_NOT_IDENTICAL] = $this->configuration['identical'];
        }

        if (null !== $this->configuration['less_and_greater']) {
            // `<`, `<=`, `>` and `>=`
            $candidateTypes[T_IS_SMALLER_OR_EQUAL] = $this->configuration['less_and_greater'];
            $this->candidatesMap[T_IS_SMALLER_OR_EQUAL] = new Token([T_IS_GREATER_OR_EQUAL, '>=']);

            $candidateTypes[T_IS_GREATER_OR_EQUAL] = $this->configuration['less_and_greater'];
            $this->candidatesMap[T_IS_GREATER_OR_EQUAL] = new Token([T_IS_SMALLER_OR_EQUAL, '<=']);

            $candidateTypes['<'] = $this->configuration['less_and_greater'];
            $this->candidatesMap['<'] = new Token('>');

            $candidateTypes['>'] = $this->configuration['less_and_greater'];
            $this->candidatesMap['>'] = new Token('<');
        }

        $this->candidateTypesConfiguration = $candidateTypes;
        $this->candidateTypes = array_keys($candidateTypes);
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Tokenizer;

/**
 * Analyzer of Tokens collection.
 *
 * Its role is to provide the ability to analyze collection.
 *
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 * @author Gregor Harlan <gharlan@web.de>
 * @author SpacePossum
 *
 * @internal
 */
final class TokensAnalyzer
{
    /**
     * Tokens collection instance.
     *
     * @var Tokens
     */
    private $tokens;

    public function __construct(Tokens $tokens)
    {
        $this->tokens = $tokens;
    }

    /**
     * Get indexes of methods and properties in classy code (classes, interfaces and traits).
     *
     * @return array[]
     */
    public function getClassyElements()
    {
        $this->tokens->rewind();
        $elements = [];

        for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) {
            if ($this->tokens[$index]->isClassy()) {
                list($index, $newElements) = $this->findClassyElements($index);
                $elements += $newElements;
            }
        }

        ksort($elements);

        return $elements;
    }

    /**
     * Get indexes of namespace uses.
     *
     * @param bool $perNamespace Return namespace uses per namespace
     *
     * @return int[]|int[][]
     */
    public function getImportUseIndexes($perNamespace = false)
    {
        $tokens = $this->tokens;

        $tokens->rewind();

        $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.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isArray($index)
    {
        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.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isArrayMultiLine($index)
    {
        if (!$this->isArray($index)) {
            throw new \InvalidArgumentException(sprintf('Not an array at given index %d.', $index));
        }

        $tokens = $this->tokens;

        // Skip only when its an array, for short arrays we need the brace for correct
        // level counting
        if ($tokens[$index]->isGivenKind(T_ARRAY)) {
            $index = $tokens->getNextMeaningfulToken($index);
        }

        $endIndex = $tokens[$index]->equals('(')
            ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index)
            : $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index)
        ;

        for (++$index; $index < $endIndex; ++$index) {
            $token = $tokens[$index];
            $blockType = Tokens::detectBlockType($token);

            if ($blockType && $blockType['isStart']) {
                $index = $tokens->findBlockEnd($blockType['type'], $index);

                continue;
            }

            if (
                $token->isWhitespace() &&
                !$tokens[$index - 1]->isGivenKind(T_END_HEREDOC) &&
                false !== strpos($token->getContent(), "\n")
            ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns the attributes of the method under the given index.
     *
     * The array has the following items:
     * 'visibility' int|null  T_PRIVATE, T_PROTECTED or T_PUBLIC
     * 'static'     bool
     * 'abstract'   bool
     * 'final'      bool
     *
     * @param int $index Token index of the method (T_FUNCTION)
     *
     * @return array
     */
    public function getMethodAttributes($index)
    {
        $tokens = $this->tokens;
        $token = $tokens[$index];

        if (!$token->isGivenKind(T_FUNCTION)) {
            throw new \LogicException(sprintf('No T_FUNCTION at given index %d, got %s.', $index, $token->getName()));
        }

        $attributes = [
            'visibility' => null,
            'static' => false,
            'abstract' => false,
            'final' => false,
        ];

        for ($i = $index; $i >= 0; --$i) {
            $tokenIndex = $tokens->getPrevMeaningfulToken($i);

            $i = $tokenIndex;
            $token = $tokens[$tokenIndex];

            if ($token->isGivenKind(T_STATIC)) {
                $attributes['static'] = true;

                continue;
            }

            if ($token->isGivenKind(T_FINAL)) {
                $attributes['final'] = true;

                continue;
            }

            if ($token->isGivenKind(T_ABSTRACT)) {
                $attributes['abstract'] = true;

                continue;
            }

            // visibility

            if ($token->isGivenKind(T_PRIVATE)) {
                $attributes['visibility'] = T_PRIVATE;

                continue;
            }

            if ($token->isGivenKind(T_PROTECTED)) {
                $attributes['visibility'] = T_PROTECTED;

                continue;
            }

            if ($token->isGivenKind(T_PUBLIC)) {
                $attributes['visibility'] = T_PUBLIC;

                continue;
            }

            // found a meaningful token that is not part of
            // the function signature; stop looking
            break;
        }

        return $attributes;
    }

    /**
     * Check if there is an anonymous class under given index.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isAnonymousClass($index)
    {
        $tokens = $this->tokens;
        $token = $tokens[$index];

        if (!$token->isClassy()) {
            throw new \LogicException(sprintf('No classy token at given index %d.', $index));
        }

        if (!$token->isGivenKind(T_CLASS)) {
            return false;
        }

        return $tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NEW);
    }

    /**
     * Check if the function under given index is a lambda.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isLambda($index)
    {
        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()));
        }

        $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.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isConstantInvocation($index)
    {
        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, 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_OBJECT_OPERATOR, T_TRAIT])) {
            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, 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 `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([CT::T_GROUP_IMPORT_BRACE_OPEN, T_IMPLEMENTS, 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 goto label
        if ($this->tokens[$nextIndex]->equals(':') && $this->tokens[$prevIndex]->equalsAny([';', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]])) {
            return false;
        }

        return true;
    }

    /**
     * Checks if there is an unary successor operator under given index.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isUnarySuccessorOperator($index)
    {
        static $allowedPrevToken = [
            ']',
            [T_STRING],
            [T_VARIABLE],
            [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 an unary predecessor operator under given index.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isUnaryPredecessorOperator($index)
    {
        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_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.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isBinaryOperator($index)
    {
        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,            // ^=
                CT::T_TYPE_ALTERNATION => true, // |
            ];

            if (\defined('T_SPACESHIP')) {
                $arrayOperators[T_SPACESHIP] = true; // <=>
            }

            if (\defined('T_COALESCE')) {
                $arrayOperators[T_COALESCE] = true;  // ??
            }
        }

        $tokens = $this->tokens;
        $token = $tokens[$index];

        if ($token->isArray()) {
            return isset($arrayOperators[$token->getId()]);
        }

        if (isset($nonArrayOperators[$token->getContent()])) {
            return true;
        }

        if (isset($potentialUnaryNonArrayOperators[$token->getContent()])) {
            return !$this->isUnaryPredecessorOperator($index);
        }

        return false;
    }

    /**
     * Check if `T_WHILE` token at given index is `do { ... } while ();` syntax
     * and not `while () { ...}`.
     *
     * @param int $index
     *
     * @return bool
     */
    public function isWhilePartOfDoWhile($index)
    {
        $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);
    }

    /**
     * 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 $index classy index
     *
     * @return array
     */
    private function findClassyElements($index)
    {
        $elements = [];
        $curlyBracesLevel = 0;
        $bracesLevel = 0;
        $classIndex = $index;
        ++$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->isClassy()) { // anonymous class in class
                list($index, $newElements) = $this->findClassyElements($index);
                $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] = [
                    'token' => $token,
                    'type' => 'property',
                    'classIndex' => $classIndex,
                ];

                continue;
            }

            if ($token->isGivenKind(T_FUNCTION)) {
                $elements[$index] = [
                    'token' => $token,
                    'type' => 'method',
                    'classIndex' => $classIndex,
                ];
            } elseif ($token->isGivenKind(T_CONST)) {
                $elements[$index] = [
                    'token' => $token,
                    'type' => 'const',
                    'classIndex' => $classIndex,
                ];
            }
        }

        return [$index, $elements];
    }
}


<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Fixer\FunctionNotation;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\VersionSpecification;
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Gregor Harlan
 */
final class CombineNestedDirnameFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0.',
            [
                new VersionSpecificCodeSample(
                    "<?php\ndirname(dirname(dirname(\$path)));\n",
                    new VersionSpecification(70000)
                ),
            ],
            null,
            'Risky when the function `dirname` is overridden.'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return \PHP_VERSION_ID >= 70000 && $tokens->isTokenKindFound(T_STRING);
    }

    /**
     * {@inheritdoc}
     */
    public function isRisky()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // should run after DirConstantFixer
        // should run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer
        return 3;
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
            $token = $tokens[$index];

            if (!$token->equals([T_STRING, 'dirname'], false)) {
                continue;
            }

            $dirnameInfo = $this->getDirnameInfo($tokens, $index);

            if (!$dirnameInfo) {
                continue;
            }

            $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]);

            if (!$tokens[$prev]->equals('(')) {
                continue;
            }

            $prev = $tokens->getPrevMeaningfulToken($prev);

            $firstArgumentEnd = $dirnameInfo['end'];

            $dirnameInfoArray = [$dirnameInfo];

            while ($dirnameInfo = $this->getDirnameInfo($tokens, $prev, $firstArgumentEnd)) {
                $dirnameInfoArray[] = $dirnameInfo;

                $prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]);

                if (!$tokens[$prev]->equals('(')) {
                    break;
                }

                $prev = $tokens->getPrevMeaningfulToken($prev);
                $firstArgumentEnd = $dirnameInfo['end'];
            }

            if (\count($dirnameInfoArray) > 1) {
                $this->combineDirnames($tokens, $dirnameInfoArray);
            }

            $index = $prev;
        }
    }

    /**
     * @param Tokens   $tokens
     * @param int      $index                 Index of `dirname`
     * @param null|int $firstArgumentEndIndex Index of last token of first argument of `dirname` call
     *
     * @return array|bool `false` when it is not a (supported) `dirname` call, an array with info about the dirname call otherwise
     */
    private function getDirnameInfo(Tokens $tokens, $index, $firstArgumentEndIndex = null)
    {
        if (!$tokens[$index]->equals([T_STRING, 'dirname'], false)) {
            return false;
        }

        if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index)) {
            return false;
        }

        $info['indexes'] = [];

        $prev = $tokens->getPrevMeaningfulToken($index);

        if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) {
            $info['indexes'][] = $prev;
        }

        $info['indexes'][] = $index;

        // opening parenthesis "("
        $next = $tokens->getNextMeaningfulToken($index);
        $info['indexes'][] = $next;

        if (null !== $firstArgumentEndIndex) {
            $next = $tokens->getNextMeaningfulToken($firstArgumentEndIndex);
        } else {
            $next = $tokens->getNextMeaningfulToken($next);

            if ($tokens[$next]->equals(')')) {
                return false;
            }

            while (!$tokens[$next]->equalsAny([',', ')'])) {
                $blockType = Tokens::detectBlockType($tokens[$next]);

                if ($blockType) {
                    $next = $tokens->findBlockEnd($blockType['type'], $next);
                }

                $next = $tokens->getNextMeaningfulToken($next);
            }
        }

        $info['indexes'][] = $next;

        if ($tokens[$next]->equals(',')) {
            $next = $tokens->getNextMeaningfulToken($next);
            $info['indexes'][] = $next;
        }

        if ($tokens[$next]->equals(')')) {
            $info['levels'] = 1;
            $info['end'] = $next;

            return $info;
        }

        if (!$tokens[$next]->isGivenKind(T_LNUMBER)) {
            return false;
        }

        $info['secondArgument'] = $next;
        $info['levels'] = (int) $tokens[$next]->getContent();

        $next = $tokens->getNextMeaningfulToken($next);

        if ($tokens[$next]->equals(',')) {
            $info['indexes'][] = $next;
            $next = $tokens->getNextMeaningfulToken($next);
        }

        if (!$tokens[$next]->equals(')')) {
            return false;
        }

        $info['indexes'][] = $next;
        $info['end'] = $next;

        return $info;
    }

    private function combineDirnames(Tokens $tokens, array $dirnameInfoArray)
    {
        $outerDirnameInfo = array_pop($dirnameInfoArray);
        $levels = $outerDirnameInfo['levels'];

        foreach ($dirnameInfoArray as $dirnameInfo) {
            $levels += $dirnameInfo['levels'];

            foreach ($dirnameInfo['indexes'] as $index) {
                $tokens->removeLeadingWhitespace($index);
                $tokens->clearTokenAndMergeSurroundingWhitespace($index);
            }
        }

        $levelsToken = new Token([T_LNUMBER, (string) $levels]);

        if (isset($outerDirnameInfo['secondArgument'])) {
            $tokens[$outerDirnameInfo['secondArgument']] = $levelsToken;
        } else {
            $prev = $tokens->getPrevMeaningfulToken($outerDirnameInfo['end']);
            $items = [];
            if (!$tokens[$prev]->equals(',')) {
                $items[] = new Token(',');
            }
            $items[] = $levelsToken;
            $tokens->insertAt($outerDirnameInfo['end'], $items);
        }
    }
}