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

use PhpCsFixer\Preg;
use PhpCsFixer\Utils;

/**
 * Collection of code tokens.
 *
 * Its role is to provide the ability to manage collection and navigate through it.
 *
 * As a token prototype you should understand a single element generated by token_get_all.
 *
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * @method Token current()
 * @method Token offsetGet($index)
 */
class Tokens extends \SplFixedArray
{
    const BLOCK_TYPE_PARENTHESIS_BRACE = 1;
    const BLOCK_TYPE_CURLY_BRACE = 2;
    const BLOCK_TYPE_INDEX_SQUARE_BRACE = 3;
    const BLOCK_TYPE_ARRAY_SQUARE_BRACE = 4;
    const BLOCK_TYPE_DYNAMIC_PROP_BRACE = 5;
    const BLOCK_TYPE_DYNAMIC_VAR_BRACE = 6;
    const BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE = 7;
    const BLOCK_TYPE_GROUP_IMPORT_BRACE = 8;
    const BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE = 9;
    const BLOCK_TYPE_BRACE_CLASS_INSTANTIATION = 10;

    /**
     * Static class cache.
     *
     * @var array
     */
    private static $cache = [];

    /**
     * Cache of block edges. Any change in collection will invalidate it.
     *
     * @var array<int, int>
     */
    private $blockEndCache = [];

    /**
     * crc32 hash of code string.
     *
     * @var string
     */
    private $codeHash;

    /**
     * Flag is collection was changed.
     *
     * It doesn't know about change of collection's items. To check it run `isChanged` method.
     *
     * @var bool
     */
    private $changed = false;

    /**
     * Set of found token kinds.
     *
     * When the token kind is present in this set it means that given token kind
     * was ever seen inside the collection (but may not be part of it any longer).
     * The key is token kind and the value is always true.
     *
     * @var array<int|string, int>
     */
    private $foundTokenKinds = [];

    /**
     * @var bool
     *
     * @todo remove at 3.0
     */
    private static $isLegacyMode = false;

    /**
     * Clone tokens collection.
     */
    public function __clone()
    {
        foreach ($this as $key => $val) {
            $this[$key] = clone $val;
        }
    }

    /**
     * @return bool
     *
     * @internal
     *
     * @todo remove at 3.0
     */
    public static function isLegacyMode()
    {
        return self::$isLegacyMode;
    }

    /**
     * @param bool $isLegacy
     *
     * @internal
     *
     * @todo remove at 3.0
     */
    public static function setLegacyMode($isLegacy)
    {
        if (getenv('PHP_CS_FIXER_FUTURE_MODE') && $isLegacy) {
            throw new \RuntimeException('Cannot enable `legacy mode` when using `future mode`. This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.');
        }

        self::$isLegacyMode = $isLegacy;
    }

    /**
     * Clear cache - one position or all of them.
     *
     * @param null|string $key position to clear, when null clear all
     */
    public static function clearCache($key = null)
    {
        if (null === $key) {
            self::$cache = [];

            return;
        }

        if (self::hasCache($key)) {
            unset(self::$cache[$key]);
        }
    }

    /**
     * Detect type of block.
     *
     * @param Token $token token
     *
     * @return null|array array with 'type' and 'isStart' keys or null if not found
     */
    public static function detectBlockType(Token $token)
    {
        foreach (self::getBlockEdgeDefinitions() as $type => $definition) {
            if ($token->equals($definition['start'])) {
                return ['type' => $type, 'isStart' => true];
            }

            if ($token->equals($definition['end'])) {
                return ['type' => $type, 'isStart' => false];
            }
        }
    }

    /**
     * Create token collection from array.
     *
     * @param Token[] $array       the array to import
     * @param bool    $saveIndexes save the numeric indexes used in the original array, default is yes
     *
     * @return Tokens
     */
    public static function fromArray($array, $saveIndexes = null)
    {
        $tokens = new self(\count($array));

        if (null === $saveIndexes || $saveIndexes) {
            foreach ($array as $key => $val) {
                $tokens[$key] = $val;
            }
        } else {
            $index = 0;

            foreach ($array as $val) {
                $tokens[$index++] = $val;
            }
        }

        $tokens->generateCode(); // regenerate code to calculate code hash

        return $tokens;
    }

    /**
     * Create token collection directly from code.
     *
     * @param string $code PHP code
     *
     * @return Tokens
     */
    public static function fromCode($code)
    {
        $codeHash = self::calculateCodeHash($code);

        if (self::hasCache($codeHash)) {
            $tokens = self::getCache($codeHash);

            // generate the code to recalculate the hash
            $tokens->generateCode();

            if ($codeHash === $tokens->codeHash) {
                $tokens->clearEmptyTokens();
                $tokens->clearChanged();

                return $tokens;
            }
        }

        $tokens = new self();
        $tokens->setCode($code);
        $tokens->clearChanged();

        return $tokens;
    }

    /**
     * @return array
     */
    public static function getBlockEdgeDefinitions()
    {
        return [
            self::BLOCK_TYPE_CURLY_BRACE => [
                'start' => '{',
                'end' => '}',
            ],
            self::BLOCK_TYPE_PARENTHESIS_BRACE => [
                'start' => '(',
                'end' => ')',
            ],
            self::BLOCK_TYPE_INDEX_SQUARE_BRACE => [
                'start' => '[',
                'end' => ']',
            ],
            self::BLOCK_TYPE_ARRAY_SQUARE_BRACE => [
                'start' => [CT::T_ARRAY_SQUARE_BRACE_OPEN, '['],
                'end' => [CT::T_ARRAY_SQUARE_BRACE_CLOSE, ']'],
            ],
            self::BLOCK_TYPE_DYNAMIC_PROP_BRACE => [
                'start' => [CT::T_DYNAMIC_PROP_BRACE_OPEN, '{'],
                'end' => [CT::T_DYNAMIC_PROP_BRACE_CLOSE, '}'],
            ],
            self::BLOCK_TYPE_DYNAMIC_VAR_BRACE => [
                'start' => [CT::T_DYNAMIC_VAR_BRACE_OPEN, '{'],
                'end' => [CT::T_DYNAMIC_VAR_BRACE_CLOSE, '}'],
            ],
            self::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE => [
                'start' => [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN, '{'],
                'end' => [CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE, '}'],
            ],
            self::BLOCK_TYPE_GROUP_IMPORT_BRACE => [
                'start' => [CT::T_GROUP_IMPORT_BRACE_OPEN, '{'],
                'end' => [CT::T_GROUP_IMPORT_BRACE_CLOSE, '}'],
            ],
            self::BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE => [
                'start' => [CT::T_DESTRUCTURING_SQUARE_BRACE_OPEN, '['],
                'end' => [CT::T_DESTRUCTURING_SQUARE_BRACE_CLOSE, ']'],
            ],
            self::BLOCK_TYPE_BRACE_CLASS_INSTANTIATION => [
                'start' => [CT::T_BRACE_CLASS_INSTANTIATION_OPEN, '('],
                'end' => [CT::T_BRACE_CLASS_INSTANTIATION_CLOSE, ')'],
            ],
        ];
    }

    /**
     * Set new size of collection.
     *
     * @param int $size
     */
    public function setSize($size)
    {
        if ($this->getSize() !== $size) {
            $this->changed = true;
            parent::setSize($size);
        }
    }

    /**
     * Unset collection item.
     *
     * @param int $index
     */
    public function offsetUnset($index)
    {
        $this->changed = true;
        $this->unregisterFoundToken($this[$index]);
        parent::offsetUnset($index);
    }

    /**
     * Set collection item.
     *
     * Warning! `$newval` must not be typehinted to be compatible with `ArrayAccess::offsetSet` method.
     *
     * @param int   $index
     * @param Token $newval
     */
    public function offsetSet($index, $newval)
    {
        $this->blockEndCache = [];

        if (!$this[$index] || !$this[$index]->equals($newval)) {
            $this->changed = true;

            if (isset($this[$index])) {
                $this->unregisterFoundToken($this[$index]);
            }

            $this->registerFoundToken($newval);
        }

        parent::offsetSet($index, $newval);
    }

    /**
     * Clear internal flag if collection was changed and flag for all collection's items.
     */
    public function clearChanged()
    {
        $this->changed = false;

        if (self::isLegacyMode()) {
            foreach ($this as $token) {
                $token->clearChanged();
            }
        }
    }

    /**
     * Clear empty tokens.
     *
     * Empty tokens can occur e.g. after calling clear on item of collection.
     */
    public function clearEmptyTokens()
    {
        $limit = $this->count();
        $index = 0;

        for (; $index < $limit; ++$index) {
            if ($this->isEmptyAt($index)) {
                break;
            }
        }

        // no empty token found, therefore there is no need to override collection
        if ($limit === $index) {
            return;
        }

        for ($count = $index; $index < $limit; ++$index) {
            if (!$this->isEmptyAt($index)) {
                $this[$count++] = $this[$index];
            }
        }

        $this->setSize($count);
    }

