<?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\Phpdoc;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\DocBlock\Annotation;
use PhpCsFixer\DocBlock\DocBlock;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Utils;

final class PhpdocTypesOrderFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Sorts PHPDoc types.',
            [
                new CodeSample(
                    '<?php
/**
 * @param string|null $bar
 */
'
                ),
                new CodeSample(
                    '<?php
/**
 * @param null|string $bar
 */
',
                    ['null_adjustment' => 'always_last']
                ),
                new CodeSample(
                    '<?php
/**
 * @param null|string|int|\Foo $bar
 */
',
                    ['sort_algorithm' => 'alpha']
                ),
                new CodeSample(
                    '<?php
/**
 * @param null|string|int|\Foo $bar
 */
',
                    [
                        'sort_algorithm' => 'alpha',
                        'null_adjustment' => 'always_last',
                    ]
                ),
                new CodeSample(
                    '<?php
/**
 * @param null|string|int|\Foo $bar
 */
',
                    [
                        'sort_algorithm' => 'alpha',
                        'null_adjustment' => 'none',
                    ]
                ),
            ]
        );
    }

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

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolver([
            (new FixerOptionBuilder('sort_algorithm', 'The sorting algorithm to apply.'))
                ->setAllowedValues(['alpha', 'none'])
                ->setDefault('alpha')
                ->getOption(),
            (new FixerOptionBuilder('null_adjustment', 'Forces the position of `null` (overrides `sort_algorithm`).'))
                ->setAllowedValues(['always_first', 'always_last', 'none'])
                ->setDefault('always_first')
                ->getOption(),
        ]);
    }

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

            $doc = new DocBlock($token->getContent());
            $annotations = $doc->getAnnotationsOfType(Annotation::getTagsWithTypes());

            if (!\count($annotations)) {
                continue;
            }

            foreach ($annotations as $annotation) {
                $types = $annotation->getTypes();

                // fix main types
                $annotation->setTypes($this->sortTypes($types));

                // fix @method parameters types
                $line = $doc->getLine($annotation->getStart());
                $line->setContent(Preg::replaceCallback('/(@method\s+.+?\s+\w+\()(.*)\)/', function (array $matches) {
                    $sorted = Preg::replaceCallback('/((?:^|,)\s*)([^\s]+)/', function (array $matches) {
                        return $matches[1].$this->sortJoinedTypes($matches[2]);
                    }, $matches[2]);

                    return $matches[1].$sorted.')';
                }, $line->getContent()));
            }

            $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
        }
    }

    /**
     * @param string[] $types
     *
     * @return string[]
     */
    private function sortTypes(array $types)
    {
        foreach ($types as $index => $type) {
            $types[$index] = Preg::replaceCallback('/^([^<]+)<(?:([\w\|]+?)(,\s*))?(.*)>$/', function (array $matches) {
                return $matches[1].'<'.$this->sortJoinedTypes($matches[2]).$matches[3].$this->sortJoinedTypes($matches[4]).'>';
            }, $type);
        }

        if ('alpha' === $this->configuration['sort_algorithm']) {
            $types = Utils::stableSort(
                $types,
                static function ($type) { return $type; },
                static function ($typeA, $typeB) {
                    $regexp = '/^\\??\\\?/';

                    return strcasecmp(
                        Preg::replace($regexp, '', $typeA),
                        Preg::replace($regexp, '', $typeB)
                    );
                }
            );
        }

        if ('none' !== $this->configuration['null_adjustment']) {
            $nulls = [];
            foreach ($types as $index => $type) {
                if (Preg::match('/^\\\?null$/i', $type)) {
                    $nulls[$index] = $type;
                    unset($types[$index]);
                }
            }

            if (\count($nulls)) {
                if ('always_last' === $this->configuration['null_adjustment']) {
                    array_push($types, ...$nulls);
                } else {
                    array_unshift($types, ...$nulls);
                }
            }
        }

        return $types;
    }

    /**
     * @param string $types
     *
     * @return string
     */
    private function sortJoinedTypes($types)
    {
        $types = array_filter(
            Preg::split('/([^|<]+(?:<.*>)?)/', $types, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY),
            static function ($value) {
                return '|' !== $value;
            }
        );

        return implode('|', $this->sortTypes($types));
    }
}


<?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\StringNotation;

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

/**
 * @author Filippo Tessarotto <zoeslam@gmail.com>
 */
