Current File : /var/www/vinorea/modules/psxdesign/vendor/friendsofphp/php-cs-fixer/src/Tokenizer/Tokens.php
<?php

declare(strict_types=1);

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

namespace PhpCsFixer\Tokenizer;

use PhpCsFixer\Preg;

/**
 * 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>
 *
 * @extends \SplFixedArray<Token>
 *
 * @final
 */
class Tokens extends \SplFixedArray
{
    public const BLOCK_TYPE_PARENTHESIS_BRACE = 1;
    public const BLOCK_TYPE_CURLY_BRACE = 2;
    public const BLOCK_TYPE_INDEX_SQUARE_BRACE = 3;
    public const BLOCK_TYPE_ARRAY_SQUARE_BRACE = 4;
    public const BLOCK_TYPE_DYNAMIC_PROP_BRACE = 5;
    public const BLOCK_TYPE_DYNAMIC_VAR_BRACE = 6;
    public const BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE = 7;
    public const BLOCK_TYPE_GROUP_IMPORT_BRACE = 8;
    public const BLOCK_TYPE_DESTRUCTURING_SQUARE_BRACE = 9;
    public const BLOCK_TYPE_BRACE_CLASS_INSTANTIATION = 10;
    public const BLOCK_TYPE_ATTRIBUTE = 11;

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

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