    /**
     * Ensure that on given index is a whitespace with given kind.
     *
     * If there is a whitespace then it's content will be modified.
     * If not - the new Token will be added.
     *
     * @param int    $index       index
     * @param int    $indexOffset index offset for Token insertion
     * @param string $whitespace  whitespace to set
     *
     * @return bool if new Token was added
     */
    public function ensureWhitespaceAtIndex($index, $indexOffset, $whitespace)
    {
        $removeLastCommentLine = static function (self $tokens, $index, $indexOffset, $whitespace) {
            $token = $tokens[$index];

            if (1 === $indexOffset && $token->isGivenKind(T_OPEN_TAG)) {
                if (0 === strpos($whitespace, "\r\n")) {
                    $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent())."\r\n"]);

                    return \strlen($whitespace) > 2 // can be removed on PHP 7; https://php.net/manual/en/function.substr.php
                        ? substr($whitespace, 2)
                        : ''
                    ;
                }

                $tokens[$index] = new Token([T_OPEN_TAG, rtrim($token->getContent()).$whitespace[0]]);

                return \strlen($whitespace) > 1 // can be removed on PHP 7; https://php.net/manual/en/function.substr.php
                    ? substr($whitespace, 1)
                    : ''
                ;
            }

            return $whitespace;
        };

        if ($this[$index]->isWhitespace()) {
            $whitespace = $removeLastCommentLine($this, $index - 1, $indexOffset, $whitespace);

            if ('' === $whitespace) {
                $this->clearAt($index);
            } else {
                $this[$index] = new Token([T_WHITESPACE, $whitespace]);
            }

            return false;
        }

        $whitespace = $removeLastCommentLine($this, $index, $indexOffset, $whitespace);
        if ('' === $whitespace) {
            return false;
        }

        $this->insertAt(
            $index + $indexOffset,
            [
                new Token([T_WHITESPACE, $whitespace]),
            ]
        );

        return true;
    }

    /**
     * @param int  $type        type of block, one of BLOCK_TYPE_*
     * @param int  $searchIndex index of opening brace
     * @param bool $findEnd     if method should find block's end, default true, otherwise method find block's start
     *
     * @return int index of closing brace
     */
    public function findBlockEnd($type, $searchIndex, $findEnd = true)
    {
        if (3 === \func_num_args()) {
            if ($findEnd) {
                @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, you can safely drop the argument.', E_USER_DEPRECATED);
            } else {
                @trigger_error('Argument #3 of Tokens::findBlockEnd is deprecated and will be removed in 3.0, use Tokens::findBlockStart instead.', E_USER_DEPRECATED);
            }
        }

        return $this->findOppositeBlockEdge($type, $searchIndex, $findEnd);
    }

    /**
     * @param int $type        type of block, one of BLOCK_TYPE_*
     * @param int $searchIndex index of closing brace
     *
     * @return int index of opening brace
     */
    public function findBlockStart($type, $searchIndex)
    {
        return $this->findOppositeBlockEdge($type, $searchIndex, false);
    }

    /**
     * @param array|int $possibleKind kind or array of kind
     * @param int       $start        optional offset
     * @param null|int  $end          optional limit
     *
     * @return array array of tokens of given kinds or assoc array of arrays
     */
    public function findGivenKind($possibleKind, $start = 0, $end = null)
    {
        $this->rewind();
        if (null === $end) {
            $end = $this->count();
        }

        $elements = [];
        $possibleKinds = (array) $possibleKind;

        foreach ($possibleKinds as $kind) {
            $elements[$kind] = [];
        }

        if (!self::isLegacyMode()) {
            $possibleKinds = array_filter($possibleKinds, function ($kind) {
                return $this->isTokenKindFound($kind);
            });
        }

        if (\count($possibleKinds)) {
            for ($i = $start; $i < $end; ++$i) {
                $token = $this[$i];
                if ($token->isGivenKind($possibleKinds)) {
                    $elements[$token->getId()][$i] = $token;
                }
            }
        }

        return \is_array($possibleKind) ? $elements : $elements[$possibleKind];
    }

    /**
     * @return string
     */
    public function generateCode()
    {
        $code = $this->generatePartialCode(0, \count($this) - 1);
        $this->changeCodeHash(self::calculateCodeHash($code));

        return $code;
    }

    /**
     * Generate code from tokens between given indexes.
     *
     * @param int $start start index
     * @param int $end   end index
     *
     * @return string
     */
    public function generatePartialCode($start, $end)
    {
        $code = '';

        for ($i = $start; $i <= $end; ++$i) {
            $code .= $this[$i]->getContent();
        }

        return $code;
    }

    /**
     * Get hash of code.
     *
     * @return string
     */
    public function getCodeHash()
    {
        return $this->codeHash;
    }

    /**
     * Get index for closest next token which is non whitespace.
     *
     * This method is shorthand for getNonWhitespaceSibling method.
     *
     * @param int         $index       token index
     * @param null|string $whitespaces whitespaces characters for Token::isWhitespace
     *
     * @return null|int
     */
    public function getNextNonWhitespace($index, $whitespaces = null)
    {
        return $this->getNonWhitespaceSibling($index, 1, $whitespaces);
    }

    /**
     * Get index for closest next token of given kind.
     *
     * This method is shorthand for getTokenOfKindSibling method.
     *
     * @param int   $index         token index
     * @param array $tokens        possible tokens
     * @param bool  $caseSensitive perform a case sensitive comparison
     *
     * @return null|int
     */
    public function getNextTokenOfKind($index, array $tokens = [], $caseSensitive = true)
    {
        return $this->getTokenOfKindSibling($index, 1, $tokens, $caseSensitive);
    }

    /**
     * Get index for closest sibling token which is non whitespace.
     *
     * @param int         $index       token index
     * @param int         $direction   direction for looking, +1 or -1
     * @param null|string $whitespaces whitespaces characters for Token::isWhitespace
     *
     * @return null|int
     */
    public function getNonWhitespaceSibling($index, $direction, $whitespaces = null)
    {
        while (true) {
            $index += $direction;

            if (!$this->offsetExists($index)) {
                return null;
            }

            $token = $this[$index];

            if (!$token->isWhitespace($whitespaces)) {
                return $index;
            }
        }
    }

    /**
     * Get index for closest previous token which is non whitespace.
     *
     * This method is shorthand for getNonWhitespaceSibling method.
     *
     * @param int         $index       token index
     * @param null|string $whitespaces whitespaces characters for Token::isWhitespace
     *
     * @return null|int
     */
    public function getPrevNonWhitespace($index, $whitespaces = null)
    {
        return $this->getNonWhitespaceSibling($index, -1, $whitespaces);
    }

    /**
     * Get index for closest previous token of given kind.
     * This method is shorthand for getTokenOfKindSibling method.
     *
     * @param int   $index         token index
     * @param array $tokens        possible tokens
     * @param bool  $caseSensitive perform a case sensitive comparison
     *
     * @return null|int
     */
    public function getPrevTokenOfKind($index, array $tokens = [], $caseSensitive = true)
    {
        return $this->getTokenOfKindSibling($index, -1, $tokens, $caseSensitive);
    }

    /**
     * Get index for closest sibling token of given kind.
     *
     * @param int   $index         token index
     * @param int   $direction     direction for looking, +1 or -1
     * @param array $tokens        possible tokens
     * @param bool  $caseSensitive perform a case sensitive comparison
     *
     * @return null|int
     */
    public function getTokenOfKindSibling($index, $direction, array $tokens = [], $caseSensitive = true)
    {
        if (!self::isLegacyMode()) {
            $tokens = array_filter($tokens, function ($token) {
                return $this->isTokenKindFound($this->extractTokenKind($token));
            });
        }

        if (!\count($tokens)) {
            return null;
        }

        while (true) {
            $index += $direction;

            if (!$this->offsetExists($index)) {
                return null;
            }

            $token = $this[$index];

            if ($token->equalsAny($tokens, $caseSensitive)) {
                return $index;
            }
        }
    }

    /**
     * Get index for closest sibling token not of given kind.
     *
     * @param int   $index     token index
     * @param int   $direction direction for looking, +1 or -1
     * @param array $tokens    possible tokens
     *
     * @return null|int
     */
    public function getTokenNotOfKindSibling($index, $direction, array $tokens = [])
    {
        while (true) {
            $index += $direction;

            if (!$this->offsetExists($index)) {
                return null;
            }

            if ($this->isEmptyAt($index)) {
                continue;
            }

            if ($this[$index]->equalsAny($tokens)) {
                continue;
            }

            return $index;
        }
    }

    /**
     * Get index for closest sibling token that is not a whitespace or comment.
     *
     * @param int $index     token index
     * @param int $direction direction for looking, +1 or -1
     *
     * @return null|int
     */
    public function getMeaningfulTokenSibling($index, $direction)
    {
        return $this->getTokenNotOfKindSibling(
            $index,
            $direction,
            [[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT]]
        );
    }

    /**
     * Get index for closest sibling token which is not empty.
     *
     * @param int $index     token index
     * @param int $direction direction for looking, +1 or -1
     *
     * @return null|int
     */
    public function getNonEmptySibling($index, $direction)
    {
        while (true) {
            $index += $direction;

            if (!$this->offsetExists($index)) {
                return null;
            }

            if (!$this->isEmptyAt($index)) {
                return $index;
            }
        }
    }

    /**
     * Get index for closest next token that is not a whitespace or comment.
     *
     * @param int $index token index
     *
     * @return null|int
     */
    public function getNextMeaningfulToken($index)
    {
        return $this->getMeaningfulTokenSibling($index, 1);
    }

    /**
     * Get index for closest previous token that is not a whitespace or comment.
     *
     * @param int $index token index
     *
     * @return null|int
     */
    public function getPrevMeaningfulToken($index)
    {
        return $this->getMeaningfulTokenSibling($index, -1);
    }

    /**
     * Find a sequence of meaningful tokens and returns the array of their locations.
     *
     * @param array                 $sequence      an array of tokens (kinds) (same format used by getNextTokenOfKind)
     * @param int                   $start         start index, defaulting to the start of the file
     * @param int                   $end           end index, defaulting to the end of the file
     * @param array<int, bool>|bool $caseSensitive global case sensitiveness or an array of booleans, whose keys should match
     *                                             the ones used in $others. If any is missing, the default case-sensitive
     *                                             comparison is used
     *
     * @return null|array<int, Token> an array containing the tokens matching the sequence elements, indexed by their position
     */
    public function findSequence(array $sequence, $start = 0, $end = null, $caseSensitive = true)
    {
        $sequenceCount = \count($sequence);
        if (0 === $sequenceCount) {
            throw new \InvalidArgumentException('Invalid sequence.');
        }

        // $end defaults to the end of the collection
        $end = null === $end ? \count($this) - 1 : min($end, \count($this) - 1);

        if ($start + $sequenceCount - 1 > $end) {
            return null;
        }

        // make sure the sequence content is "meaningful"
        foreach ($sequence as $key => $token) {
            // if not a Token instance already, we convert it to verify the meaningfulness
            if (!$token instanceof Token) {
                if (\is_array($token) && !isset($token[1])) {
                    // fake some content as it is required by the Token constructor,
                    // although optional for search purposes
                    $token[1] = 'DUMMY';
                }
                $token = new Token($token);
            }

            if ($token->isWhitespace() || $token->isComment() || '' === $token->getContent()) {
                throw new \InvalidArgumentException(sprintf('Non-meaningful token at position: %s.', $key));
            }
        }

        if (!self::isLegacyMode()) {
            foreach ($sequence as $token) {
                if (!$this->isTokenKindFound($this->extractTokenKind($token))) {
                    return null;
                }
            }
        }

        // remove the first token from the sequence, so we can freely iterate through the sequence after a match to
        // the first one is found
        $key = key($sequence);
        $firstCs = Token::isKeyCaseSensitive($caseSensitive, $key);
        $firstToken = $sequence[$key];
        unset($sequence[$key]);

        // begin searching for the first token in the sequence (start included)
        $index = $start - 1;
        while (null !== $index && $index <= $end) {
            $index = $this->getNextTokenOfKind($index, [$firstToken], $firstCs);

            // ensure we found a match and didn't get past the end index
            if (null === $index || $index > $end) {
                return null;
            }

            // initialise the result array with the current index
            $result = [$index => $this[$index]];

            // advance cursor to the current position
            $currIdx = $index;

            // iterate through the remaining tokens in the sequence
            foreach ($sequence as $key => $token) {
                $currIdx = $this->getNextMeaningfulToken($currIdx);

                // ensure we didn't go too far
                if (null === $currIdx || $currIdx > $end) {
                    return null;
                }

                if (!$this[$currIdx]->equals($token, Token::isKeyCaseSensitive($caseSensitive, $key))) {
                    // not a match, restart the outer loop
                    continue 2;
                }

                // append index to the result array
                $result[$currIdx] = $this[$currIdx];
            }

            // do we have a complete match?
            // hint: $result is bigger than $sequence since the first token has been removed from the latter
            if (\count($sequence) < \count($result)) {
                return $result;
            }
        }
    }

    /**
     * Insert instances of Token inside collection.
     *
     * @param int                  $index start inserting index
     * @param Token|Token[]|Tokens $items instances of Token to insert
     */
    public function insertAt($index, $items)
    {
        $items = \is_array($items) || $items instanceof self ? $items : [$items];
        $itemsCnt = \count($items);

        if (0 === $itemsCnt) {
            return;
        }

        $oldSize = \count($this);
        $this->changed = true;
        $this->blockEndCache = [];
        $this->setSize($oldSize + $itemsCnt);

        // since we only move already existing items around, we directly call into SplFixedArray::offset* methods.
        // that way we get around additional overhead this class adds with overridden offset* methods.
        for ($i = $oldSize + $itemsCnt - 1; $i >= $index; --$i) {
            $oldItem = parent::offsetExists($i - $itemsCnt) ? parent::offsetGet($i - $itemsCnt) : new Token('');
            parent::offsetSet($i, $oldItem);
        }

        for ($i = 0; $i < $itemsCnt; ++$i) {
            if ('' === $items[$i]->getContent()) {
                throw new \InvalidArgumentException('Must not add empty token to collection.');
            }

            $this->registerFoundToken($items[$i]);
            parent::offsetSet($i + $index, $items[$i]);
        }
    }

    /**
     * Check if collection was change: collection itself (like insert new tokens) or any of collection's elements.
     *
     * @return bool
     */
    public function isChanged()
    {
        if ($this->changed) {
            return true;
        }

        if (self::isLegacyMode()) {
            foreach ($this as $token) {
                if ($token->isChanged()) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @param int $index
     *
     * @return bool
     */
    public function isEmptyAt($index)
    {
        $token = $this[$index];

        return null === $token->getId() && '' === $token->getContent();
    }

    public function clearAt($index)
    {
        $this[$index] = new Token('');
    }

    /**
     * Override token at given index and register it.
     *
     * @param int                $index
     * @param array|string|Token $token token prototype
     *
     * @deprecated since 2.4, use offsetSet instead
     */
    public function overrideAt($index, $token)
    {
        @trigger_error(__METHOD__.' is deprecated and will be removed in 3.0, use offsetSet instead.', E_USER_DEPRECATED);
        self::$isLegacyMode = true;

        $this[$index]->override($token);
        $this->registerFoundToken($token);
    }

    /**
     * Override tokens at given range.
     *
     * @param int            $indexStart start overriding index
     * @param int            $indexEnd   end overriding index
     * @param Token[]|Tokens $items      tokens to insert
     */
    public function overrideRange($indexStart, $indexEnd, $items)
    {
        $oldCode = $this->generatePartialCode($indexStart, $indexEnd);

        $newCode = '';
        foreach ($items as $item) {
            $newCode .= $item->getContent();
        }

        // no changes, return
        if ($oldCode === $newCode) {
            return;
        }

        $indexToChange = $indexEnd - $indexStart + 1;
        $itemsCount = \count($items);

        // If we want to add more items than passed range contains we need to
        // add placeholders for overhead items.
        if ($itemsCount > $indexToChange) {
            $placeholders = [];
            while ($itemsCount > $indexToChange) {
                $placeholders[] = new Token('__PLACEHOLDER__');
                ++$indexToChange;
            }
            $this->insertAt($indexEnd + 1, $placeholders);
        }

        // Override each items.
        foreach ($items as $itemIndex => $item) {
            $this[$indexStart + $itemIndex] = $item;
        }

        // If we want to add less tokens than passed range contains then clear
        // not needed tokens.
        if ($itemsCount < $indexToChange) {
            $this->clearRange($indexStart + $itemsCount, $indexEnd);
        }
    }

    /**
     * @param int         $index
     * @param null|string $whitespaces optional whitespaces characters for Token::isWhitespace
     */
    public function removeLeadingWhitespace($index, $whitespaces = null)
    {
        $this->removeWhitespaceSafely($index, -1, $whitespaces);
    }

    /**
     * @param int         $index
     * @param null|string $whitespaces optional whitespaces characters for Token::isWhitespace
     */
    public function removeTrailingWhitespace($index, $whitespaces = null)
    {
        $this->removeWhitespaceSafely($index, 1, $whitespaces);
    }

    /**
     * Set code. Clear all current content and replace it by new Token items generated from code directly.
     *
     * @param string $code PHP code
     */
    public function setCode($code)
    {
        // No need to work when the code is the same.
        // That is how we avoid a lot of work and setting changed flag.
        if ($code === $this->generateCode()) {
            return;
        }

        // clear memory
        $this->setSize(0);

        $tokens = \defined('TOKEN_PARSE')
            ? token_get_all($code, TOKEN_PARSE)
            : token_get_all($code);

        $this->setSize(\count($tokens));

        foreach ($tokens as $index => $token) {
            $this[$index] = new Token($token);
        }

        $transformers = Transformers::create();
        $transformers->transform($this);

        $this->foundTokenKinds = [];
        foreach ($this as $token) {
            $this->registerFoundToken($token);
        }

        $this->rewind();
        $this->changeCodeHash(self::calculateCodeHash($code));
        $this->changed = true;
    }

    public function toJson()
    {
        static $options = null;

        if (null === $options) {
            $options = Utils::calculateBitmask(['JSON_PRETTY_PRINT', 'JSON_NUMERIC_CHECK']);
        }

        $output = new \SplFixedArray(\count($this));

        foreach ($this as $index => $token) {
            $output[$index] = $token->toArray();
        }

        $this->rewind();

        return json_encode($output, $options);
    }

    /**
     * Check if all token kinds given as argument are found.
     *
     * @param array $tokenKinds
     *
     * @return bool
     */
    public function isAllTokenKindsFound(array $tokenKinds)
    {
        foreach ($tokenKinds as $tokenKind) {
            if (empty($this->foundTokenKinds[$tokenKind])) {
                return false;
            }
        }

        return true;
    }

    /**
     * Check if any token kind given as argument is found.
     *
     * @param array $tokenKinds
     *
     * @return bool
     */
    public function isAnyTokenKindsFound(array $tokenKinds)
    {
        foreach ($tokenKinds as $tokenKind) {
            if (!empty($this->foundTokenKinds[$tokenKind])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if token kind given as argument is found.
     *
     * @param int|string $tokenKind
     *
     * @return bool
     */
    public function isTokenKindFound($tokenKind)
    {
        return !empty($this->foundTokenKinds[$tokenKind]);
    }

    /**
     * @param int|string $tokenKind
     *
     * @return int
     */
    public function countTokenKind($tokenKind)
    {
        if (self::isLegacyMode()) {
            throw new \RuntimeException(sprintf('%s is not available in legacy mode.', __METHOD__));
        }

        return isset($this->foundTokenKinds[$tokenKind]) ? $this->foundTokenKinds[$tokenKind] : 0;
    }

    /**
     * Clear tokens in the given range.
     *
     * @param int $indexStart
     * @param int $indexEnd
     */
    public function clearRange($indexStart, $indexEnd)
    {
        for ($i = $indexStart; $i <= $indexEnd; ++$i) {
            $this->clearAt($i);
        }
    }

    /**
     * Checks for monolithic PHP code.
     *
     * Checks that the code is pure PHP code, in a single code block, starting
     * with an open tag.
     *
     * @return bool
     */
    public function isMonolithicPhp()
    {
        $size = $this->count();

        if (0 === $size) {
            return false;
        }

        if (self::isLegacyMode()) {
            // If code is not monolithic there is a great chance that first or last token is `T_INLINE_HTML`:
            if ($this[0]->isGivenKind(T_INLINE_HTML) || $this[$size - 1]->isGivenKind(T_INLINE_HTML)) {
                return false;
            }

            for ($index = 1; $index < $size; ++$index) {
                if ($this[$index]->isGivenKind([T_INLINE_HTML, T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO])) {
                    return false;
                }
            }

            return true;
        }

        if ($this->isTokenKindFound(T_INLINE_HTML)) {
            return false;
        }

        return 1 >= ($this->countTokenKind(T_OPEN_TAG) + $this->countTokenKind(T_OPEN_TAG_WITH_ECHO));
    }

    /**
     * @param int $start start index
     * @param int $end   end index
     *
     * @return bool
     */
    public function isPartialCodeMultiline($start, $end)
    {
        for ($i = $start; $i <= $end; ++$i) {
            if (false !== strpos($this[$i]->getContent(), "\n")) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param int $index
     */
    public function clearTokenAndMergeSurroundingWhitespace($index)
    {
        $count = \count($this);
        $this->clearAt($index);

        if ($index === $count - 1) {
            return;
        }

        $nextIndex = $this->getNonEmptySibling($index, 1);

        if (null === $nextIndex || !$this[$nextIndex]->isWhitespace()) {
            return;
        }

        $prevIndex = $this->getNonEmptySibling($index, -1);

        if ($this[$prevIndex]->isWhitespace()) {
            $this[$prevIndex] = new Token([T_WHITESPACE, $this[$prevIndex]->getContent().$this[$nextIndex]->getContent()]);
        } elseif ($this->isEmptyAt($prevIndex + 1)) {
            $this[$prevIndex + 1] = new Token([T_WHITESPACE, $this[$nextIndex]->getContent()]);
        }

        $this->clearAt($nextIndex);
    }

    private function removeWhitespaceSafely($index, $direction, $whitespaces = null)
    {
        $whitespaceIndex = $this->getNonEmptySibling($index, $direction);
        if (isset($this[$whitespaceIndex]) && $this[$whitespaceIndex]->isWhitespace()) {
            $newContent = '';
            $tokenToCheck = $this[$whitespaceIndex];

            // if the token candidate to remove is preceded by single line comment we do not consider the new line after this comment as part of T_WHITESPACE
            if (isset($this[$whitespaceIndex - 1]) && $this[$whitespaceIndex - 1]->isComment() && '/*' !== substr($this[$whitespaceIndex - 1]->getContent(), 0, 2)) {
                list($emptyString, $newContent, $whitespacesToCheck) = Preg::split('/^(\R)/', $this[$whitespaceIndex]->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE);
                if ('' === $whitespacesToCheck) {
                    return;
                }
                $tokenToCheck = new Token([T_WHITESPACE, $whitespacesToCheck]);
            }

            if (!$tokenToCheck->isWhitespace($whitespaces)) {
                return;
            }

            if ('' === $newContent) {
                $this->clearAt($whitespaceIndex);
            } else {
                $this[$whitespaceIndex] = new Token([T_WHITESPACE, $newContent]);
            }
        }
    }

    /**
     * @param int  $type        type of block, one of BLOCK_TYPE_*
     * @param int  $searchIndex index of starting brace
     * @param bool $findEnd     if method should find block's end or start
     *
     * @return int index of opposite brace
     */
    private function findOppositeBlockEdge($type, $searchIndex, $findEnd)
    {
        $blockEdgeDefinitions = self::getBlockEdgeDefinitions();

        if (!isset($blockEdgeDefinitions[$type])) {
            throw new \InvalidArgumentException(sprintf('Invalid param type: %s.', $type));
        }

        if (!self::isLegacyMode() && isset($this->blockEndCache[$searchIndex])) {
            return $this->blockEndCache[$searchIndex];
        }

        $startEdge = $blockEdgeDefinitions[$type]['start'];
        $endEdge = $blockEdgeDefinitions[$type]['end'];
        $startIndex = $searchIndex;
        $endIndex = $this->count() - 1;
        $indexOffset = 1;

        if (!$findEnd) {
            list($startEdge, $endEdge) = [$endEdge, $startEdge];
            $indexOffset = -1;
            $endIndex = 0;
        }

        if (!$this[$startIndex]->equals($startEdge)) {
            throw new \InvalidArgumentException(sprintf('Invalid param $startIndex - not a proper block %s.', $findEnd ? 'start' : 'end'));
        }

        $blockLevel = 0;

        for ($index = $startIndex; $index !== $endIndex; $index += $indexOffset) {
            $token = $this[$index];

            if ($token->equals($startEdge)) {
                ++$blockLevel;

                continue;
            }

            if ($token->equals($endEdge)) {
                --$blockLevel;

                if (0 === $blockLevel) {
                    break;
                }

                continue;
            }
        }

        if (!$this[$index]->equals($endEdge)) {
            throw new \UnexpectedValueException(sprintf('Missing block %s.', $findEnd ? 'end' : 'start'));
        }

        $this->blockEndCache[$startIndex] = $index;
        $this->blockEndCache[$index] = $startIndex;

        return $index;
    }

    /**
     * Calculate hash for code.
     *
     * @param string $code
     *
     * @return string
     */
    private static function calculateCodeHash($code)
    {
        return CodeHasher::calculateCodeHash($code);
    }

    /**
     * Get cache value for given key.
     *
     * @param string $key item key
     *
     * @return Tokens
     */
    private static function getCache($key)
    {
        if (!self::hasCache($key)) {
            throw new \OutOfBoundsException(sprintf('Unknown cache key: "%s".', $key));
        }

        return self::$cache[$key];
    }

    /**
     * Check if given key exists in cache.
     *
     * @param string $key item key
     *
     * @return bool
     */
    private static function hasCache($key)
    {
        return isset(self::$cache[$key]);
    }

    /**
     * @param string $key   item key
     * @param Tokens $value item value
     */
    private static function setCache($key, self $value)
    {
        self::$cache[$key] = $value;
    }

    /**
     * Change code hash.
     *
     * Remove old cache and set new one.
     *
     * @param string $codeHash new code hash
     */
    private function changeCodeHash($codeHash)
    {
        if (null !== $this->codeHash) {
            self::clearCache($this->codeHash);
        }

        $this->codeHash = $codeHash;
        self::setCache($this->codeHash, $this);
    }

    /**
     * Register token as found.
     *
     * @param array|string|Token $token token prototype
     */
    private function registerFoundToken($token)
    {
        // inlined extractTokenKind() call on the hot path
        $tokenKind = $token instanceof Token
            ? ($token->isArray() ? $token->getId() : $token->getContent())
            : (\is_array($token) ? $token[0] : $token)
        ;

        if (!isset($this->foundTokenKinds[$tokenKind])) {
            $this->foundTokenKinds[$tokenKind] = 0;
        }

        ++$this->foundTokenKinds[$tokenKind];
    }

    /**
     * Register token as found.
     *
     * @param array|string|Token $token token prototype
     */
    private function unregisterFoundToken($token)
    {
        // inlined extractTokenKind() call on the hot path
        $tokenKind = $token instanceof Token
            ? ($token->isArray() ? $token->getId() : $token->getContent())
            : (\is_array($token) ? $token[0] : $token)
        ;

        if (!isset($this->foundTokenKinds[$tokenKind])) {
            return;
        }

        --$this->foundTokenKinds[$tokenKind];
    }

    /**
     * @param array|string|Token $token token prototype
     *
     * @return int|string
     */
    private function extractTokenKind($token)
    {
        return $token instanceof Token
            ? ($token->isArray() ? $token->getId() : $token->getContent())
            : (\is_array($token) ? $token[0] : $token)
        ;
    }
}


<?php

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

namespace PhpCsFixer\Fixer\Whitespace;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Javier Spagnoletti <phansys@gmail.com>
 */
final class NoSpacesAroundOffsetFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'There MUST NOT be spaces around offset braces.',
            [
                new CodeSample("<?php\n\$sample = \$b [ 'a' ] [ 'b' ];\n"),
                new CodeSample("<?php\n\$sample = \$b [ 'a' ] [ 'b' ];\n", ['positions' => ['inside']]),
                new CodeSample("<?php\n\$sample = \$b [ 'a' ] [ 'b' ];\n", ['positions' => ['outside']]),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isAnyTokenKindsFound(['[', CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]);
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        foreach ($tokens as $index => $token) {
            if (!$token->equalsAny(['[', [CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN]])) {
                continue;
            }

            if (\in_array('inside', $this->configuration['positions'], true)) {
                if ($token->equals('[')) {
                    $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index);
                } else {
                    $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE, $index);
                }

                // remove space after opening `[` or `{`
                if ($tokens[$index + 1]->isWhitespace(" \t")) {
                    $tokens->clearAt($index + 1);
                }

                // remove space before closing `]` or `}`
                if ($tokens[$endIndex - 1]->isWhitespace(" \t")) {
                    $tokens->clearAt($endIndex - 1);
                }
            }

            if (\in_array('outside', $this->configuration['positions'], true)) {
                $prevNonWhitespaceIndex = $tokens->getPrevNonWhitespace($index);
                if ($tokens[$prevNonWhitespaceIndex]->isComment()) {
                    continue;
                }

                $tokens->removeLeadingWhitespace($index);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        $values = ['inside', 'outside'];

        return new FixerConfigurationResolverRootless('positions', [
            (new FixerOptionBuilder('positions', 'Whether spacing should be fixed inside and/or outside the offset braces.'))
                ->setAllowedTypes(['array'])
                ->setAllowedValues([new AllowedValueSubset($values)])
                ->setDefault($values)
                ->getOption(),
        ], $this->getName());
    }
}


<?php

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

namespace PhpCsFixer\Fixer\PhpUnit;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 */
final class PhpUnitExpectationFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface
{
    /**
     * @var array<string, string>
     */
    private $methodMap = [];

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

        $this->methodMap = [
            'setExpectedException' => 'expectExceptionMessage',
        ];

        if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) {
            $this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageRegExp';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Usages of `->setExpectedException*` methods MUST be replaced by `->expectException*` methods.',
            [
                new CodeSample(
                    '<?php
final class MyTest extends \PHPUnit_Framework_TestCase
{
    public function testFoo()
    {
        $this->setExpectedException("RuntimeException", "Msg", 123);
        foo();
    }

    public function testBar()
    {
        $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123);
        bar();
    }
}
'
                ),
                new CodeSample(
                    '<?php
final class MyTest extends \PHPUnit_Framework_TestCase
{
    public function testFoo()
    {
        $this->setExpectedException("RuntimeException", null, 123);
        foo();
    }

    public function testBar()
    {
        $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123);
        bar();
    }
}
',
                    ['target' => PhpUnitTargetVersion::VERSION_5_6]
                ),
                new CodeSample(
                    '<?php
final class MyTest extends \PHPUnit_Framework_TestCase
{
    public function testFoo()
    {
        $this->setExpectedException("RuntimeException", "Msg", 123);
        foo();
    }

    public function testBar()
    {
        $this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123);
        bar();
    }
}
',
                    ['target' => PhpUnitTargetVersion::VERSION_5_2]
                ),
            ],
            null,
            'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.'
        );
    }

    /**
     * {@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)
    {
        $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator();
        foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) {
            $this->fixExpectation($tokens, $indexes[0], $indexes[1]);
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolver([
            (new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
                ->setAllowedTypes(['string'])
                ->setAllowedValues([PhpUnitTargetVersion::VERSION_5_2, PhpUnitTargetVersion::VERSION_5_6, PhpUnitTargetVersion::VERSION_NEWEST])
                ->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
                ->getOption(),
        ]);
    }

    private function fixExpectation(Tokens $tokens, $startIndex, $endIndex)
    {
        $argumentsAnalyzer = new ArgumentsAnalyzer();

        $oldMethodSequence = [
            new Token([T_VARIABLE, '$this']),
            new Token([T_OBJECT_OPERATOR, '->']),
            [T_STRING],
        ];

        for ($index = $startIndex; $startIndex < $endIndex; ++$index) {
            $match = $tokens->findSequence($oldMethodSequence, $index);

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

            list($thisIndex, , $index) = array_keys($match);

            if (!isset($this->methodMap[$tokens[$index]->getContent()])) {
                continue;
            }

            $openIndex = $tokens->getNextTokenOfKind($index, ['(']);
            $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
            $commaIndex = $tokens->getPrevMeaningfulToken($closeIndex);
            if ($tokens[$commaIndex]->equals(',')) {
                $tokens->removeTrailingWhitespace($commaIndex);
                $tokens->clearAt($commaIndex);
            }

            $arguments = $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex);
            $argumentsCnt = \count($arguments);

            $argumentsReplacements = ['expectException', $this->methodMap[$tokens[$index]->getContent()], 'expectExceptionCode'];

            $indent = $this->whitespacesConfig->getLineEnding().$this->detectIndent($tokens, $thisIndex);

            $isMultilineWhitespace = false;

            for ($cnt = $argumentsCnt - 1; $cnt >= 1; --$cnt) {
                $argStart = array_keys($arguments)[$cnt];
                $argBefore = $tokens->getPrevMeaningfulToken($argStart);

                if ('expectExceptionMessage' === $argumentsReplacements[$cnt]) {
                    $paramIndicatorIndex = $tokens->getNextMeaningfulToken($argBefore);
                    $afterParamIndicatorIndex = $tokens->getNextMeaningfulToken($paramIndicatorIndex);

                    if (
                        $tokens[$paramIndicatorIndex]->equals([T_STRING, 'null'], false) &&
                        $tokens[$afterParamIndicatorIndex]->equals(')')
                    ) {
                        if ($tokens[$argBefore + 1]->isWhitespace()) {
                            $tokens->clearTokenAndMergeSurroundingWhitespace($argBefore + 1);
                        }
                        $tokens->clearTokenAndMergeSurroundingWhitespace($argBefore);
                        $tokens->clearTokenAndMergeSurroundingWhitespace($paramIndicatorIndex);

                        continue;
                    }
                }

                $isMultilineWhitespace = $isMultilineWhitespace || ($tokens[$argStart]->isWhitespace() && !$tokens[$argStart]->isWhitespace(" \t"));
                $tokensOverrideArgStart = [
                    new Token([T_WHITESPACE, $indent]),
                    new Token([T_VARIABLE, '$this']),
                    new Token([T_OBJECT_OPERATOR, '->']),
                    new Token([T_STRING, $argumentsReplacements[$cnt]]),
                    new Token('('),
                ];
                $tokensOverrideArgBefore = [
                    new Token(')'),
                    new Token(';'),
                ];

                if ($isMultilineWhitespace) {
                    array_push($tokensOverrideArgStart, new Token([T_WHITESPACE, $indent.$this->whitespacesConfig->getIndent()]));
                    array_unshift($tokensOverrideArgBefore, new Token([T_WHITESPACE, $indent]));
                }

                if ($tokens[$argStart]->isWhitespace()) {
                    $tokens->overrideRange($argStart, $argStart, $tokensOverrideArgStart);
                } else {
                    $tokens->insertAt($argStart, $tokensOverrideArgStart);
                }

                $tokens->overrideRange($argBefore, $argBefore, $tokensOverrideArgBefore);
            }

            $tokens[$index] = new Token([T_STRING, 'expectException']);
        }
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     *
     * @return string
     */
    private function detectIndent(Tokens $tokens, $index)
    {
        if (!$tokens[$index - 1]->isWhitespace()) {
            return ''; // cannot detect indent
        }

        $explodedContent = explode("\n", $tokens[$index - 1]->getContent());

        return end($explodedContent);
    }
}


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

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

/**
 * @author Adam Marczuk <adam@marczuk.info>
 */
final class NoWhitespaceBeforeCommaInArrayFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'In array declaration, there MUST NOT be a whitespace before each comma.',
            [new CodeSample("<?php \$x = array(1 , \"2\");\n")]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isAnyTokenKindsFound([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN]);
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        for ($index = $tokens->count() - 1; $index >= 0; --$index) {
            if ($tokens[$index]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
                $this->fixSpacing($index, $tokens);
            }
        }
    }

    /**
     * Method to fix spacing in array declaration.
     *
     * @param int    $index
     * @param Tokens $tokens
     */
    private function fixSpacing($index, Tokens $tokens)
    {
        if ($tokens[$index]->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
            $startIndex = $index;
            $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $startIndex);
        } else {
            $startIndex = $tokens->getNextTokenOfKind($index, ['(']);
            $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex);
        }

        for ($i = $endIndex - 1; $i > $startIndex; --$i) {
            $i = $this->skipNonArrayElements($i, $tokens);
            $currentToken = $tokens[$i];
            $prevIndex = $tokens->getPrevNonWhitespace($i - 1);
            if ($currentToken->equals(',') && !$tokens[$prevIndex]->equals([T_END_HEREDOC]) && !$tokens[$prevIndex]->isComment()) {
                $tokens->removeLeadingWhitespace($i);
            }
        }
    }

    /**
     * Method to move index over the non-array elements like function calls or function declarations.
     *
     * @param int    $index
     * @param Tokens $tokens
     *
     * @return int New index
     */
    private function skipNonArrayElements($index, Tokens $tokens)
    {
        if ($tokens[$index]->equals('}')) {
            return $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
        }

        if ($tokens[$index]->equals(')')) {
            $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
            $startIndex = $tokens->getPrevMeaningfulToken($startIndex);
            if (!$tokens[$startIndex]->isGivenKind([T_ARRAY, CT::T_ARRAY_SQUARE_BRACE_OPEN])) {
                return $startIndex;
            }
        }

        return $index;
    }
}


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

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