final class ExplicitStringVariableFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax.',
            [new CodeSample(
                <<<'EOT'
<?php
$a = "My name is $name !";
$b = "I live in $state->country !";
$c = "I have $farm[0] chickens !";

EOT
            )],
            'The reasoning behind this rule is the following:'
                ."\n".'- When there are two valid ways of doing the same thing, using both is confusing, there should be a coding standard to follow'
                ."\n".'- PHP manual marks `"$var"` syntax as implicit and `"${var}"` syntax as explicit: explicit code should always be preferred'
                ."\n".'- Explicit syntax allows word concatenation inside strings, e.g. `"${var}IsAVar"`, implicit doesn\'t'
                ."\n".'- Explicit syntax is easier to detect for IDE/editors and therefore has colors/hightlight with higher contrast, which is easier to read'
            ."\n".'Backtick operator is skipped because it is harder to handle; you can use `backtick_to_shell_exec` fixer to normalize backticks to strings'
        );
    }

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

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $backtickStarted = false;
        for ($index = \count($tokens) - 1; $index > 0; --$index) {
            $token = $tokens[$index];
            if ($token->equals('`')) {
                $backtickStarted = !$backtickStarted;

                continue;
            }

            if ($backtickStarted || !$token->isGivenKind(T_VARIABLE)) {
                continue;
            }

            $prevToken = $tokens[$index - 1];
            if (!$this->isStringPartToken($prevToken)) {
                continue;
            }

            $distinctVariableIndex = $index;
            $variableTokens = [
                $distinctVariableIndex => [
                    'tokens' => [$index => $token],
                    'firstVariableTokenIndex' => $index,
                    'lastVariableTokenIndex' => $index,
                ],
            ];

            $nextIndex = $index + 1;
            $squareBracketCount = 0;
            while (!$this->isStringPartToken($tokens[$nextIndex])) {
                if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE) && 1 !== $squareBracketCount) {
                    $distinctVariableIndex = $nextIndex;
                    $variableTokens[$distinctVariableIndex] = [
                        'tokens' => [$nextIndex => $tokens[$nextIndex]],
                        'firstVariableTokenIndex' => $nextIndex,
                        'lastVariableTokenIndex' => $nextIndex,
                    ];
                } else {
                    $variableTokens[$distinctVariableIndex]['tokens'][$nextIndex] = $tokens[$nextIndex];
                    $variableTokens[$distinctVariableIndex]['lastVariableTokenIndex'] = $nextIndex;

                    if ($tokens[$nextIndex]->equalsAny(['[', ']'])) {
                        ++$squareBracketCount;
                    }
                }

                ++$nextIndex;
            }
            krsort($variableTokens, \SORT_NUMERIC);

            foreach ($variableTokens as $distinctVariableSet) {
                if (1 === \count($distinctVariableSet['tokens'])) {
                    $singleVariableIndex = key($distinctVariableSet['tokens']);
                    $singleVariableToken = current($distinctVariableSet['tokens']);
                    $tokens->overrideRange($singleVariableIndex, $singleVariableIndex, [
                        new Token([T_DOLLAR_OPEN_CURLY_BRACES, '${']),
                        new Token([T_STRING_VARNAME, substr($singleVariableToken->getContent(), 1)]),
                        new Token([CT::T_DOLLAR_CLOSE_CURLY_BRACES, '}']),
                    ]);
                } else {
                    foreach ($distinctVariableSet['tokens'] as $variablePartIndex => $variablePartToken) {
                        if ($variablePartToken->isGivenKind(T_NUM_STRING)) {
                            $tokens[$variablePartIndex] = new Token([T_LNUMBER, $variablePartToken->getContent()]);

                            continue;
                        }

                        if ($variablePartToken->isGivenKind(T_STRING) && $tokens[$variablePartIndex + 1]->equals(']')) {
                            $tokens[$variablePartIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$variablePartToken->getContent()."'"]);
                        }
                    }

                    $tokens->insertAt($distinctVariableSet['lastVariableTokenIndex'] + 1, new Token([CT::T_CURLY_CLOSE, '}']));
                    $tokens->insertAt($distinctVariableSet['firstVariableTokenIndex'], new Token([T_CURLY_OPEN, '{']));
                }
            }
        }
    }

    /**
     * Check if token is a part of a string.
     *
     * @param Token $token The token to check
     *
     * @return bool
     */
    private function isStringPartToken(Token $token)
    {
        return $token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)
            || $token->isGivenKind(T_START_HEREDOC)
            || '"' === $token->getContent()
            || 'b"' === strtolower($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\FixerDefinition;

/**
 * @author Andreas Möller <am@localheinz.com>
 */
final class VersionSpecification implements VersionSpecificationInterface
{
    /**
     * @var null|int
     */
    private $minimum;

    /**
     * @var null|int
     */
    private $maximum;

    /**
     * @param null|int $minimum
     * @param null|int $maximum
     *
     * @throws \InvalidArgumentException
     */
    public function __construct($minimum = null, $maximum = null)
    {
        if (null === $minimum && null === $maximum) {
            throw new \InvalidArgumentException('Minimum or maximum need to be specified.');
        }

        if (null !== $minimum && (!\is_int($minimum) || 1 > $minimum)) {
            throw new \InvalidArgumentException('Minimum needs to be either null or an integer greater than 0.');
        }

        if (null !== $maximum) {
            if (!\is_int($maximum) || 1 > $maximum) {
                throw new \InvalidArgumentException('Maximum needs to be either null or an integer greater than 0.');
            }

            if (null !== $minimum && $maximum < $minimum) {
                throw new \InvalidArgumentException('Maximum should not be lower than the minimum.');
            }
        }

        $this->minimum = $minimum;
        $this->maximum = $maximum;
    }

    /**
     * {@inheritdoc}
     */
    public function isSatisfiedBy($version)
    {
        if (null !== $this->minimum && $version < $this->minimum) {
            return false;
        }

        if (null !== $this->maximum && $version > $this->maximum) {
            return false;
        }

        return true;
    }
}


<?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\Console\SelfUpdate;

use Composer\Semver\Comparator;
use Composer\Semver\Semver;
use Composer\Semver\VersionParser;

/**
 * @internal
 */
final class NewVersionChecker implements NewVersionCheckerInterface
{
    /**
     * @var GithubClientInterface
     */
    private $githubClient;

    /**
     * @var VersionParser
     */
    private $versionParser;

    /**
     * @var null|string[]
     */
    private $availableVersions;

    /**
     * @param GithubClientInterface $githubClient
     */
    public function __construct(GithubClientInterface $githubClient)
    {
        $this->githubClient = $githubClient;
        $this->versionParser = new VersionParser();
    }

    /**
     * {@inheritdoc}
     */
    public function getLatestVersion()
    {
        $this->retrieveAvailableVersions();

        return $this->availableVersions[0];
    }

    /**
     * {@inheritdoc}
     */
    public function getLatestVersionOfMajor($majorVersion)
    {
        $this->retrieveAvailableVersions();

        $semverConstraint = '^'.$majorVersion;

        foreach ($this->availableVersions as $availableVersion) {
            if (Semver::satisfies($availableVersion, $semverConstraint)) {
                return $availableVersion;
            }
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function compareVersions($versionA, $versionB)
    {
        $versionA = $this->versionParser->normalize($versionA);
        $versionB = $this->versionParser->normalize($versionB);

        if (Comparator::lessThan($versionA, $versionB)) {
            return -1;
        }

        if (Comparator::greaterThan($versionA, $versionB)) {
            return 1;
        }

        return 0;
    }

    private function retrieveAvailableVersions()
    {
        if (null !== $this->availableVersions) {
            return;
        }

        foreach ($this->githubClient->getTags() as $tag) {
            $version = $tag['name'];

            try {
                $this->versionParser->normalize($version);

                if ('stable' === VersionParser::parseStability($version)) {
                    $this->availableVersions[] = $version;
                }
            } catch (\UnexpectedValueException $exception) {
                // not a valid version tag
            }
        }

        $this->availableVersions = Semver::rsort($this->availableVersions);
    }
}


<?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\Phpdoc;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\DocBlock\DocBlock;
use PhpCsFixer\DocBlock\Line;
use PhpCsFixer\DocBlock\ShortDescription;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Nobu Funaki <nobu.funaki@gmail.com>
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 */
final class PhpdocTrimConsecutiveBlankLineSeparationFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Removes extra blank lines after summary and after description in PHPDoc.',
            [
                new CodeSample(
                    '<?php
/**
 * Summary.
 *
 *
 * Description that contain 4 lines,
 *
 *
 * while 2 of them are blank!
 *
 *
 * @param string $foo
 *
 *
 * @dataProvider provideFixCases
 */
function fnc($foo) {}
'
                ),
            ]
        );
    }

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

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

            $doc = new DocBlock($token->getContent());
            $summaryEnd = (new ShortDescription($doc))->getEnd();

            if (null !== $summaryEnd) {
                $this->fixSummary($doc, $summaryEnd);
                $this->fixDescription($doc, $summaryEnd);
            }

            $this->fixAllTheRest($doc);

            $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]);
        }
    }

    /**
     * @param DocBlock $doc
     * @param int      $summaryEnd
     */
    private function fixSummary(DocBlock $doc, $summaryEnd)
    {
        $nonBlankLineAfterSummary = $this->findNonBlankLine($doc, $summaryEnd);

        $this->removeExtraBlankLinesBetween($doc, $summaryEnd, $nonBlankLineAfterSummary);
    }

    /**
     * @param DocBlock $doc
     * @param int      $summaryEnd
     */
    private function fixDescription(DocBlock $doc, $summaryEnd)
    {
        $annotationStart = $this->findFirstAnnotationOrEnd($doc);

        // assuming the end of the Description appears before the first Annotation
        $descriptionEnd = $this->reverseFindLastUsefulContent($doc, $annotationStart);

        if (null === $descriptionEnd || $summaryEnd === $descriptionEnd) {
            return; // no Description
        }

        if ($annotationStart === \count($doc->getLines()) - 1) {
            return; // no content after Description
        }

        $this->removeExtraBlankLinesBetween($doc, $descriptionEnd, $annotationStart);
    }

    private function fixAllTheRest(DocBlock $doc)
    {
        $annotationStart = $this->findFirstAnnotationOrEnd($doc);
        $lastLine = $this->reverseFindLastUsefulContent($doc, \count($doc->getLines()) - 1);

        if (null !== $lastLine && $annotationStart !== $lastLine) {
            $this->removeExtraBlankLinesBetween($doc, $annotationStart, $lastLine);
        }
    }

    /**
     * @param DocBlock $doc
     * @param int      $from
     * @param int      $to
     */
    private function removeExtraBlankLinesBetween(DocBlock $doc, $from, $to)
    {
        for ($index = $from + 1; $index < $to; ++$index) {
            $line = $doc->getLine($index);
            $next = $doc->getLine($index + 1);
            $this->removeExtraBlankLine($line, $next);
        }
    }

    /**
     * @param Line $current
     * @param Line $next
     */
    private function removeExtraBlankLine(Line $current, Line $next)
    {
        if (!$current->isTheEnd() && !$current->containsUsefulContent()
            && !$next->isTheEnd() && !$next->containsUsefulContent()) {
            $current->remove();
        }
    }

    /**
     * @param DocBlock $doc
     * @param int      $after
     *
     * @return null|int
     */
    private function findNonBlankLine(DocBlock $doc, $after)
    {
        foreach ($doc->getLines() as $index => $line) {
            if ($index <= $after) {
                continue;
            }

            if ($line->containsATag() || $line->containsUsefulContent() || $line->isTheEnd()) {
                return $index;
            }
        }
    }

    /**
     * @param DocBlock $doc
     *
     * @return int
     */
    private function findFirstAnnotationOrEnd(DocBlock $doc)
    {
        $index = null;
        foreach ($doc->getLines() as $index => $line) {
            if ($line->containsATag()) {
                return $index;
            }
        }

        return $index; // no Annotation, return the last line
    }

    /**
     * @param DocBlock $doc
     * @param int      $from
     *
     * @return null|int
     */
    private function reverseFindLastUsefulContent(DocBlock $doc, $from)
    {
        for ($index = $from - 1; $index >= 0; --$index) {
            if ($doc->getLine($index)->containsUsefulContent()) {
                return $index;
            }
        }

        return 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\Basic;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\VersionSpecification;
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use Symfony\Component\OptionsResolver\Options;

/**
 * Removes Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.
 *
 * @author Ivan Boprzenkov <ivan.borzenkov@gmail.com>
 */
final class NonPrintableCharacterFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    private $symbolsReplace;

    private static $tokens = [
        T_STRING_VARNAME,
        T_INLINE_HTML,
        T_VARIABLE,
        T_COMMENT,
        T_ENCAPSED_AND_WHITESPACE,
        T_CONSTANT_ENCAPSED_STRING,
        T_DOC_COMMENT,
    ];

    public function __construct()
    {
        parent::__construct();

        $this->symbolsReplace = [
            pack('H*', 'e2808b') => ['', '200b'], // ZWSP U+200B
            pack('H*', 'e28087') => [' ', '2007'], // FIGURE SPACE U+2007
            pack('H*', 'e280af') => [' ', '202f'], // NBSP U+202F
            pack('H*', 'e281a0') => ['', '2060'], // WORD JOINER U+2060
            pack('H*', 'c2a0') => [' ', 'a0'], // NO-BREAK SPACE U+A0
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.',
            [
                new CodeSample(
                    '<?php echo "'.pack('H*', 'e2808b').'Hello'.pack('H*', 'e28087').'World'.pack('H*', 'c2a0')."!\";\n"
                ),
                new VersionSpecificCodeSample(
                    '<?php echo "'.pack('H*', 'e2808b').'Hello'.pack('H*', 'e28087').'World'.pack('H*', 'c2a0')."!\";\n",
                    new VersionSpecification(70000),
                    ['use_escape_sequences_in_strings' => true]
                ),
            ],
            null,
            'Risky when strings contain intended invisible characters.'
        );
    }

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

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

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolver([
            (new FixerOptionBuilder('use_escape_sequences_in_strings', 'Whether characters should be replaced with escape sequences in strings.'))
                ->setAllowedTypes(['bool'])
                ->setDefault(false) // @TODO 3.0 change to true
                ->setNormalizer(static function (Options $options, $value) {
                    if (\PHP_VERSION_ID < 70000 && $value) {
                        throw new InvalidOptionsForEnvException('Escape sequences require PHP 7.0+.');
                    }

                    return $value;
                })
                ->getOption(),
        ]);
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $replacements = [];
        $escapeSequences = [];
        foreach ($this->symbolsReplace as $character => list($replacement, $codepoint)) {
            $replacements[$character] = $replacement;
            $escapeSequences[$character] = '\u{'.$codepoint.'}';
        }

        foreach ($tokens as $index => $token) {
            $content = $token->getContent();

            if (
                $this->configuration['use_escape_sequences_in_strings']
                && $token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE])
            ) {
                if (!Preg::match('/'.implode('|', array_keys($escapeSequences)).'/', $content)) {
                    continue;
                }

                $previousToken = $tokens[$index - 1];
                $stringTypeChanged = false;

                if ($previousToken->isGivenKind(T_START_HEREDOC)) {
                    $previousTokenContent = $previousToken->getContent();

                    if (false !== strpos($previousTokenContent, '\'')) {
                        $tokens[$index - 1] = new Token([T_START_HEREDOC, str_replace('\'', '', $previousTokenContent)]);
                        $stringTypeChanged = true;
                    }
                } elseif ("'" === $content[0]) {
                    $content = Preg::replace('/^\'(.*)\'$/', '"$1"', $content);
                    $stringTypeChanged = true;
                }

                if ($stringTypeChanged) {
                    $content = Preg::replace('/([\\\\$])/', '\\\\$1', $content);
                }

                $tokens[$index] = new Token([$token->getId(), strtr($content, $escapeSequences)]);

                continue;
            }

            if ($token->isGivenKind(self::$tokens)) {
                $tokens[$index] = new Token([$token->getId(), strtr($content, $replacements)]);
            }
        }
    }
}