    /**
     * Cache of block ends. 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 = [];

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

    /**
     * Clear cache - one position or all of them.
     *
     * @param null|string $key position to clear, when null clear all
     */
    public static function clearCache(?string $key = null): void
    {
        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): ?array
    {
        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];
            }
        }

        return null;
    }

    /**
     * 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
     */
    public static function fromArray($array, $saveIndexes = null): self
    {
        $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
        $tokens->clearChanged();

        return $tokens;
    }

    /**
     * Create token collection directly from code.
     *
     * @param string $code PHP code
     */
    public static function fromCode(string $code): self
    {
        $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;
    }

    public static function getBlockEdgeDefinitions(): array
    {
        $definitions = [
            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, ')'],
            ],
        ];

        // @TODO: drop condition when PHP 8.0+ is required
        if (\defined('T_ATTRIBUTE')) {
            $definitions[self::BLOCK_TYPE_ATTRIBUTE] = [
                'start' => [T_ATTRIBUTE, '#['],
                'end' => [CT::T_ATTRIBUTE_CLOSE, ']'],
            ];
        }

        return $definitions;
    }

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

            return parent::setSize($size);
        }

        return true;
    }

    /**
     * Unset collection item.
     *
     * @param int $index
     */
    public function offsetUnset($index): void
    {
        $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): void
    {
        $this->blockStartCache = [];
        $this->blockEndCache = [];

        if (!isset($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(): void
    {
        $this->changed = false;
    }

    /**
     * Clear empty tokens.
     *
     * Empty tokens can occur e.g. after calling clear on item of collection.
     */
    public function clearEmptyTokens(): void
    {
        $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]; // @phpstan-ignore-line as we know that index exists
            }
        }

        $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(int $index, int $indexOffset, string $whitespace): bool
    {
        $removeLastCommentLine = static function (self $tokens, int $index, int $indexOffset, string $whitespace): string {
            $token = $tokens[$index];

            if (1 === $indexOffset && $token->isGivenKind(T_OPEN_TAG)) {
                if (str_starts_with($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
     *
     * @return int index of closing brace
     */
    public function findBlockEnd(int $type, int $searchIndex): int
    {
        return $this->findOppositeBlockEdge($type, $searchIndex, true);
    }

    /**
     * @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(int $type, int $searchIndex): int
    {
        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, int $start = 0, ?int $end = null): array
    {
        if (null === $end) {
            $end = $this->count();
        }

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

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

        $possibleKinds = array_filter($possibleKinds, function ($kind): bool {
            return $this->isTokenKindFound($kind);
        });

        if (\count($possibleKinds) > 0) {
            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];
    }

    public function generateCode(): string
    {
        $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
     */
    public function generatePartialCode(int $start, int $end): string
    {
        $code = '';

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

        return $code;
    }

    /**
     * Get hash of code.
     */
    public function getCodeHash(): string
    {
        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
     */
    public function getNextNonWhitespace(int $index, ?string $whitespaces = null): ?int
    {
        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
     */
    public function getNextTokenOfKind(int $index, array $tokens = [], bool $caseSensitive = true): ?int
    {
        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
     */
    public function getNonWhitespaceSibling(int $index, int $direction, ?string $whitespaces = null): ?int
    {
        while (true) {
            $index += $direction;

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

            if (!$this[$index]->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
     */
    public function getPrevNonWhitespace(int $index, ?string $whitespaces = null): ?int
    {
        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
     */
    public function getPrevTokenOfKind(int $index, array $tokens = [], bool $caseSensitive = true): ?int
    {
        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
     */
    public function getTokenOfKindSibling(int $index, int $direction, array $tokens = [], bool $caseSensitive = true): ?int
    {
        $tokens = array_filter($tokens, function ($token): bool {
            return $this->isTokenKindFound($this->extractTokenKind($token));
        });

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

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

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

            if ($this[$index]->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
     */
    public function getTokenNotOfKindSibling(int $index, int $direction, array $tokens = []): ?int
    {
        return $this->getTokenNotOfKind(
            $index,
            $direction,
            function (int $a) use ($tokens): bool {
                return $this[$a]->equalsAny($tokens);
            }
        );
    }

    /**
     * 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 $kinds     possible tokens kinds
     */
    public function getTokenNotOfKindsSibling(int $index, int $direction, array $kinds = []): ?int
    {
        return $this->getTokenNotOfKind(
            $index,
            $direction,
            function (int $index) use ($kinds): bool {
                return $this[$index]->isGivenKind($kinds);
            }
        );
    }

    /**
     * Get index for closest sibling token that is not a whitespace, comment or attribute.
     *
     * @param int $index     token index
     * @param int $direction direction for looking, +1 or -1
     */
    public function getMeaningfulTokenSibling(int $index, int $direction): ?int
    {
        return $this->getTokenNotOfKindsSibling(
            $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
     */
    public function getNonEmptySibling(int $index, int $direction): ?int
    {
        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
     */
    public function getNextMeaningfulToken(int $index): ?int
    {
        return $this->getMeaningfulTokenSibling($index, 1);
    }

    /**
     * Get index for closest previous token that is not a whitespace or comment.
     *
     * @param int $index token index
     */
    public function getPrevMeaningfulToken(int $index): ?int
    {
        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 null|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, int $start = 0, ?int $end = null, $caseSensitive = true): ?array
    {
        $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;
        }

        $nonMeaningFullKind = [T_COMMENT, T_DOC_COMMENT, T_WHITESPACE];

        // 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->isGivenKind($nonMeaningFullKind)) {
                throw new \InvalidArgumentException(sprintf('Non-meaningful token at position: "%s".', $key));
            }

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

        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 ($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;
            }
        }

        return null;
    }

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

        $this->insertSlices([$index => $items]);
    }

    /**
     * Insert a slices or individual Tokens into multiple places in a single run.
     *
     * This approach is kind-of an experiment - it's proven to improve performance a lot for big files that needs plenty of new tickets to be inserted,
     * like edge case example of 3.7h vs 4s (https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/3996#issuecomment-455617637),
     * yet at same time changing a logic of fixers in not-always easy way.
     *
     * To be discussed:
     * - should we always aim to use this method?
     * - should we deprecate `insertAt` method ?
     *
     * The `$slices` parameter is an assoc array, in which:
     * - index: starting point for inserting of individual slice, with indexes being relatives to original array collection before any Token inserted
     * - value under index: a slice of Tokens to be inserted
     *
     * @internal
     *
     * @param array<int, array<Token>|Token|Tokens> $slices
     */
    public function insertSlices(array $slices): void
    {
        $itemsCount = 0;
        foreach ($slices as $slice) {
            $itemsCount += \is_array($slice) || $slice instanceof self ? \count($slice) : 1;
        }

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

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

        krsort($slices);

        $insertBound = $oldSize - 1;

        // 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.
        foreach ($slices as $index => $slice) {
            $slice = \is_array($slice) || $slice instanceof self ? $slice : [$slice];
            $sliceCount = \count($slice);

            for ($i = $insertBound; $i >= $index; --$i) {
                $oldItem = parent::offsetExists($i) ? parent::offsetGet($i) : new Token('');
                parent::offsetSet($i + $itemsCount, $oldItem);
            }

            $insertBound = $index - $sliceCount;
            $itemsCount -= $sliceCount;

            foreach ($slice as $indexItem => $item) {
                if ('' === $item->getContent()) {
                    throw new \InvalidArgumentException('Must not add empty token to collection.');
                }

                $this->registerFoundToken($item);
                $newOffset = $index + $itemsCount + $indexItem;
                parent::offsetSet($newOffset, $item);
            }
        }
    }

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

        return false;
    }

    public function isEmptyAt(int $index): bool
    {
        $token = $this[$index];

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

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

    /**
     * Override tokens at given range.
     *
     * @param int                 $indexStart start overriding index
     * @param int                 $indexEnd   end overriding index
     * @param array<Token>|Tokens $items      tokens to insert
     */
    public function overrideRange(int $indexStart, int $indexEnd, iterable $items): void
    {
        $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 fewer tokens than passed range contains then clear
        // not needed tokens.
        if ($itemsCount < $indexToChange) {
            $this->clearRange($indexStart + $itemsCount, $indexEnd);
        }
    }

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

    /**
     * @param null|string $whitespaces optional whitespaces characters for Token::isWhitespace
     */
    public function removeTrailingWhitespace(int $index, ?string $whitespaces = null): void
    {
        $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(string $code): void
    {
        // 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 = token_get_all($code, TOKEN_PARSE);

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

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

        $this->applyTransformers();

        $this->foundTokenKinds = [];

        foreach ($this as $token) {
            $this->registerFoundToken($token);
        }

        if (\PHP_VERSION_ID < 80000) {
            $this->rewind();
        }

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

    public function toJson(): string
    {
        $output = new \SplFixedArray(\count($this));

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

        if (\PHP_VERSION_ID < 80000) {
            $this->rewind();
        }

        return json_encode($output, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK);
    }

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

        return true;
    }

    /**
     * Check if any token kind given as argument is found.
     */
    public function isAnyTokenKindsFound(array $tokenKinds): bool
    {
        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
     */
    public function isTokenKindFound($tokenKind): bool
    {
        return !empty($this->foundTokenKinds[$tokenKind]);
    }

    /**
     * @param int|string $tokenKind
     */
    public function countTokenKind($tokenKind): int
    {
        return $this->foundTokenKinds[$tokenKind] ?? 0;
    }

    /**
     * Clear tokens in the given range.
     */
    public function clearRange(int $indexStart, int $indexEnd): void
    {
        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.
     */
    public function isMonolithicPhp(): bool
    {
        $size = $this->count();

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

        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
     */
    public function isPartialCodeMultiline(int $start, int $end): bool
    {
        for ($i = $start; $i <= $end; ++$i) {
            if (str_contains($this[$i]->getContent(), "\n")) {
                return true;
            }
        }

        return false;
    }

    public function hasAlternativeSyntax(): bool
    {
        return $this->isAnyTokenKindsFound([
            T_ENDDECLARE,
            T_ENDFOR,
            T_ENDFOREACH,
            T_ENDIF,
            T_ENDSWITCH,
            T_ENDWHILE,
        ]);
    }

    public function clearTokenAndMergeSurroundingWhitespace(int $index): void
    {
        $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);
    }

    /**
     * @internal
     */
    protected function applyTransformers(): void
    {
        $transformers = Transformers::createSingleton();
        $transformers->transform($this);
    }

    private function removeWhitespaceSafely(int $index, int $direction, ?string $whitespaces = null): void
    {
        $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() && !str_starts_with($this[$whitespaceIndex - 1]->getContent(), '/*')) {
                [, $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(int $type, int $searchIndex, bool $findEnd): int
    {
        $blockEdgeDefinitions = self::getBlockEdgeDefinitions();

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

        if ($findEnd && isset($this->blockStartCache[$searchIndex])) {
            return $this->blockStartCache[$searchIndex];
        }
        if (!$findEnd && 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) {
            [$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;
                }
            }
        }

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

        if ($startIndex < $index) {
            $this->blockStartCache[$startIndex] = $index;
            $this->blockEndCache[$index] = $startIndex;
        } else {
            $this->blockStartCache[$index] = $startIndex;
            $this->blockEndCache[$startIndex] = $index;
        }

        return $index;
    }

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

    /**
     * Get cache value for given key.
     *
     * @param string $key item key
     */
    private static function getCache(string $key): self
    {
        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
     */
    private static function hasCache(string $key): bool
    {
        return isset(self::$cache[$key]);
    }

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

    /**
     * Change code hash.
     *
     * Remove old cache and set new one.
     *
     * @param string $codeHash new code hash
     */
    private function changeCodeHash(string $codeHash): void
    {
        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): void
    {
        // 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): void
    {
        // 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)
        ;
    }

    /**
     * @param int $index     token index
     * @param int $direction direction for looking, +1 or -1
     */
    private function getTokenNotOfKind(int $index, int $direction, callable $filter): ?int
    {
        while (true) {
            $index += $direction;

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

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

            return $index;
        }
    }
}
¿Qué es la limpieza dental de perros? - Clínica veterinaria


Es la eliminación del sarro y la placa adherida a la superficie de los dientes mediante un equipo de ultrasonidos que garantiza la integridad de las piezas dentales a la vez que elimina en profundidad cualquier resto de suciedad.

A continuación se procede al pulido de los dientes mediante una fresa especial que elimina la placa bacteriana y devuelve a los dientes el aspecto sano que deben tener.

Una vez terminado todo el proceso, se mantiene al perro en observación hasta que se despierta de la anestesia, bajo la atenta supervisión de un veterinario.

¿Cada cuánto tiempo tengo que hacerle una limpieza dental a mi perro?

A partir de cierta edad, los perros pueden necesitar una limpieza dental anual o bianual. Depende de cada caso. En líneas generales, puede decirse que los perros de razas pequeñas suelen acumular más sarro y suelen necesitar una atención mayor en cuanto a higiene dental.


Riesgos de una mala higiene


Los riesgos más evidentes de una mala higiene dental en los perros son los siguientes:

  • Cuando la acumulación de sarro no se trata, se puede producir una inflamación y retracción de las encías que puede descalzar el diente y provocar caídas.
  • Mal aliento (halitosis).
  • Sarro perros
  • Puede ir a más
  • Las bacterias de la placa pueden trasladarse a través del torrente circulatorio a órganos vitales como el corazón ocasionando problemas de endocarditis en las válvulas. Las bacterias pueden incluso acantonarse en huesos (La osteomielitis es la infección ósea, tanto cortical como medular) provocando mucho dolor y una artritis séptica).

¿Cómo se forma el sarro?

El sarro es la calcificación de la placa dental. Los restos de alimentos, junto con las bacterias presentes en la boca, van a formar la placa bacteriana o placa dental. Si la placa no se retira, al mezclarse con la saliva y los minerales presentes en ella, reaccionará formando una costra. La placa se calcifica y se forma el sarro.

El sarro, cuando se forma, es de color blanquecino pero a medida que pasa el tiempo se va poniendo amarillo y luego marrón.

Síntomas de una pobre higiene dental
La señal más obvia de una mala salud dental canina es el mal aliento.

Sin embargo, a veces no es tan fácil de detectar
Y hay perros que no se dejan abrir la boca por su dueño. Por ejemplo…

Recientemente nos trajeron a la clínica a un perro que parpadeaba de un ojo y decía su dueño que le picaba un lado de la cara. Tenía molestias y dificultad para comer, lo que había llevado a sus dueños a comprarle comida blanda (que suele ser un poco más cara y llevar más contenido en grasa) durante medio año. Después de una exploración oftalmológica, nos dimos cuenta de que el ojo tenía una úlcera en la córnea probablemente de rascarse . Además, el canto lateral del ojo estaba inflamado. Tenía lo que en humanos llamamos flemón pero como era un perro de pelo largo, no se le notaba a simple vista. Al abrirle la boca nos llamó la atención el ver una muela llena de sarro. Le realizamos una radiografía y encontramos una fístula que llegaba hasta la parte inferior del ojo.

Le tuvimos que extraer la muela. Tras esto, el ojo se curó completamente con unos colirios y una lentilla protectora de úlcera. Afortunadamente, la úlcera no profundizó y no perforó el ojo. Ahora el perro come perfectamente a pesar de haber perdido una muela.

¿Cómo mantener la higiene dental de tu perro?
Hay varias maneras de prevenir problemas derivados de la salud dental de tu perro.

Limpiezas de dientes en casa
Es recomendable limpiar los dientes de tu perro semanal o diariamente si se puede. Existe una gran variedad de productos que se pueden utilizar:

Pastas de dientes.
Cepillos de dientes o dedales para el dedo índice, que hacen más fácil la limpieza.
Colutorios para echar en agua de bebida o directamente sobre el diente en líquido o en spray.

En la Clínica Tus Veterinarios enseñamos a nuestros clientes a tomar el hábito de limpiar los dientes de sus perros desde que son cachorros. Esto responde a nuestro compromiso con la prevención de enfermedades caninas.

Hoy en día tenemos muchos clientes que limpian los dientes todos los días a su mascota, y como resultado, se ahorran el dinero de hacer limpiezas dentales profesionales y consiguen una mejor salud de su perro.


Limpiezas dentales profesionales de perros y gatos

Recomendamos hacer una limpieza dental especializada anualmente. La realizamos con un aparato de ultrasonidos que utiliza agua para quitar el sarro. Después, procedemos a pulir los dientes con un cepillo de alta velocidad y una pasta especial. Hacemos esto para proteger el esmalte.

La frecuencia de limpiezas dentales necesaria varía mucho entre razas. En general, las razas grandes tienen buena calidad de esmalte, por lo que no necesitan hacerlo tan a menudo e incluso pueden pasarse la vida sin requerir una limpieza. Sin embargo, razas pequeñas como el Yorkshire o el Maltés, deben hacérselas todos los años desde cachorros si se quiere conservar sus piezas dentales.

Otro factor fundamental es la calidad del pienso. Algunas marcas han diseñado croquetas que limpian la superficie del diente y de la muela al masticarse.

Ultrasonido para perros

¿Se necesita anestesia para las limpiezas dentales de perros y gatos?

La limpieza dental en perros no es una técnica que pueda practicarse sin anestesia general , aunque hay veces que los propietarios no quieren anestesiar y si tiene poco sarro y el perro es muy bueno se puede intentar…… , pero no se va a poder pulir ni acceder a todas la zona de la boca …. Además los limpiadores dentales van a irrigar agua y hay riesgo de aspiración a vías respiratorias si no se realiza una anestesia correcta con intubación traqueal . En resumen , sin anestesia no se va hacer una correcta limpieza dental.

Tampoco sirve la sedación ya que necesitamos que el animal esté totalmente quieto, y el veterinario tenga un acceso completo a todas sus piezas dentales y encías.

Alimentos para la limpieza dental

Hay que tener cierto cuidado a la hora de comprar determinados alimentos porque no todos son saludables. Algunos tienen demasiado contenido graso, que en exceso puede causar problemas cardiovasculares y obesidad.

Los mejores alimentos para los dientes son aquellos que están elaborados por empresas farmacéuticas y llevan componentes químicos con tratamientos específicos para el diente del perro. Esto implica no solo limpieza a través de la acción mecánica de morder sino también un tratamiento antibacteriano para prevenir el sarro.

Conclusión

Si eres como la mayoría de dueños, por falta de tiempo , es probable que no estés prestando la suficiente atención a la limpieza dental de tu perro. Por eso te animamos a que comiences a limpiar los dientes de tu perro y consideres atender a su higiene bucal con frecuencia.

Estas simples medidas pueden conllevar a que tu perro tenga una vida más larga y mucho más saludable.

Si te resulta imposible introducir un cepillo de dientes a tu perro en la boca, pásate con él por clínica Tus Veterinarios y te explicamos cómo hacerlo.

Necesitas hacer una limpieza dental profesional a tu mascota?
Llámanos al 622575274 o contacta con nosotros

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

¡Hola!