/**
 * @author SpacePossum
 */
final class NoUnsetCastFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Variables must be set `null` instead of using `(unset)` casting.',
            [new CodeSample("<?php\n\$a = (unset) \$b;\n")]
        );
    }

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

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

    /**
     * @param Tokens $tokens
     * @param int    $index
     */
    private function fixUnsetCast(Tokens $tokens, $index)
    {
        $assignmentIndex = $tokens->getPrevMeaningfulToken($index);
        if (null === $assignmentIndex || !$tokens[$assignmentIndex]->equals('=')) {
            return;
        }

        $varIndex = $tokens->getNextMeaningfulToken($index);
        if (null === $varIndex || !$tokens[$varIndex]->isGivenKind(T_VARIABLE)) {
            return;
        }

        $afterVar = $tokens->getNextMeaningfulToken($varIndex);
        if (null === $afterVar || !$tokens[$afterVar]->equalsAny([';', [T_CLOSE_TAG]])) {
            return;
        }

        $nextIsWhiteSpace = $tokens[$assignmentIndex + 1]->isWhitespace();

        $tokens->clearTokenAndMergeSurroundingWhitespace($index);
        $tokens->clearTokenAndMergeSurroundingWhitespace($varIndex);

        ++$assignmentIndex;
        if (!$nextIsWhiteSpace) {
            $tokens->insertAt($assignmentIndex, new Token([T_WHITESPACE, ' ']));
        }

        ++$assignmentIndex;
        $tokens->insertAt($assignmentIndex, 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\Fixer\CastNotation;

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\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 */
final class CastSpacesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    /**
     * @internal
     */
    const INSIDE_CAST_SPACE_REPLACE_MAP = [
        ' ' => '',
        "\t" => '',
        "\n" => '',
        "\r" => '',
        "\0" => '',
        "\x0B" => '',
    ];

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'A single space or none should be between cast and variable.',
            [
                new CodeSample(
                    "<?php\n\$bar = ( string )  \$a;\n\$foo = (int)\$b;\n"
                ),
                new CodeSample(
                    "<?php\n\$bar = ( string )  \$a;\n\$foo = (int)\$b;\n",
                    ['space' => 'single']
                ),
                new CodeSample(
                    "<?php\n\$bar = ( string )  \$a;\n\$foo = (int) \$b;\n",
                    ['space' => 'none']
                ),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // should be run after the NoShortBoolCastFixer
        return -10;
    }

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

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

            $tokens[$index] = new Token([
                $token->getId(),
                strtr($token->getContent(), self::INSIDE_CAST_SPACE_REPLACE_MAP),
            ]);

            if ('single' === $this->configuration['space']) {
                // force single whitespace after cast token:
                if ($tokens[$index + 1]->isWhitespace(" \t")) {
                    // - if next token is whitespaces that contains only spaces and tabs - override next token with single space
                    $tokens[$index + 1] = new Token([T_WHITESPACE, ' ']);
                } elseif (!$tokens[$index + 1]->isWhitespace()) {
                    // - if next token is not whitespaces that contains spaces, tabs and new lines - append single space to current token
                    $tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' ']));
                }

                continue;
            }

            // force no whitespace after cast token:
            if ($tokens[$index + 1]->isWhitespace()) {
                $tokens->clearAt($index + 1);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolver([
            (new FixerOptionBuilder('space', 'spacing to apply between cast and variable.'))
                ->setAllowedValues(['none', 'single'])
                ->setDefault('single')
                ->getOption(),
        ]);
    }
}