<?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\Alias;

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;

/**
 * @author Sullivan Senechal <soullivaneuh@gmail.com>
 * @author SpacePossum
 */
final class NoMixedEchoPrintFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    /**
     * @deprecated will be removed in 3.0
     */
    public static $defaultConfig = ['use' => 'echo'];

    /**
     * @var string
     */
    private $callBack;

    /**
     * @var int T_ECHO or T_PRINT
     */
    private $candidateTokenType;

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

        if ('echo' === $this->configuration['use']) {
            $this->candidateTokenType = T_PRINT;
            $this->callBack = 'fixPrintToEcho';
        } else {
            $this->candidateTokenType = T_ECHO;
            $this->callBack = 'fixEchoToPrint';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Either language construct `print` or `echo` should be used.',
            [
                new CodeSample("<?php print 'example';\n"),
                new CodeSample("<?php echo('example');\n", ['use' => 'print']),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // should run after NoShortEchoTagFixer.
        return -10;
    }

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

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

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolver([
            (new FixerOptionBuilder('use', 'The desired language construct.'))
                ->setAllowedValues(['print', 'echo'])
                ->setDefault('echo')
                ->getOption(),
        ]);
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     */
    private function fixEchoToPrint(Tokens $tokens, $index)
    {
        $nextTokenIndex = $tokens->getNextMeaningfulToken($index);
        $endTokenIndex = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
        $canBeConverted = true;

        for ($i = $nextTokenIndex; $i < $endTokenIndex; ++$i) {
            if ($tokens[$i]->equalsAny(['(', [CT::T_ARRAY_SQUARE_BRACE_OPEN]])) {
                $blockType = Tokens::detectBlockType($tokens[$i]);
                $i = $tokens->findBlockEnd($blockType['type'], $i);
            }

            if ($tokens[$i]->equals(',')) {
                $canBeConverted = false;

                break;
            }
        }

        if (false === $canBeConverted) {
            return;
        }

        $tokens[$index] = new Token([T_PRINT, 'print']);
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     */
    private function fixPrintToEcho(Tokens $tokens, $index)
    {
        $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];

        if (!$prevToken->equalsAny([';', '{', '}', [T_OPEN_TAG]])) {
            return;
        }

        $tokens[$index] = new Token([T_ECHO, 'echo']);
    }
}


<?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\Resolver;

use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @internal
 */
final class TypeShortNameResolver
{
    /**
     * This method will resolve the shortName of a FQCN if possible or otherwise return the inserted type name.
     * E.g.: use Foo\Bar => "Bar".
     *
     * @param Tokens $tokens
     * @param string $typeName
     *
     * @return string
     */
    public function resolve(Tokens $tokens, $typeName)
    {
        // First match explicit imports:
        $useMap = $this->getUseMapFromTokens($tokens);
        foreach ($useMap as $shortName => $fullName) {
            $regex = '/^\\\\?'.preg_quote($fullName, '/').'$/';
            if (Preg::match($regex, $typeName)) {
                return $shortName;
            }
        }

        // Next try to match (partial) classes inside the same namespace
        // For now only support one namespace per file:
        $namespaces = $this->getNamespacesFromTokens($tokens);
        if (1 === \count($namespaces)) {
            foreach ($namespaces as $fullName) {
                $matches = [];
                $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P<className>.+)$/';
                if (Preg::match($regex, $typeName, $matches)) {
                    return $matches['className'];
                }
            }
        }

        // Next: Try to match partial use statements:

        foreach ($useMap as $shortName => $fullName) {
            $matches = [];
            $regex = '/^\\\\?'.preg_quote($fullName, '/').'\\\\(?P<className>.+)$/';
            if (Preg::match($regex, $typeName, $matches)) {
                return $shortName.'\\'.$matches['className'];
            }
        }

        return $typeName;
    }

    /**
     * @param Tokens $tokens
     *
     * @return array<string, string> A list of all FQN namespaces in the file with the short name as key
     */
    private function getNamespacesFromTokens(Tokens $tokens)
    {
        return array_map(function (NamespaceAnalysis $info) {
            return $info->getFullName();
        }, (new NamespacesAnalyzer())->getDeclarations($tokens));
    }

    /**
     * @param Tokens $tokens
     *
     * @return array<string, string> A list of all FQN use statements in the file with the short name as key
     */
    private function getUseMapFromTokens(Tokens $tokens)
    {
        $map = [];

        foreach ((new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens) as $useDeclaration) {
            $map[$useDeclaration->getShortName()] = $useDeclaration->getFullName();
        }

        return $map;
    }
}


<?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\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;

/**
 * @author SpacePossum
 */
final class StaticLambdaFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Lambdas not (indirect) referencing `$this` must be declared `static`.',
            [new CodeSample("<?php\n\$a = function () use (\$b)\n{   echo \$b;\n};\n")],
            null,
            'Risky when using "->bindTo" on lambdas without referencing to `$this`.'
        );
    }

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

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

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $analyzer = new TokensAnalyzer($tokens);

        for ($index = $tokens->count() - 4; $index > 0; --$index) {
            if (!$tokens[$index]->isGivenKind(T_FUNCTION) || !$analyzer->isLambda($index)) {
                continue;
            }

            $prev = $tokens->getPrevMeaningfulToken($index);
            if ($tokens[$prev]->isGivenKind(T_STATIC)) {
                continue; // lambda is already 'static'
            }

            // figure out where the lambda starts ...
            $lambdaOpenIndex = $tokens->getNextTokenOfKind($index, ['{']);

            // ... and where it ends
            $lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex);

            if ($this->hasPossibleReferenceToThis($tokens, $lambdaOpenIndex, $lambdaEndIndex)) {
                continue;
            }

            // make the lambda static
            $tokens->insertAt(
                $index,
                [
                    new Token([T_STATIC, 'static']),
                    new Token([T_WHITESPACE, ' ']),
                ]
            );

            $index -= 4; // fixed after a lambda, closes candidate is at least 4 tokens before that
        }
    }

    /**
     * Returns 'true' if there is a possible reference to '$this' within the given tokens index range.
     *
     * @param Tokens $tokens
     * @param int    $startIndex
     * @param int    $endIndex
     *
     * @return bool
     */
    private function hasPossibleReferenceToThis(Tokens $tokens, $startIndex, $endIndex)
    {
        for ($i = $startIndex; $i < $endIndex; ++$i) {
            if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) {
                return true; // directly accessing '$this'
            }

            if ($tokens[$i]->isGivenKind([
                T_INCLUDE,                    // loading additional symbols we cannot analyze here
                T_INCLUDE_ONCE,               // "
                T_REQUIRE,                    // "
                T_REQUIRE_ONCE,               // "
                CT::T_DYNAMIC_VAR_BRACE_OPEN, // "$h = ${$g};" case
                T_EVAL,                       // "$c = eval('return $this;');" case
            ])) {
                return true;
            }

            if ($tokens[$i]->equals('$')) {
                $nextIndex = $tokens->getNextMeaningfulToken($i);
                if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) {
                    return true; // "$$a" case
                }
            }
        }

        return false;
    }
}