<?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\Doctrine\Annotation;

use Doctrine\Common\Annotations\DocLexer;

/**
 * A Doctrine annotation token.
 *
 * @internal
 */
final class Token
{
    /**
     * @var int
     */
    private $type;

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

    /**
     * @param int    $type    The type
     * @param string $content The content
     */
    public function __construct($type = DocLexer::T_NONE, $content = '')
    {
        $this->type = $type;
        $this->content = $content;
    }

    /**
     * @return int
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @param int $type
     */
    public function setType($type)
    {
        $this->type = $type;
    }

    /**
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * @param string $content
     */
    public function setContent($content)
    {
        $this->content = $content;
    }

    /**
     * Returns whether the token type is one of the given types.
     *
     * @param int|int[] $types
     *
     * @return bool
     */
    public function isType($types)
    {
        if (!\is_array($types)) {
            $types = [$types];
        }

        return \in_array($this->getType(), $types, true);
    }

    /**
     * Overrides the content with an empty string.
     */
    public function clear()
    {
        $this->setContent('');
    }
}


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

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

/**
 * @author Carlos Cirello <carlos.cirello.nl@gmail.com>
 * @author Graham Campbell <graham@alt-three.com>
 *
 * @deprecated
 */
final class AlignEqualsFixerHelper extends AbstractAlignFixerHelper
{
    public function __construct()
    {
        @trigger_error(
            sprintf(
                'The "%s" class is deprecated. You should stop using it, as it will be removed in 3.0 version.',
                __CLASS__
            ),
            E_USER_DEPRECATED
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function injectAlignmentPlaceholders(Tokens $tokens, $startAt, $endAt)
    {
        for ($index = $startAt; $index < $endAt; ++$index) {
            $token = $tokens[$index];

            if ($token->equals('=')) {
                $tokens[$index] = new Token(sprintf(self::ALIGNABLE_PLACEHOLDER, $this->deepestLevel).$token->getContent());

                continue;
            }

            if ($token->isGivenKind(T_FUNCTION)) {
                ++$this->deepestLevel;

                continue;
            }

            if ($token->equals('(')) {
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);

                continue;
            }

            if ($token->equals('[')) {
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $index);

                continue;
            }

            if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_OPEN)) {
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index);