<?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\PhpTag;

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

/**
 * @author Ceeram <ceeram@cakephp.org>
 */
final class BlankLineAfterOpeningTagFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line.',
            [new CodeSample("<?php \$a = 1;\n\$b = 1;\n")]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // should be run before the NoBlankLinesBeforeNamespaceFixer
        return 1;
    }

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

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

        // ignore files with short open tag and ignore non-monolithic files
        if (!$tokens[0]->isGivenKind(T_OPEN_TAG) || !$tokens->isMonolithicPhp()) {
            return;
        }

        $newlineFound = false;
        /** @var Token $token */
        foreach ($tokens as $token) {
            if ($token->isWhitespace() && false !== strpos($token->getContent(), "\n")) {
                $newlineFound = true;

                break;
            }
        }

        // ignore one-line files
        if (!$newlineFound) {
            return;
        }

        $token = $tokens[0];

        if (false === strpos($token->getContent(), "\n")) {
            $tokens[0] = new Token([$token->getId(), rtrim($token->getContent()).$lineEnding]);
        }

        if (!$tokens[1]->isWhitespace() && false === strpos($tokens[1]->getContent(), "\n")) {
            $tokens->insertAt(1, new Token([T_WHITESPACE, $lineEnding]));
        }
    }
}


<?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\Report;

use PhpCsFixer\Differ\DiffConsoleFormatter;

/**
 * @author Boris Gorbylev <ekho@ekho.name>
 *
 * @internal
 */
final class TextReporter implements ReporterInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFormat()
    {
        return 'txt';
    }

    /**
     * {@inheritdoc}
     */
    public function generate(ReportSummary $reportSummary)
    {
        $output = '';

        $i = 0;
        foreach ($reportSummary->getChanged() as $file => $fixResult) {
            ++$i;
            $output .= sprintf('%4d) %s', $i, $file);

            if ($reportSummary->shouldAddAppliedFixers()) {
                $output .= $this->getAppliedFixers($reportSummary->isDecoratedOutput(), $fixResult);
            }

            $output .= $this->getDiff($reportSummary->isDecoratedOutput(), $fixResult);
            $output .= PHP_EOL;
        }

        return $output.$this->getFooter($reportSummary->getTime(), $reportSummary->getMemory(), $reportSummary->isDryRun());
    }

    /**
     * @param bool  $isDecoratedOutput
     * @param array $fixResult
     *
     * @return string
     */
    private function getAppliedFixers($isDecoratedOutput, array $fixResult)
    {
        return sprintf(
            $isDecoratedOutput ? ' (<comment>%s</comment>)' : ' (%s)',
            implode(', ', $fixResult['appliedFixers'])
        );
    }

    /**
     * @param bool  $isDecoratedOutput
     * @param array $fixResult
     *
     * @return string
     */
    private function getDiff($isDecoratedOutput, array $fixResult)
    {
        if (empty($fixResult['diff'])) {
            return '';
        }

        $diffFormatter = new DiffConsoleFormatter($isDecoratedOutput, sprintf(
            '<comment>      ---------- begin diff ----------</comment>%s%%s%s<comment>      ----------- end diff -----------</comment>',
            PHP_EOL,
            PHP_EOL
        ));

        return PHP_EOL.$diffFormatter->format($fixResult['diff']).PHP_EOL;
    }

    /**
     * @param int  $time
     * @param int  $memory
     * @param bool $isDryRun
     *
     * @return string
     */
    private function getFooter($time, $memory, $isDryRun)
    {
        if (0 === $time || 0 === $memory) {
            return '';
        }

        return PHP_EOL.sprintf(
            '%s all files in %.3f seconds, %.3f MB memory used'.PHP_EOL,
            $isDryRun ? 'Checked' : 'Fixed',
            $time / 1000,
            $memory / 1024 / 1024
        );
    }
}


<?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\Runner;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Cache\CacheManagerInterface;
use PhpCsFixer\Cache\Directory;
use PhpCsFixer\Cache\DirectoryInterface;
use PhpCsFixer\Differ\DifferInterface;
use PhpCsFixer\Error\Error;
use PhpCsFixer\Error\ErrorsManager;
use PhpCsFixer\FileReader;
use PhpCsFixer\Fixer\FixerInterface;
use PhpCsFixer\FixerFileProcessedEvent;
use PhpCsFixer\Linter\LinterInterface;
use PhpCsFixer\Linter\LintingException;
use PhpCsFixer\Linter\LintingResultInterface;
use PhpCsFixer\Tokenizer\Tokens;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Filesystem\Exception\IOException;

/**
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 */
final class Runner
{
    /**
     * @var DifferInterface
     */
    private $differ;

    /**
     * @var DirectoryInterface
     */
    private $directory;

    /**
     * @var null|EventDispatcherInterface
     */
    private $eventDispatcher;

    /**
     * @var ErrorsManager
     */
    private $errorsManager;

    /**
     * @var CacheManagerInterface
     */
    private $cacheManager;

    /**
     * @var bool
     */
    private $isDryRun;

    /**
     * @var LinterInterface
     */
    private $linter;

    /**
     * @var \Traversable
     */
    private $finder;

    /**
     * @var FixerInterface[]
     */
    private $fixers;

    /**
     * @var bool
     */
    private $stopOnViolation;

    public function __construct(
        $finder,
        array $fixers,
        DifferInterface $differ,
        EventDispatcherInterface $eventDispatcher = null,
        ErrorsManager $errorsManager,
        LinterInterface $linter,
        $isDryRun,
        CacheManagerInterface $cacheManager,
        DirectoryInterface $directory = null,
        $stopOnViolation = false
    ) {
        $this->finder = $finder;
        $this->fixers = $fixers;
        $this->differ = $differ;
        $this->eventDispatcher = $eventDispatcher;
        $this->errorsManager = $errorsManager;
        $this->linter = $linter;
        $this->isDryRun = $isDryRun;
        $this->cacheManager = $cacheManager;
        $this->directory = $directory ?: new Directory('');
        $this->stopOnViolation = $stopOnViolation;
    }

    /**
     * @return array
     */
    public function fix()
    {
        $changed = [];

        $finder = $this->finder;
        $finderIterator = $finder instanceof \IteratorAggregate ? $finder->getIterator() : $finder;
        $fileFilteredFileIterator = new FileFilterIterator(
            $finderIterator,
            $this->eventDispatcher,
            $this->cacheManager
        );

        $collection = $this->linter->isAsync()
            ? new FileCachingLintingIterator($fileFilteredFileIterator, $this->linter)
            : new FileLintingIterator($fileFilteredFileIterator, $this->linter);

        foreach ($collection as $file) {
            $fixInfo = $this->fixFile($file, $collection->currentLintingResult());

            // we do not need Tokens to still caching just fixed file - so clear the cache
            Tokens::clearCache();

            if ($fixInfo) {
                $name = $this->directory->getRelativePathTo($file);
                $changed[$name] = $fixInfo;

                if ($this->stopOnViolation) {
                    break;
                }
            }
        }

        return $changed;
    }

    private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult)
    {
        $name = $file->getPathname();

        try {
            $lintingResult->check();
        } catch (LintingException $e) {
            $this->dispatchEvent(
                FixerFileProcessedEvent::NAME,
                new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_INVALID)
            );

            $this->errorsManager->report(new Error(Error::TYPE_INVALID, $name, $e));

            return;
        }

        $old = FileReader::createSingleton()->read($file->getRealPath());

        Tokens::setLegacyMode(false);

        $tokens = Tokens::fromCode($old);
        $oldHash = $tokens->getCodeHash();

        $newHash = $oldHash;
        $new = $old;

        $appliedFixers = [];

        try {
            foreach ($this->fixers as $fixer) {
                // for custom fixers we don't know is it safe to run `->fix()` without checking `->supports()` and `->isCandidate()`,
                // thus we need to check it and conditionally skip fixing
                if (
                    !$fixer instanceof AbstractFixer &&
                    (!$fixer->supports($file) || !$fixer->isCandidate($tokens))
                ) {
                    continue;
                }

                $fixer->fix($file, $tokens);

                if ($tokens->isChanged()) {
                    $tokens->clearEmptyTokens();
                    $tokens->clearChanged();
                    $appliedFixers[] = $fixer->getName();
                }
            }
        } catch (\Exception $e) {
            $this->processException($name, $e);

            return;
        } catch (\ParseError $e) {
            $this->dispatchEvent(
                FixerFileProcessedEvent::NAME,
                new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT)
            );

            $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e));

            return;
        } catch (\Throwable $e) {
            $this->processException($name, $e);

            return;
        }

        $fixInfo = null;

        if (!empty($appliedFixers)) {
            $new = $tokens->generateCode();
            $newHash = $tokens->getCodeHash();
        }

        // We need to check if content was changed and then applied changes.
        // But we can't simple check $appliedFixers, because one fixer may revert
        // work of other and both of them will mark collection as changed.
        // Therefore we need to check if code hashes changed.
        if ($oldHash !== $newHash) {
            $fixInfo = [
                'appliedFixers' => $appliedFixers,
                'diff' => $this->differ->diff($old, $new),
            ];

            try {
                $this->linter->lintSource($new)->check();
            } catch (LintingException $e) {
                $this->dispatchEvent(
                    FixerFileProcessedEvent::NAME,
                    new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT)
                );

                $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e, $fixInfo['appliedFixers'], $fixInfo['diff']));

                return;
            }

            if (!$this->isDryRun) {
                if (false === @file_put_contents($file->getRealPath(), $new)) {
                    $error = error_get_last();

                    throw new IOException(
                        sprintf('Failed to write file "%s", "%s".', $file->getPathname(), $error ? $error['message'] : 'no reason available'),
                        0,
                        null,
                        $file->getRealPath()
                    );
                }
            }
        }

        $this->cacheManager->setFile($name, $new);

        $this->dispatchEvent(
            FixerFileProcessedEvent::NAME,
            new FixerFileProcessedEvent($fixInfo ? FixerFileProcessedEvent::STATUS_FIXED : FixerFileProcessedEvent::STATUS_NO_CHANGES)
        );

        return $fixInfo;
    }

    /**
     * Process an exception that occurred.
     *
     * @param string     $name
     * @param \Throwable $e
     */
    private function processException($name, $e)
    {
        $this->dispatchEvent(
            FixerFileProcessedEvent::NAME,
            new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_EXCEPTION)
        );

        $this->errorsManager->report(new Error(Error::TYPE_EXCEPTION, $name, $e));
    }

    /**
     * @param string $name
     * @param Event  $event
     */
    private function dispatchEvent($name, Event $event)
    {
        if (null === $this->eventDispatcher) {
            return;
        }

        $this->eventDispatcher->dispatch($name, $event);
    }
}


<?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\ClassNotation;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\VersionSpecification;
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;
use Symfony\Component\OptionsResolver\Options;

/**
 * Fixer for rules defined in PSR2 ¶4.3, ¶4.5.
 *
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 * @author SpacePossum
 */