                continue;
            }
        }
    }
}


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

use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author SpacePossum
 */
abstract class AbstractNoUselessElseFixer extends AbstractFixer
{
    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // should be run before NoWhitespaceInBlankLineFixer, NoExtraBlankLinesFixer, BracesFixer and after NoEmptyStatementFixer.
        return 25;
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     *
     * @return bool
     */
    protected function isSuperfluousElse(Tokens $tokens, $index)
    {
        $previousBlockStart = $index;
        do {
            // Check if all 'if', 'else if ' and 'elseif' blocks above this 'else' always end,
            // if so this 'else' is overcomplete.
            list($previousBlockStart, $previousBlockEnd) = $this->getPreviousBlock($tokens, $previousBlockStart);

            // short 'if' detection
            $previous = $previousBlockEnd;
            if ($tokens[$previous]->equals('}')) {
                $previous = $tokens->getPrevMeaningfulToken($previous);
            }

            if (
                !$tokens[$previous]->equals(';') ||                              // 'if' block doesn't end with semicolon, keep 'else'
                $tokens[$tokens->getPrevMeaningfulToken($previous)]->equals('{') // empty 'if' block, keep 'else'
            ) {
                return false;
            }

            $candidateIndex = $tokens->getPrevTokenOfKind(
                $previous,
                [
                    ';',
                    [T_BREAK],
                    [T_CLOSE_TAG],
                    [T_CONTINUE],
                    [T_EXIT],
                    [T_GOTO],
                    [T_IF],
                    [T_RETURN],
                    [T_THROW],
                ]
            );

            if (
                null === $candidateIndex
                || $tokens[$candidateIndex]->equalsAny([';', [T_CLOSE_TAG], [T_IF]])
                || $this->isInConditional($tokens, $candidateIndex, $previousBlockStart)
                || $this->isInConditionWithoutBraces($tokens, $candidateIndex, $previousBlockStart)
            ) {
                return false;
            }

            // implicit continue, i.e. delete candidate
        } while (!$tokens[$previousBlockStart]->isGivenKind(T_IF));

        return true;
    }

    /**
     * Return the first and last token index of the previous block.
     *
     * [0] First is either T_IF, T_ELSE or T_ELSEIF
     * [1] Last is either '}' or ';' / T_CLOSE_TAG for short notation blocks
     *
     * @param Tokens $tokens
     * @param int    $index  T_IF, T_ELSE, T_ELSEIF
     *
     * @return int[]
     */
    private function getPreviousBlock(Tokens $tokens, $index)
    {
        $close = $previous = $tokens->getPrevMeaningfulToken($index);
        // short 'if' detection
        if ($tokens[$close]->equals('}')) {
            $previous = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $close);
        }

        $open = $tokens->getPrevTokenOfKind($previous, [[T_IF], [T_ELSE], [T_ELSEIF]]);
        if ($tokens[$open]->isGivenKind(T_IF)) {
            $elseCandidate = $tokens->getPrevMeaningfulToken($open);
            if ($tokens[$elseCandidate]->isGivenKind(T_ELSE)) {
                $open = $elseCandidate;
            }
        }

        return [$open, $close];
    }

    /**
     * @param Tokens $tokens
     * @param int    $index           Index of the token to check
     * @param int    $lowerLimitIndex Lower limit index. Since the token to check will always be in a conditional we must stop checking at this index
     *
     * @return bool
     */
    private function isInConditional(Tokens $tokens, $index, $lowerLimitIndex)
    {
        $candidateIndex = $tokens->getPrevTokenOfKind($index, [')', ';', ':']);
        if ($tokens[$candidateIndex]->equals(':')) {
            return true;
        }

        if (!$tokens[$candidateIndex]->equals(')')) {
            return false; // token is ';' or close tag
        }

        // token is always ')' here.
        // If it is part of the condition the token is always in, return false.
        // If it is not it is a nested condition so return true
        $open = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $candidateIndex);

        return $tokens->getPrevMeaningfulToken($open) > $lowerLimitIndex;
    }

    /**
     * For internal use only, as it is not perfect.
     *
     * Returns if the token at given index is part of a if/elseif/else statement
     * without {}. Assumes not passing the last `;`/close tag of the statement, not
     * out of range index, etc.
     *
     * @param Tokens $tokens
     * @param int    $index           Index of the token to check
     * @param int    $lowerLimitIndex
     *
     * @return bool
     */
    private function isInConditionWithoutBraces(Tokens $tokens, $index, $lowerLimitIndex)
    {
        do {
            if ($tokens[$index]->isComment() || $tokens[$index]->isWhitespace()) {
                $index = $tokens->getPrevMeaningfulToken($index);
            }

            $token = $tokens[$index];
            if ($token->isGivenKind([T_IF, T_ELSEIF, T_ELSE])) {
                return true;
            }

            if ($token->equals(';', '}')) {
                return false;
            }
            if ($token->equals('{')) {
                $index = $tokens->getPrevMeaningfulToken($index);

                // OK if belongs to: for, do, while, foreach
                // Not OK if belongs to: if, else, elseif
                if ($tokens[$index]->isGivenKind(T_DO)) {
                    --$index;

                    continue;
                }

                if (!$tokens[$index]->equals(')')) {
                    return false; // like `else {`
                }

                $index = $tokens->findBlockStart(
                    Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
                    $index
                );

                $index = $tokens->getPrevMeaningfulToken($index);
                if ($tokens[$index]->isGivenKind([T_IF, T_ELSEIF])) {
                    return false;
                }
            } elseif ($token->equals(')')) {
                $type = Tokens::detectBlockType($token);
                $index = $tokens->findBlockStart(
                    $type['type'],
                    $index
                );

                $index = $tokens->getPrevMeaningfulToken($index);
            } else {
                --$index;
            }
        } while ($index > $lowerLimitIndex);

        return false;
    }
}


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