final class VisibilityRequiredFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility.',
            [
                new CodeSample(
                    '<?php
class Sample
{
    var $a;
    static protected $var_foo2;

    function A()
    {
    }
}
'
                ),
                new VersionSpecificCodeSample(
                    '<?php
class Sample
{
    const SAMPLE = 1;
}
',
                    new VersionSpecification(70100),
                    ['elements' => ['const']]
                ),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
    }

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolverRootless('elements', [
            (new FixerOptionBuilder('elements', 'The structural elements to fix (PHP >= 7.1 required for `const`).'))
                ->setAllowedTypes(['array'])
                ->setAllowedValues([new AllowedValueSubset(['property', 'method', 'const'])])
                ->setNormalizer(static function (Options $options, $value) {
                    if (\PHP_VERSION_ID < 70100 && \in_array('const', $value, true)) {
                        throw new InvalidOptionsForEnvException('"const" option can only be enabled with PHP 7.1+.');
                    }

                    return $value;
                })
                ->setDefault(['property', 'method'])
                ->getOption(),
        ], $this->getName());
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $tokensAnalyzer = new TokensAnalyzer($tokens);
        $elements = $tokensAnalyzer->getClassyElements();

        foreach (array_reverse($elements, true) as $index => $element) {
            if (!\in_array($element['type'], $this->configuration['elements'], true)) {
                continue;
            }

            $abstractFinalIndex = null;
            $visibilityIndex = null;
            $staticIndex = null;
            $prevIndex = $tokens->getPrevMeaningfulToken($index);
            while ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR])) {
                if ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL])) {
                    $abstractFinalIndex = $prevIndex;
                } elseif ($tokens[$prevIndex]->isGivenKind(T_STATIC)) {
                    $staticIndex = $prevIndex;
                } else {
                    $visibilityIndex = $prevIndex;
                }
                $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
            }

            if ($tokens[$prevIndex]->equals(',')) {
                continue;
            }

            if (null !== $staticIndex) {
                if ($this->isKeywordPlacedProperly($tokens, $staticIndex, $index)) {
                    $index = $staticIndex;
                } else {
                    $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $staticIndex, $index);
                }
            }

            if (null === $visibilityIndex) {
                $tokens->insertAt($index, [new Token([T_PUBLIC, 'public']), new Token([T_WHITESPACE, ' '])]);
            } else {
                if ($tokens[$visibilityIndex]->isGivenKind(T_VAR)) {
                    $tokens[$visibilityIndex] = new Token([T_PUBLIC, 'public']);
                }
                if ($this->isKeywordPlacedProperly($tokens, $visibilityIndex, $index)) {
                    $index = $visibilityIndex;
                } else {
                    $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $visibilityIndex, $index);
                }
            }

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

            if ($this->isKeywordPlacedProperly($tokens, $abstractFinalIndex, $index)) {
                continue;
            }

            $this->moveTokenAndEnsureSingleSpaceFollows($tokens, $abstractFinalIndex, $index);
        }
    }

    /**
     * @param Tokens $tokens
     * @param int    $keywordIndex
     * @param int    $comparedIndex
     *
     * @return bool
     */
    private function isKeywordPlacedProperly(Tokens $tokens, $keywordIndex, $comparedIndex)
    {
        return $keywordIndex + 2 === $comparedIndex && ' ' === $tokens[$keywordIndex + 1]->getContent();
    }

    /**
     * @param Tokens $tokens
     * @param int    $fromIndex
     * @param int    $toIndex
     */
    private function moveTokenAndEnsureSingleSpaceFollows(Tokens $tokens, $fromIndex, $toIndex)
    {
        $tokens->insertAt($toIndex, [$tokens[$fromIndex], new Token([T_WHITESPACE, ' '])]);

        $tokens->clearAt($fromIndex);
        if ($tokens[$fromIndex + 1]->isWhitespace()) {
            $tokens->clearAt($fromIndex + 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\Fixer\ClassNotation;

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

/**
 * @author Matteo Beccati <matteo@beccati.com>
 */
final class NoPhp4ConstructorFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Convert PHP4-style constructors to `__construct`.',
            [
                new CodeSample('<?php
class Foo
{
    public function Foo($bar)
    {
    }
}
'),
            ],
            null,
            'Risky when old style constructor being fixed is overridden or overrides parent one.'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // must run before OrderedClassElementsFixer
        return 75;
    }

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

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

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $tokensAnalyzer = new TokensAnalyzer($tokens);
        $classes = array_keys($tokens->findGivenKind(T_CLASS));
        $numClasses = \count($classes);

        for ($i = 0; $i < $numClasses; ++$i) {
            $index = $classes[$i];

            // is it an an anonymous class definition?
            if ($tokensAnalyzer->isAnonymousClass($index)) {
                continue;
            }

            // is it inside a namespace?
            $nspIndex = $tokens->getPrevTokenOfKind($index, [[T_NAMESPACE, 'namespace']]);
            if (null !== $nspIndex) {
                $nspIndex = $tokens->getNextMeaningfulToken($nspIndex);

                // make sure it's not the global namespace, as PHP4 constructors are allowed in there
                if (!$tokens[$nspIndex]->equals('{')) {
                    // unless it's the global namespace, the index currently points to the name
                    $nspIndex = $tokens->getNextTokenOfKind($nspIndex, [';', '{']);

                    if ($tokens[$nspIndex]->equals(';')) {
                        // the class is inside a (non-block) namespace, no PHP4-code should be in there
                        break;
                    }

                    // the index points to the { of a block-namespace
                    $nspEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nspIndex);
                    if ($index < $nspEnd) {
                        // the class is inside a block namespace, skip other classes that might be in it
                        for ($j = $i + 1; $j < $numClasses; ++$j) {
                            if ($classes[$j] < $nspEnd) {
                                ++$i;
                            }
                        }
                        // and continue checking the classes that might follow
                        continue;
                    }
                }
            }

            $classNameIndex = $tokens->getNextMeaningfulToken($index);
            $className = $tokens[$classNameIndex]->getContent();
            $classStart = $tokens->getNextTokenOfKind($classNameIndex, ['{']);
            $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart);

            $this->fixConstructor($tokens, $className, $classStart, $classEnd);
            $this->fixParent($tokens, $classStart, $classEnd);
        }
    }

    /**
     * Fix constructor within a class, if possible.
     *
     * @param Tokens $tokens     the Tokens instance
     * @param string $className  the class name
     * @param int    $classStart the class start index
     * @param int    $classEnd   the class end index
     */
    private function fixConstructor(Tokens $tokens, $className, $classStart, $classEnd)
    {
        $php4 = $this->findFunction($tokens, $className, $classStart, $classEnd);

        if (null === $php4) {
            // no PHP4-constructor!
            return;
        }

        if (!empty($php4['modifiers'][T_ABSTRACT]) || !empty($php4['modifiers'][T_STATIC])) {
            // PHP4 constructor can't be abstract or static
            return;
        }

        $php5 = $this->findFunction($tokens, '__construct', $classStart, $classEnd);

        if (null === $php5) {
            // no PHP5-constructor, we can rename the old one to __construct
            $tokens[$php4['nameIndex']] = new Token([T_STRING, '__construct']);

            // in some (rare) cases we might have just created an infinite recursion issue
            $this->fixInfiniteRecursion($tokens, $php4['bodyIndex'], $php4['endIndex']);

            return;
        }

        // does the PHP4-constructor only call $this->__construct($args, ...)?
        list($seq, $case) = $this->getWrapperMethodSequence($tokens, '__construct', $php4['startIndex'], $php4['bodyIndex']);
        if (null !== $tokens->findSequence($seq, $php4['bodyIndex'] - 1, $php4['endIndex'], $case)) {
            // good, delete it!
            for ($i = $php4['startIndex']; $i <= $php4['endIndex']; ++$i) {
                $tokens->clearAt($i);
            }

            return;
        }

        // does __construct only call the PHP4-constructor (with the same args)?
        list($seq, $case) = $this->getWrapperMethodSequence($tokens, $className, $php4['startIndex'], $php4['bodyIndex']);
        if (null !== $tokens->findSequence($seq, $php5['bodyIndex'] - 1, $php5['endIndex'], $case)) {
            // that was a weird choice, but we can safely delete it and...
            for ($i = $php5['startIndex']; $i <= $php5['endIndex']; ++$i) {
                $tokens->clearAt($i);
            }
            // rename the PHP4 one to __construct
            $tokens[$php4['nameIndex']] = new Token([T_STRING, '__construct']);
        }
    }

    /**
     * Fix calls to the parent constructor within a class.
     *
     * @param Tokens $tokens     the Tokens instance
     * @param int    $classStart the class start index
     * @param int    $classEnd   the class end index
     */
    private function fixParent(Tokens $tokens, $classStart, $classEnd)
    {
        // check calls to the parent constructor
        foreach ($tokens->findGivenKind(T_EXTENDS) as $index => $token) {
            $parentIndex = $tokens->getNextMeaningfulToken($index);
            $parentClass = $tokens[$parentIndex]->getContent();

            // using parent::ParentClassName() or ParentClassName::ParentClassName()
            $parentSeq = $tokens->findSequence([
                [T_STRING],
                [T_DOUBLE_COLON],
                [T_STRING, $parentClass],
                '(',
            ], $classStart, $classEnd, [2 => false]);

            if (null !== $parentSeq) {
                // we only need indexes
                $parentSeq = array_keys($parentSeq);

                // match either of the possibilities
                if ($tokens[$parentSeq[0]]->equalsAny([[T_STRING, 'parent'], [T_STRING, $parentClass]], false)) {
                    // replace with parent::__construct
                    $tokens[$parentSeq[0]] = new Token([T_STRING, 'parent']);
                    $tokens[$parentSeq[2]] = new Token([T_STRING, '__construct']);
                }
            }

            // using $this->ParentClassName()
            $parentSeq = $tokens->findSequence([
                [T_VARIABLE, '$this'],
                [T_OBJECT_OPERATOR],
                [T_STRING, $parentClass],
                '(',
            ], $classStart, $classEnd, [2 => false]);

            if (null !== $parentSeq) {
                // we only need indexes
                $parentSeq = array_keys($parentSeq);

                // replace call with parent::__construct()
                $tokens[$parentSeq[0]] = new Token([
                    T_STRING,
                    'parent',
                ]);
                $tokens[$parentSeq[1]] = new Token([
                    T_DOUBLE_COLON,
                    '::',
                ]);
                $tokens[$parentSeq[2]] = new Token([T_STRING, '__construct']);
            }
        }
    }

    /**
     * Fix a particular infinite recursion issue happening when the parent class has __construct and the child has only
     * a PHP4 constructor that calls the parent constructor as $this->__construct().
     *
     * @param Tokens $tokens the Tokens instance
     * @param int    $start  the PHP4 constructor body start
     * @param int    $end    the PHP4 constructor body end
     */
    private function fixInfiniteRecursion(Tokens $tokens, $start, $end)
    {
        $seq = [
            [T_VARIABLE, '$this'],
            [T_OBJECT_OPERATOR],
            [T_STRING, '__construct'],
        ];

        while (true) {
            $callSeq = $tokens->findSequence($seq, $start, $end, [2 => false]);

            if (null === $callSeq) {
                return;
            }

            $callSeq = array_keys($callSeq);

            $tokens[$callSeq[0]] = new Token([T_STRING, 'parent']);
            $tokens[$callSeq[1]] = new Token([T_DOUBLE_COLON, '::']);
        }
    }

    /**
     * Generate the sequence of tokens necessary for the body of a wrapper method that simply
     * calls $this->{$method}( [args...] ) with the same arguments as its own signature.
     *
     * @param Tokens $tokens     the Tokens instance
     * @param string $method     the wrapped method name
     * @param int    $startIndex function/method start index
     * @param int    $bodyIndex  function/method body index
     *
     * @return array an array containing the sequence and case sensitiveness [ 0 => $seq, 1 => $case ]
     */
    private function getWrapperMethodSequence(Tokens $tokens, $method, $startIndex, $bodyIndex)
    {
        // initialise sequence as { $this->{$method}(
        $seq = [
            '{',
            [T_VARIABLE, '$this'],
            [T_OBJECT_OPERATOR],
            [T_STRING, $method],
            '(',
        ];
        $case = [3 => false];

        // parse method parameters, if any
        $index = $startIndex;
        while (true) {
            // find the next variable name
            $index = $tokens->getNextTokenOfKind($index, [[T_VARIABLE]]);

            if (null === $index || $index >= $bodyIndex) {
                // we've reached the body already
                break;
            }

            // append a comma if it's not the first variable
            if (\count($seq) > 5) {
                $seq[] = ',';
            }

            // append variable name to the sequence
            $seq[] = [T_VARIABLE, $tokens[$index]->getContent()];
        }

        // almost done, close the sequence with ); }
        $seq[] = ')';
        $seq[] = ';';
        $seq[] = '}';

        return [$seq, $case];
    }

    /**
     * Find a function or method matching a given name within certain bounds.
     *
     * @param Tokens $tokens     the Tokens instance
     * @param string $name       the function/Method name
     * @param int    $startIndex the search start index
     * @param int    $endIndex   the search end index
     *
     * @return null|array An associative array, if a match is found:
     *
     *     - nameIndex (int): The index of the function/method name.
     *     - startIndex (int): The index of the function/method start.
     *     - endIndex (int): The index of the function/method end.
     *     - bodyIndex (int): The index of the function/method body.
     *     - modifiers (array): The modifiers as array keys and their index as
     *       the values, e.g. array(T_PUBLIC => 10)
     */
    private function findFunction(Tokens $tokens, $name, $startIndex, $endIndex)
    {
        $function = $tokens->findSequence([
            [T_FUNCTION],
            [T_STRING, $name],
            '(',
        ], $startIndex, $endIndex, false);

        if (null === $function) {
            return null;
        }

        // keep only the indexes
        $function = array_keys($function);

        // find previous block, saving method modifiers for later use
        $possibleModifiers = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_ABSTRACT, T_FINAL];
        $modifiers = [];

        $prevBlock = $tokens->getPrevMeaningfulToken($function[0]);
        while (null !== $prevBlock && $tokens[$prevBlock]->isGivenKind($possibleModifiers)) {
            $modifiers[$tokens[$prevBlock]->getId()] = $prevBlock;
            $prevBlock = $tokens->getPrevMeaningfulToken($prevBlock);
        }

        if (isset($modifiers[T_ABSTRACT])) {
            // abstract methods have no body
            $bodyStart = null;
            $funcEnd = $tokens->getNextTokenOfKind($function[2], [';']);
        } else {
            // find method body start and the end of the function definition
            $bodyStart = $tokens->getNextTokenOfKind($function[2], ['{']);
            $funcEnd = null !== $bodyStart ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $bodyStart) : null;
        }

        return [
            'nameIndex' => $function[1],
            'startIndex' => $prevBlock + 1,
            'endIndex' => $funcEnd,
            'bodyIndex' => $bodyStart,
            'modifiers' => $modifiers,
        ];
    }
}


<?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\Whitespace;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * A file must always end with a line endings character.
 *
 * Fixer for rules defined in PSR2 ¶2.2.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 */
final class SingleBlankLineAtEofFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'A PHP file without end tag must always end with a single empty line feed.',
            [new CodeSample("<?php\n\$a = 1;")]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // must run last to be sure the file is properly formatted before it runs
        return -50;
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return true;
    }

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

        if ($count && !$tokens[$count - 1]->isGivenKind([T_INLINE_HTML, T_CLOSE_TAG, T_OPEN_TAG])) {
            $tokens->ensureWhitespaceAtIndex($count - 1, 1, $this->whitespacesConfig->getLineEnding());
        }
    }
}


<?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\LanguageConstruct;

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

/**
 * @author Gert de Pagter <BackEndTea@gmail.com>
 */
final class NoUnsetOnPropertyFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Properties should be set to `null` instead of using `unset`.',
            [new CodeSample("<?php\nunset(\$this->a);\n")],
            null,
            'Changing variables to `null` instead of unsetting them will mean they still show up '.
            'when looping over class variables.'
        );
    }

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

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isTokenKindFound(T_UNSET)
            && $tokens->isAnyTokenKindsFound([T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM]);
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // should be run before CombineConsecutiveUnsetsFixer
        return 25;
    }

    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
            if (!$tokens[$index]->isGivenKind(T_UNSET)) {
                continue;
            }

            $unsetsInfo = $this->getUnsetsInfo($tokens, $index);

            if (!$this->isAnyUnsetToTransform($unsetsInfo)) {
                continue;
            }

            $isLastUnset = true; // yes, last - we reverse the array below
            foreach (array_reverse($unsetsInfo) as $unsetInfo) {
                $this->updateTokens($tokens, $unsetInfo, $isLastUnset);
                $isLastUnset = false;
            }
        }
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     *
     * @return array<array<string, bool|int>>
     */
    private function getUnsetsInfo(Tokens $tokens, $index)
    {
        $argumentsAnalyzer = new ArgumentsAnalyzer();

        $unsetStart = $tokens->getNextTokenOfKind($index, ['(']);
        $unsetEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $unsetStart);
        $isFirst = true;

        $unsets = [];
        foreach ($argumentsAnalyzer->getArguments($tokens, $unsetStart, $unsetEnd) as $startIndex => $endIndex) {
            $startIndex = $tokens->getNextMeaningfulToken($startIndex - 1);
            $endIndex = $tokens->getPrevMeaningfulToken($endIndex + 1);
            $unsets[] = [
                'startIndex' => $startIndex,
                'endIndex' => $endIndex,
                'isToTransform' => $this->isProperty($tokens, $startIndex, $endIndex),
                'isFirst' => $isFirst,
            ];
            $isFirst = false;
        }

        return $unsets;
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     * @param int    $endIndex
     *
     * @return bool
     */
    private function isProperty(Tokens $tokens, $index, $endIndex)
    {
        if ($tokens[$index]->isGivenKind(T_VARIABLE)) {
            $nextIndex = $tokens->getNextMeaningfulToken($index);
            if (null === $nextIndex || !$tokens[$nextIndex]->isGivenKind(T_OBJECT_OPERATOR)) {
                return false;
            }
            $nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
            $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex);
            if (null !== $nextNextIndex && $nextNextIndex < $endIndex) {
                return false;
            }

            return null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_STRING);
        }

        if ($tokens[$index]->isGivenKind([T_NS_SEPARATOR, T_STRING])) {
            $nextIndex = $tokens->getTokenNotOfKindSibling($index, 1, [[T_DOUBLE_COLON], [T_NS_SEPARATOR], [T_STRING]]);
            $nextNextIndex = $tokens->getNextMeaningfulToken($nextIndex);
            if (null !== $nextNextIndex && $nextNextIndex < $endIndex) {
                return false;
            }

            return null !== $nextIndex && $tokens[$nextIndex]->isGivenKind(T_VARIABLE);
        }

        return false;
    }

    /**
     * @param array<array<string, bool|int>> $unsetsInfo
     *
     * @return bool
     */
    private function isAnyUnsetToTransform(array $unsetsInfo)
    {
        foreach ($unsetsInfo as $unsetInfo) {
            if ($unsetInfo['isToTransform']) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param Tokens                  $tokens
     * @param array<string, bool|int> $unsetInfo
     * @param bool                    $isLastUnset
     */
    private function updateTokens(Tokens $tokens, array $unsetInfo, $isLastUnset)
    {
        // if entry is first and to be transform we remove leading "unset("
        if ($unsetInfo['isFirst'] && $unsetInfo['isToTransform']) {
            $braceIndex = $tokens->getPrevTokenOfKind($unsetInfo['startIndex'], ['(']);
            $unsetIndex = $tokens->getPrevTokenOfKind($braceIndex, [[T_UNSET]]);
            $tokens->clearTokenAndMergeSurroundingWhitespace($braceIndex);
            $tokens->clearTokenAndMergeSurroundingWhitespace($unsetIndex);
        }

        // if entry is last and to be transformed we remove trailing ")"
        if ($isLastUnset && $unsetInfo['isToTransform']) {
            $braceIndex = $tokens->getNextTokenOfKind($unsetInfo['endIndex'], [')']);
            $previousIndex = $tokens->getPrevMeaningfulToken($braceIndex);
            if ($tokens[$previousIndex]->equals(',')) {
                $tokens->clearTokenAndMergeSurroundingWhitespace($previousIndex); // trailing ',' in function call (PHP 7.3)
            }

            $tokens->clearTokenAndMergeSurroundingWhitespace($braceIndex);
        }

        // if entry is not last we replace comma with semicolon (last entry already has semicolon - from original unset)
        if (!$isLastUnset) {
            $commaIndex = $tokens->getNextTokenOfKind($unsetInfo['endIndex'], [',']);
            $tokens[$commaIndex] = new Token(';');
        }

        // if entry is to be unset and is not last we add trailing ")"
        if (!$unsetInfo['isToTransform'] && !$isLastUnset) {
            $tokens->insertAt($unsetInfo['endIndex'] + 1, new Token(')'));
        }

        // if entry is to be unset and is not first we add leading "unset("
        if (!$unsetInfo['isToTransform'] && !$unsetInfo['isFirst']) {
            $tokens->insertAt(
                $unsetInfo['startIndex'],
                [
                    new Token([T_UNSET, 'unset']),
                    new Token('('),
                ]
            );
        }

        // and finally
        // if entry is to be transformed we add trailing " = null"
        if ($unsetInfo['isToTransform']) {
            $tokens->insertAt(
                $unsetInfo['endIndex'] + 1,
                [
                    new Token([T_WHITESPACE, ' ']),
                    new Token('='),
                    new Token([T_WHITESPACE, ' ']),
                    new Token([T_STRING, '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\FixerConfiguration;

use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class FixerConfigurationResolver implements FixerConfigurationResolverInterface
{
    /**
     * @var FixerOptionInterface[]
     */
    private $options = [];

    /**
     * @var string[]
     */
    private $registeredNames = [];

    /**
     * @param iterable<FixerOptionInterface> $options
     */
    public function __construct($options)
    {
        foreach ($options as $option) {
            $this->addOption($option);
        }

        if (empty($this->registeredNames)) {
            throw new \LogicException('Options cannot be empty.');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * {@inheritdoc}
     */
    public function resolve(array $options)
    {
        $resolver = new OptionsResolver();

        foreach ($this->options as $option) {
            $name = $option->getName();

            if ($option instanceof AliasedFixerOption) {
                $alias = $option->getAlias();

                if (\array_key_exists($alias, $options)) {
                    // @TODO 2.12 Trigger a deprecation notice and add a test for it
                    if (\array_key_exists($name, $options)) {
                        throw new InvalidOptionsException(sprintf('Aliased option %s/%s is passed multiple times.', $name, $alias));
                    }

                    $options[$name] = $options[$alias];
                    unset($options[$alias]);
                }
            }

            if ($option->hasDefault()) {
                $resolver->setDefault($name, $option->getDefault());
            } else {
                $resolver->setRequired($name);
            }

            $allowedValues = $option->getAllowedValues();
            if (null !== $allowedValues) {
                foreach ($allowedValues as &$allowedValue) {
                    if (\is_object($allowedValue) && \is_callable($allowedValue)) {
                        $allowedValue = function ($values) use ($allowedValue) {
                            return $allowedValue($values);
                        };
                    }
                }

                $resolver->setAllowedValues($name, $allowedValues);
            }

            $allowedTypes = $option->getAllowedTypes();
            if (null !== $allowedTypes) {
                $resolver->setAllowedTypes($name, $allowedTypes);
            }

            $normalizer = $option->getNormalizer();
            if (null !== $normalizer) {
                $resolver->setNormalizer($name, $normalizer);
            }
        }

        return $resolver->resolve($options);
    }

    /**
     * @param FixerOptionInterface $option
     *
     * @throws \LogicException when the option is already defined
     *
     * @return $this
     */
    private function addOption(FixerOptionInterface $option)
    {
        $name = $option->getName();

        if (\in_array($name, $this->registeredNames, true)) {
            throw new \LogicException(sprintf('The "%s" option is defined multiple times.', $name));
        }

        $this->options[] = $option;
        $this->registeredNames[] = $name;

        return $this;
    }
}


<?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\AbstractNoUselessElseFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

final class NoSuperfluousElseifFixer extends AbstractNoUselessElseFixer
{
    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isAnyTokenKindsFound([T_ELSE, T_ELSEIF]);
    }

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Replaces superfluous `elseif` with `if`.',
            [
                new CodeSample("<?php\nif (\$a) {\n    return 1;\n} elseif (\$b) {\n    return 2;\n}\n"),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        foreach ($tokens as $index => $token) {
            if ($this->isElseif($tokens, $index) && $this->isSuperfluousElse($tokens, $index)) {
                $this->convertElseifToIf($tokens, $index);
            }
        }
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     *
     * @return bool
     */
    private function isElseif(Tokens $tokens, $index)
    {
        if ($tokens[$index]->isGivenKind(T_ELSEIF)) {
            return true;
        }

        return $tokens[$index]->isGivenKind(T_ELSE) && $tokens[$tokens->getNextMeaningfulToken($index)]->isGivenKind(T_IF);
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     */
    private function convertElseifToIf(Tokens $tokens, $index)
    {
        if ($tokens[$index]->isGivenKind(T_ELSE)) {
            $tokens->clearTokenAndMergeSurroundingWhitespace($index);
        } else {
            $tokens[$index] = new Token([T_IF, 'if']);
        }

        $whitespace = '';
        for ($previous = $index - 1; $previous > 0; --$previous) {
            $token = $tokens[$previous];
            if ($token->isWhitespace() && Preg::match('/(\R[^\R]*)$/', $token->getContent(), $matches)) {
                $whitespace = $matches[1];

                break;
            }
        }

        $previousToken = $tokens[$index - 1];
        if (!$previousToken->isWhitespace()) {
            $tokens->insertAt($index, new Token([T_WHITESPACE, $whitespace]));
        } elseif (!Preg::match('/\R/', $previousToken->getContent())) {
            $tokens[$index - 1] = new Token([T_WHITESPACE, $whitespace]);
        }
    }
}


<?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\Alias;

use PhpCsFixer\AbstractFunctionReferenceFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author SpacePossum
 */
final class SetTypeToCastFixer extends AbstractFunctionReferenceFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Cast shall be used, not `settype`.',
            [
                new CodeSample(
                    '<?php
settype($foo, "integer");
settype($bar, "string");
settype($bar, "null");
'
                ),
            ],
            null,
            'Risky when the `settype` function is overridden or when used as the 2nd or 3rd expression in a `for` loop .'
        );
    }

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

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $map = [
            'array' => [T_ARRAY_CAST, '(array)'],
            'bool' => [T_BOOL_CAST, '(bool)'],
            'boolean' => [T_BOOL_CAST, '(bool)'],
            'double' => [T_DOUBLE_CAST, '(float)'],
            'float' => [T_DOUBLE_CAST, '(float)'],
            'int' => [T_INT_CAST, '(int)'],
            'integer' => [T_INT_CAST, '(int)'],
            'object' => [T_OBJECT_CAST, '(object)'],
            'string' => [T_STRING_CAST, '(string)'],
            // note: `'null' is dealt with later on
        ];

        $argumentsAnalyzer = new ArgumentsAnalyzer();

        foreach (array_reverse($this->findSettypeCalls($tokens)) as $candidate) {
            $functionNameIndex = $candidate[0];

            $arguments = $argumentsAnalyzer->getArguments($tokens, $candidate[1], $candidate[2]);
            if (2 !== \count($arguments)) {
                continue; // function must be overridden or used incorrectly
            }

            $prev = $tokens->getPrevMeaningfulToken($functionNameIndex);
            if (!$tokens[$prev]->isGivenKind(T_OPEN_TAG) && !$tokens[$prev]->equalsAny([';', '{'])) {
                continue; // return value of the function is used
            }

            reset($arguments);

            // --- Test first argument --------------------

            $firstArgumentStart = key($arguments);
            if ($tokens[$firstArgumentStart]->isComment() || $tokens[$firstArgumentStart]->isWhitespace()) {
                $firstArgumentStart = $tokens->getNextMeaningfulToken($firstArgumentStart);
            }

            if (!$tokens[$firstArgumentStart]->isGivenKind(T_VARIABLE)) {
                continue; // settype only works with variables pass by reference, function must be overridden
            }

            $commaIndex = $tokens->getNextMeaningfulToken($firstArgumentStart);

            if (null === $commaIndex || !$tokens[$commaIndex]->equals(',')) {
                continue; // first argument is complex statement; function must be overridden
            }

            // --- Test second argument -------------------

            next($arguments);
            $secondArgumentStart = key($arguments);
            $secondArgumentEnd = $arguments[$secondArgumentStart];

            if ($tokens[$secondArgumentStart]->isComment() || $tokens[$secondArgumentStart]->isWhitespace()) {
                $secondArgumentStart = $tokens->getNextMeaningfulToken($secondArgumentStart);
            }

            if (
                !$tokens[$secondArgumentStart]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)
                || $tokens->getNextMeaningfulToken($secondArgumentStart) < $secondArgumentEnd
            ) {
                continue; // second argument is of the wrong type or is a (complex) statement of some sort (function is overridden)
            }

            // --- Test type ------------------------------

            $type = strtolower(trim($tokens[$secondArgumentStart]->getContent(), '"\'"'));

            if ('null' !== $type && !isset($map[$type])) {
                continue; // we don't know how to map
            }

            // --- Fixing ---------------------------------

            $argumentToken = $tokens[$firstArgumentStart];

            $this->removeSettypeCall(
                $tokens,
                $functionNameIndex,
                $candidate[1],
                $firstArgumentStart,
                $commaIndex,
                $secondArgumentStart,
                $candidate[2]
            );

            if ('null' === $type) {
                $this->findSettypeNullCall($tokens, $functionNameIndex, $argumentToken);
            } else {
                $this->fixSettypeCall($tokens, $functionNameIndex, $argumentToken, new Token($map[$type]));
            }
        }
    }

    private function findSettypeCalls(Tokens $tokens)
    {
        $candidates = [];

        $end = \count($tokens);
        for ($i = 1; $i < $end; ++$i) {
            $candidate = $this->find('settype', $tokens, $i, $end);
            if (null === $candidate) {
                break;
            }

            $i = $candidate[1]; // proceed to openParenthesisIndex
            $candidates[] = $candidate;
        }

        return $candidates;
    }

    /**
     * @param Tokens $tokens
     * @param int    $functionNameIndex
     * @param int    $openParenthesisIndex
     * @param int    $firstArgumentStart
     * @param int    $commaIndex
     * @param int    $secondArgumentStart
     * @param int    $closeParenthesisIndex
     */
    private function removeSettypeCall(
        Tokens $tokens,
        $functionNameIndex,
        $openParenthesisIndex,
        $firstArgumentStart,
        $commaIndex,
        $secondArgumentStart,
        $closeParenthesisIndex
    ) {
        $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex);
        $prevIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex);
        if ($tokens[$prevIndex]->equals(',')) {
            $tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex);
        }
        $tokens->clearTokenAndMergeSurroundingWhitespace($secondArgumentStart);
        $tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
        $tokens->clearTokenAndMergeSurroundingWhitespace($firstArgumentStart);
        $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex);
        $tokens->clearAt($functionNameIndex); // we'll be inserting here so no need to merge the space tokens
        $tokens->clearEmptyTokens();
    }

    /**
     * @param Tokens $tokens
     * @param Token  $castToken
     * @param int    $functionNameIndex
     * @param Token  $argumentToken
     */
    private function fixSettypeCall(
        Tokens $tokens,
        $functionNameIndex,
        Token $argumentToken,
        Token $castToken
    ) {
        $tokens->insertAt(
            $functionNameIndex,
            [
                clone $argumentToken,
                new Token([T_WHITESPACE, ' ']),
                new Token('='),
                new Token([T_WHITESPACE, ' ']),
                $castToken,
                new Token([T_WHITESPACE, ' ']),
                clone $argumentToken,
            ]
        );

        $tokens->removeTrailingWhitespace($functionNameIndex + 6); // 6 = number of inserted tokens -1 for offset correction
    }

    /**
     * @param Tokens $tokens
     * @param int    $functionNameIndex
     * @param Token  $argumentToken
     */
    private function findSettypeNullCall(
        Tokens $tokens,
        $functionNameIndex,
        Token $argumentToken
    ) {
        $tokens->insertAt(
            $functionNameIndex,
            [
                clone $argumentToken,
                new Token([T_WHITESPACE, ' ']),
                new Token('='),
                new Token([T_WHITESPACE, ' ']),
                new Token([T_STRING, 'null']),
            ]
        );

        $tokens->removeTrailingWhitespace($functionNameIndex + 4); // 4 = number of inserted tokens -1 for offset correction
    }
}