use PhpCsFixer\FileReader;
use PhpCsFixer\FileRemoval;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;

/**
 * Handle PHP code linting using separated process of `php -l _file_`.
 *
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * @internal
 */
final class ProcessLinter implements LinterInterface
{
    /**
     * @var FileRemoval
     */
    private $fileRemoval;

    /**
     * @var ProcessLinterProcessBuilder
     */
    private $processBuilder;

    /**
     * Temporary file for code linting.
     *
     * @var null|string
     */
    private $temporaryFile;

    /**
     * @param null|string $executable PHP executable, null for autodetection
     */
    public function __construct($executable = null)
    {
        if (null === $executable) {
            $executableFinder = new PhpExecutableFinder();
            $executable = $executableFinder->find(false);

            if (false === $executable) {
                throw new UnavailableLinterException('Cannot find PHP executable.');
            }

            if ('phpdbg' === \PHP_SAPI) {
                if (false === strpos($executable, 'phpdbg')) {
                    throw new UnavailableLinterException('Automatically found PHP executable is non-standard phpdbg. Could not find proper PHP executable.');
                }

                // automatically found executable is `phpdbg`, let us try to fallback to regular `php`
                $executable = str_replace('phpdbg', 'php', $executable);

                if (!is_executable($executable)) {
                    throw new UnavailableLinterException('Automatically found PHP executable is phpdbg. Could not find proper PHP executable.');
                }
            }
        }

        $this->processBuilder = new ProcessLinterProcessBuilder($executable);

        $this->fileRemoval = new FileRemoval();
    }

    public function __destruct()
    {
        if (null !== $this->temporaryFile) {
            $this->fileRemoval->delete($this->temporaryFile);
        }
    }

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

    /**
     * {@inheritdoc}
     */
    public function lintFile($path)
    {
        return new ProcessLintingResult($this->createProcessForFile($path));
    }

    /**
     * {@inheritdoc}
     */
    public function lintSource($source)
    {
        return new ProcessLintingResult($this->createProcessForSource($source));
    }

    /**
     * @param string $path path to file
     *
     * @return Process
     */
    private function createProcessForFile($path)
    {
        // in case php://stdin
        if (!is_file($path)) {
            return $this->createProcessForSource(FileReader::createSingleton()->read($path));
        }

        $process = $this->processBuilder->build($path);
        $process->setTimeout(10);
        $process->start();

        return $process;
    }

    /**
     * Create process that lint PHP code.
     *
     * @param string $source code
     *
     * @return Process
     */
    private function createProcessForSource($source)
    {
        if (null === $this->temporaryFile) {
            $this->temporaryFile = tempnam('.', 'cs_fixer_tmp_');
            $this->fileRemoval->observe($this->temporaryFile);
        }

        if (false === @file_put_contents($this->temporaryFile, $source)) {
            throw new IOException(sprintf('Failed to write file "%s".', $this->temporaryFile), 0, null, $this->temporaryFile);
        }

        return $this->createProcessForFile($this->temporaryFile);
    }
}


<?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\AbstractPsrAutoloadingFixer;
use PhpCsFixer\FixerDefinition\FileSpecificCodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 * @author Bram Gotink <bram@gotink.me>
 * @author Graham Campbell <graham@alt-three.com>
 */
final class Psr4Fixer extends AbstractPsrAutoloadingFixer
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Class names should match the file name.',
            [
                new FileSpecificCodeSample(
                    '<?php
namespace PhpCsFixer\FIXER\Basic;
class InvalidName {}
',
                    new \SplFileInfo(__FILE__)
                ),
            ],
            null,
            'This fixer may change your class name, which will break the code that is depended on old name.'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $isNamespaceFound = false;
        $classyIndex = 0;
        $classyName = null;

        foreach ($tokens as $index => $token) {
            if ($token->isGivenKind(T_NAMESPACE)) {
                if ($isNamespaceFound) {
                    return;
                }

                $isNamespaceFound = true;
            } elseif ($token->isClassy()) {
                $prevToken = $tokens[$tokens->getPrevMeaningfulToken($index)];
                if ($prevToken->isGivenKind(T_NEW)) {
                    continue;
                }

                if (null !== $classyName) {
                    return;
                }

                $classyIndex = $tokens->getNextMeaningfulToken($index);
                $classyName = $tokens[$classyIndex]->getContent();
            }
        }

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

        if ($isNamespaceFound) {
            $filename = basename(str_replace('\\', '/', $file->getRealPath()), '.php');

            if ($classyName !== $filename) {
                $tokens[$classyIndex] = new Token([T_STRING, $filename]);
            }
        } else {
            $normClass = str_replace('_', '/', $classyName);
            $filename = substr(str_replace('\\', '/', $file->getRealPath()), -\strlen($normClass) - 4, -4);

            if ($normClass !== $filename && strtolower($normClass) === strtolower($filename)) {
                $tokens[$classyIndex] = new Token([T_STRING, str_replace('/', '_', $filename)]);
            }
        }
    }
}


<?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\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Options;

/**
 * @author SpacePossum
 */
final class PhpdocReturnSelfReferenceFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    private static $toTypes = [
        '$this',
        'static',
        'self',
    ];

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'The type of `@return` annotations of methods returning a reference to itself must the configured one.',
            [
                new CodeSample(
                    '<?php
class Sample
{
    /**
     * @return this
     */
    public function test1()
    {
        return $this;
    }

    /**
     * @return $self
     */
    public function test2()
    {
        return $this;
    }
}
'
                ),
                new CodeSample(
                    '<?php
class Sample
{
    /**
     * @return this
     */
    public function test1()
    {
        return $this;
    }

    /**
     * @return $self
     */
    public function test2()
    {
        return $this;
    }
}
',
                    ['replacements' => ['this' => 'self']]
                ),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return \count($tokens) > 10 && $tokens->isTokenKindFound(T_DOC_COMMENT) && $tokens->isAnyTokenKindsFound([T_CLASS, T_INTERFACE]);
    }

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

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $tokensAnalyzer = new TokensAnalyzer($tokens);
        foreach ($tokensAnalyzer->getClassyElements() as $index => $element) {
            if ('method' === $element['type']) {
                $this->fixMethod($tokens, $index);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        $default = [
            'this' => '$this',
            '@this' => '$this',
            '$self' => 'self',
            '@self' => 'self',
            '$static' => 'static',
            '@static' => 'static',
        ];

        return new FixerConfigurationResolverRootless('replacements', [
            (new FixerOptionBuilder('replacements', 'Mapping between replaced return types with new ones.'))
                ->setAllowedTypes(['array'])
                ->setNormalizer(static function (Options $options, $value) use ($default) {
                    $normalizedValue = [];
                    foreach ($value as $from => $to) {
                        if (\is_string($from)) {
                            $from = strtolower($from);
                        }

                        if (!isset($default[$from])) {
                            throw new InvalidOptionsException(sprintf(
                                'Unknown key "%s", expected any of "%s".',
                                \is_object($from) ? \get_class($from) : \gettype($from).(\is_resource($from) ? '' : '#'.$from),
                                implode('", "', array_keys($default))
                            ));
                        }

                        if (!\in_array($to, self::$toTypes, true)) {
                            throw new InvalidOptionsException(sprintf(
                                'Unknown value "%s", expected any of "%s".',
                                \is_object($to) ? \get_class($to) : \gettype($to).(\is_resource($to) ? '' : '#'.$to),
                                implode('", "', self::$toTypes)
                            ));
                        }

                        $normalizedValue[$from] = $to;
                    }

                    return $normalizedValue;
                })
                ->setDefault($default)
                ->getOption(),
        ], $this->getName());
    }

    /**
     * @param Tokens $tokens
     * @param int    $index
     */
    private function fixMethod(Tokens $tokens, $index)
    {
        static $methodModifiers = [T_STATIC, T_FINAL, T_ABSTRACT, T_PRIVATE, T_PROTECTED, T_PUBLIC];

        // find PHPDoc of method (if any)
        do {
            $tokenIndex = $tokens->getPrevMeaningfulToken($index);
            if (!$tokens[$tokenIndex]->isGivenKind($methodModifiers)) {
                break;
            }

            $index = $tokenIndex;
        } while (true);

        $docIndex = $tokens->getPrevNonWhitespace($index);
        if (!$tokens[$docIndex]->isGivenKind(T_DOC_COMMENT)) {
            return;
        }

        // find @return
        $docBlock = new DocBlock($tokens[$docIndex]->getContent());
        $returnsBlock = $docBlock->getAnnotationsOfType('return');

        if (!\count($returnsBlock)) {
            return; // no return annotation found
        }

        $returnsBlock = $returnsBlock[0];
        $types = $returnsBlock->getTypes();

        if (!\count($types)) {
            return; // no return type(s) found
        }

        $newTypes = [];
        foreach ($types as $type) {
            $lower = strtolower($type);
            $newTypes[] = isset($this->configuration['replacements'][$lower]) ? $this->configuration['replacements'][$lower] : $type;
        }

        if ($types === $newTypes) {
            return;
        }

        $returnsBlock->setTypes($newTypes);
        $tokens[$docIndex] = new Token([T_DOC_COMMENT, $docBlock->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\Report;

use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * @author Kévin Gomez <contact@kevingomez.fr>
 *
 * @internal
 */
final class CheckstyleReporter implements ReporterInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFormat()
    {
        return 'checkstyle';
    }

    /**
     * {@inheritdoc}
     */
    public function generate(ReportSummary $reportSummary)
    {
        if (!\extension_loaded('dom')) {
            throw new \RuntimeException('Cannot generate report! `ext-dom` is not available!');
        }

        $dom = new \DOMDocument('1.0', 'UTF-8');
        $checkstyles = $dom->appendChild($dom->createElement('checkstyle'));

        foreach ($reportSummary->getChanged() as $filePath => $fixResult) {
            /** @var \DOMElement $file */
            $file = $checkstyles->appendChild($dom->createElement('file'));
            $file->setAttribute('name', $filePath);

            foreach ($fixResult['appliedFixers'] as $appliedFixer) {
                $error = $this->createError($dom, $appliedFixer);
                $file->appendChild($error);
            }
        }

        $dom->formatOutput = true;

        return $reportSummary->isDecoratedOutput() ? OutputFormatter::escape($dom->saveXML()) : $dom->saveXML();
    }

    /**
     * @param \DOMDocument $dom
     * @param string       $appliedFixer
     *
     * @return \DOMElement
     */
    private function createError(\DOMDocument $dom, $appliedFixer)
    {
        $error = $dom->createElement('error');
        $error->setAttribute('severity', 'warning');
        $error->setAttribute('source', 'PHP-CS-Fixer.'.$appliedFixer);
        $error->setAttribute('message', 'Found violation(s) of type: '.$appliedFixer);

        return $error;
    }
}


<?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\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverRootless;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Options;

/**
 * Case sensitive tag replace fixer (does not process inline tags like {@inheritdoc}).
 *
 * @author Graham Campbell <graham@alt-three.com>
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 * @author SpacePossum
 */
final class PhpdocNoAliasTagFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
{
    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isTokenKindFound(T_DOC_COMMENT);
    }

    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'No alias PHPDoc tags should be used.',
            [
                new CodeSample(
                    '<?php
/**
 * @property string $foo
 * @property-read string $bar
 *
 * @link baz
 */
final class Example
{
}
'
                ),
                new CodeSample(
                    '<?php
/**
 * @property string $foo
 * @property-read string $bar
 *
 * @link baz
 */
final class Example
{
}
',
                    ['replacements' => ['link' => 'website']]
                ),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // must be run before PhpdocAddMissingParamAnnotationFixer
        return 11;
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $searchFor = array_keys($this->configuration['replacements']);

        foreach ($tokens as $index => $token) {
            if (!$token->isGivenKind(T_DOC_COMMENT)) {
                continue;
            }

            $doc = new DocBlock($token->getContent());
            $annotations = $doc->getAnnotationsOfType($searchFor);

            if (empty($annotations)) {
                continue;
            }

            foreach ($annotations as $annotation) {
                $annotation->getTag()->setName($this->configuration['replacements'][$annotation->getTag()->getName()]);
            }

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

    /**
     * {@inheritdoc}
     */
    protected function createConfigurationDefinition()
    {
        return new FixerConfigurationResolverRootless('replacements', [
            (new FixerOptionBuilder('replacements', 'Mapping between replaced annotations with new ones.'))
                ->setAllowedTypes(['array'])
                ->setNormalizer(static function (Options $options, $value) {
                    $normalizedValue = [];

                    foreach ($value as $from => $to) {
                        if (!\is_string($from)) {
                            throw new InvalidOptionsException('Tag to replace must be a string.');
                        }

                        if (!\is_string($to)) {
                            throw new InvalidOptionsException(sprintf(
                                'Tag to replace to from "%s" must be a string.',
                                $from
                            ));
                        }

                        if (1 !== Preg::match('#^\S+$#', $to) || false !== strpos($to, '*/')) {
                            throw new InvalidOptionsException(sprintf(
                                'Tag "%s" cannot be replaced by invalid tag "%s".',
                                $from,
                                $to
                            ));
                        }

                        $normalizedValue[trim($from)] = trim($to);
                    }

                    foreach ($normalizedValue as $from => $to) {
                        if (isset($normalizedValue[$to])) {
                            throw new InvalidOptionsException(sprintf(
                                'Cannot change tag "%1$s" to tag "%2$s", as the tag "%2$s" is configured to be replaced to "%3$s".',
                                $from,
                                $to,
                                $normalizedValue[$to]
                            ));
                        }
                    }

                    return $normalizedValue;
                })
                ->setDefault([
                    'property-read' => 'property',
                    'property-write' => 'property',
                    'type' => 'var',
                    'link' => 'see',
                ])
                ->getOption(),
        ], $this->getName());
    }
}


<?php

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

namespace PhpCsFixer\Tokenizer\Transformer;

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

/**
 * Transform `:` operator into CT::T_TYPE_COLON in `function foo() : int {}`.
 *
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * @internal
 */
final class TypeColonTransformer extends AbstractTransformer
{
    /**
     * {@inheritdoc}
     */
    public function getCustomTokens()
    {
        return [CT::T_TYPE_COLON];
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        // needs to run after ReturnRefTransformer and UseTransformer
        return -10;
    }

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

    /**
     * {@inheritdoc}
     */
    public function process(Tokens $tokens, Token $token, $index)
    {
        if (!$token->equals(':')) {
            return;
        }

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

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

        $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $endIndex);
        $prevIndex = $tokens->getPrevMeaningfulToken($startIndex);
        $prevToken = $tokens[$prevIndex];

        // if this could be a function name we need to take one more step
        if ($prevToken->isGivenKind(T_STRING)) {
            $prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
            $prevToken = $tokens[$prevIndex];
        }

        if ($prevToken->isGivenKind([T_FUNCTION, CT::T_RETURN_REF, CT::T_USE_LAMBDA])) {
            $tokens[$index] = new Token([CT::T_TYPE_COLON, ':']);
        }
    }
}