Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. AbstractControlStructureSpacing.php

class AbstractControlStructureSpacing

@internal

Hierarchy

  • class \SlevomatCodingStandard\Sniffs\ControlStructures\AbstractControlStructureSpacing implements \PHP_CodeSniffer\Sniffs\Sniff

Expanded class hierarchy of AbstractControlStructureSpacing

1 file declares its use of AbstractControlStructureSpacing
ParentCallSpacingSniff.php in vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/ParentCallSpacingSniff.php

File

vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/ControlStructures/AbstractControlStructureSpacing.php, line 58

Namespace

SlevomatCodingStandard\Sniffs\ControlStructures
View source
abstract class AbstractControlStructureSpacing implements Sniff {
    public const CODE_INCORRECT_LINES_COUNT_BEFORE_CONTROL_STRUCTURE = 'IncorrectLinesCountBeforeControlStructure';
    public const CODE_INCORRECT_LINES_COUNT_BEFORE_FIRST_CONTROL_STRUCTURE = 'IncorrectLinesCountBeforeFirstControlStructure';
    public const CODE_INCORRECT_LINES_COUNT_AFTER_CONTROL_STRUCTURE = 'IncorrectLinesCountAfterControlStructure';
    public const CODE_INCORRECT_LINES_COUNT_AFTER_LAST_CONTROL_STRUCTURE = 'IncorrectLinesCountAfterLastControlStructure';
    protected const KEYWORD_IF = 'if';
    protected const KEYWORD_DO = 'do';
    protected const KEYWORD_WHILE = 'while';
    protected const KEYWORD_FOR = 'for';
    protected const KEYWORD_FOREACH = 'foreach';
    protected const KEYWORD_SWITCH = 'switch';
    protected const KEYWORD_CASE = 'case';
    protected const KEYWORD_DEFAULT = 'default';
    protected const KEYWORD_TRY = 'try';
    protected const KEYWORD_PARENT = 'parent';
    protected const KEYWORD_GOTO = 'goto';
    protected const KEYWORD_BREAK = 'break';
    protected const KEYWORD_CONTINUE = 'continue';
    protected const KEYWORD_RETURN = 'return';
    protected const KEYWORD_THROW = 'throw';
    protected const KEYWORD_YIELD = 'yield';
    protected const KEYWORD_YIELD_FROM = 'yield_from';
    
    /** @var array<(string|int)>|null */
    private $tokensToCheck;
    
    /**
     * @return list<string>
     */
    protected abstract function getSupportedKeywords() : array;
    
    /**
     * @return list<string>
     */
    protected abstract function getKeywordsToCheck() : array;
    protected abstract function getLinesCountBefore() : int;
    protected abstract function getLinesCountBeforeFirst(File $phpcsFile, int $controlStructurePointer) : int;
    protected abstract function getLinesCountAfter() : int;
    protected abstract function getLinesCountAfterLast(File $phpcsFile, int $controlStructurePointer, int $controlStructureEndPointer) : int;
    
    /**
     * @return array<int, (int|string)>
     */
    public function register() : array {
        return $this->getTokensToCheck();
    }
    
    /**
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
     * @param int $controlStructurePointer
     */
    public function process(File $phpcsFile, $controlStructurePointer) : void {
        $this->checkLinesBefore($phpcsFile, $controlStructurePointer);
        try {
            $this->checkLinesAfter($phpcsFile, $controlStructurePointer);
        } catch (Throwable $e) {
            // Unsupported syntax without curly braces.
            return;
        }
    }
    protected function checkLinesBefore(File $phpcsFile, int $controlStructurePointer) : void {
        $tokens = $phpcsFile->getTokens();
        if (in_array($tokens[$controlStructurePointer]['code'], [
            T_CASE,
            T_DEFAULT,
        ], true)) {
            $pointerBefore = TokenHelper::findPreviousEffective($phpcsFile, $controlStructurePointer - 1);
            if ($tokens[$pointerBefore]['code'] === T_COLON) {
                return;
            }
        }
        $nonWhitespacePointerBefore = TokenHelper::findPreviousNonWhitespace($phpcsFile, $controlStructurePointer - 1);
        $controlStructureStartPointer = $controlStructurePointer;
        $pointerBefore = $nonWhitespacePointerBefore;
        $pointerToCheckFirst = $pointerBefore;
        if (in_array($tokens[$nonWhitespacePointerBefore]['code'], Tokens::$commentTokens, true)) {
            $effectivePointerBefore = TokenHelper::findPreviousEffective($phpcsFile, $pointerBefore - 1);
            if ($tokens[$effectivePointerBefore]['line'] === $tokens[$nonWhitespacePointerBefore]['line']) {
                $pointerToCheckFirst = $effectivePointerBefore;
            }
            elseif ($tokens[$nonWhitespacePointerBefore]['line'] + 1 === $tokens[$controlStructurePointer]['line']) {
                if ($tokens[$effectivePointerBefore]['line'] !== $tokens[$nonWhitespacePointerBefore]['line']) {
                    $controlStructureStartPointer = array_key_exists('comment_opener', $tokens[$nonWhitespacePointerBefore]) ? $tokens[$nonWhitespacePointerBefore]['comment_opener'] : CommentHelper::getMultilineCommentStartPointer($phpcsFile, $nonWhitespacePointerBefore);
                    $pointerBefore = TokenHelper::findPreviousNonWhitespace($phpcsFile, $controlStructureStartPointer - 1);
                }
                $pointerToCheckFirst = $pointerBefore;
            }
        }
        $isFirstControlStructure = in_array($tokens[$pointerToCheckFirst]['code'], [
            T_OPEN_CURLY_BRACKET,
            T_COLON,
        ], true);
        $whitespaceBefore = '';
        if ($tokens[$pointerBefore]['code'] === T_OPEN_TAG) {
            $whitespaceBefore .= substr($tokens[$pointerBefore]['content'], strlen('<?php'));
        }
        $hasCommentWithLineEndBefore = in_array($tokens[$pointerBefore]['code'], TokenHelper::$inlineCommentTokenCodes, true) && substr($tokens[$pointerBefore]['content'], -strlen($phpcsFile->eolChar)) === $phpcsFile->eolChar;
        if ($hasCommentWithLineEndBefore) {
            $whitespaceBefore .= $phpcsFile->eolChar;
        }
        if ($pointerBefore + 1 !== $controlStructurePointer) {
            $whitespaceBefore .= TokenHelper::getContent($phpcsFile, $pointerBefore + 1, $controlStructureStartPointer - 1);
        }
        $requiredLinesCountBefore = $isFirstControlStructure ? $this->getLinesCountBeforeFirst($phpcsFile, $controlStructurePointer) : $this->getLinesCountBefore();
        $actualLinesCountBefore = substr_count($whitespaceBefore, $phpcsFile->eolChar) - 1;
        if ($requiredLinesCountBefore === $actualLinesCountBefore) {
            return;
        }
        $fix = $phpcsFile->addFixableError(sprintf('Expected %d line%s before "%s", found %d.', $requiredLinesCountBefore, $requiredLinesCountBefore === 1 ? '' : 's', $tokens[$controlStructurePointer]['content'], $actualLinesCountBefore), $controlStructurePointer, $isFirstControlStructure ? self::CODE_INCORRECT_LINES_COUNT_BEFORE_FIRST_CONTROL_STRUCTURE : self::CODE_INCORRECT_LINES_COUNT_BEFORE_CONTROL_STRUCTURE);
        if (!$fix) {
            return;
        }
        $endOfLineBeforePointer = TokenHelper::findPreviousContent($phpcsFile, T_WHITESPACE, $phpcsFile->eolChar, $controlStructureStartPointer - 1);
        $phpcsFile->fixer
            ->beginChangeset();
        if ($tokens[$pointerBefore]['code'] === T_OPEN_TAG) {
            $phpcsFile->fixer
                ->replaceToken($pointerBefore, '<?php');
        }
        if ($endOfLineBeforePointer !== null) {
            FixerHelper::removeBetweenIncluding($phpcsFile, $pointerBefore + 1, $endOfLineBeforePointer);
        }
        $linesToAdd = $hasCommentWithLineEndBefore ? $requiredLinesCountBefore - 1 : $requiredLinesCountBefore;
        for ($i = 0; $i <= $linesToAdd; $i++) {
            $phpcsFile->fixer
                ->addNewline($pointerBefore);
        }
        $phpcsFile->fixer
            ->endChangeset();
    }
    protected function checkLinesAfter(File $phpcsFile, int $controlStructurePointer) : void {
        $tokens = $phpcsFile->getTokens();
        if (in_array($tokens[$controlStructurePointer]['code'], [
            T_CASE,
            T_DEFAULT,
        ], true)) {
            $colonPointer = TokenHelper::findNext($phpcsFile, T_COLON, $controlStructurePointer + 1);
            $pointerAfterColon = TokenHelper::findNextEffective($phpcsFile, $colonPointer + 1);
            if (in_array($tokens[$pointerAfterColon]['code'], [
                T_CASE,
                T_DEFAULT,
            ], true)) {
                return;
            }
        }
        $controlStructureEndPointer = $this->findControlStructureEnd($phpcsFile, $controlStructurePointer);
        $pointerAfterControlStructureEnd = TokenHelper::findNextEffective($phpcsFile, $controlStructureEndPointer + 1);
        if ($pointerAfterControlStructureEnd !== null && $tokens[$pointerAfterControlStructureEnd]['code'] === T_SEMICOLON) {
            $controlStructureEndPointer = $pointerAfterControlStructureEnd;
        }
        $notWhitespacePointerAfter = TokenHelper::findNextNonWhitespace($phpcsFile, $controlStructureEndPointer + 1);
        if ($notWhitespacePointerAfter === null) {
            return;
        }
        $hasCommentAfter = in_array($tokens[$notWhitespacePointerAfter]['code'], Tokens::$commentTokens, true);
        $isCommentAfterOnSameLine = false;
        $pointerAfter = $notWhitespacePointerAfter;
        $isControlStructureEndAfterPointer = static function (int $pointer) use ($tokens, $controlStructurePointer) : bool {
            return in_array($tokens[$controlStructurePointer]['code'], [
                T_CASE,
                T_DEFAULT,
            ], true) ? $tokens[$pointer]['code'] === T_CLOSE_CURLY_BRACKET : in_array($tokens[$pointer]['code'], [
                T_CLOSE_CURLY_BRACKET,
                T_CASE,
                T_DEFAULT,
            ], true);
        };
        if ($hasCommentAfter) {
            if ($tokens[$notWhitespacePointerAfter]['line'] === $tokens[$controlStructureEndPointer]['line'] + 1) {
                $commentEndPointer = CommentHelper::getCommentEndPointer($phpcsFile, $notWhitespacePointerAfter);
                $pointerAfterComment = TokenHelper::findNextNonWhitespace($phpcsFile, $commentEndPointer + 1);
                if ($isControlStructureEndAfterPointer($pointerAfterComment)) {
                    $controlStructureEndPointer = $commentEndPointer;
                    $pointerAfter = $pointerAfterComment;
                }
            }
            elseif ($tokens[$notWhitespacePointerAfter]['line'] === $tokens[$controlStructureEndPointer]['line']) {
                $isCommentAfterOnSameLine = true;
                $pointerAfter = TokenHelper::findNextNonWhitespace($phpcsFile, $notWhitespacePointerAfter + 1);
            }
        }
        $isLastControlStructure = $isControlStructureEndAfterPointer($pointerAfter);
        $requiredLinesCountAfter = $isLastControlStructure ? $this->getLinesCountAfterLast($phpcsFile, $controlStructurePointer, $controlStructureEndPointer) : $this->getLinesCountAfter();
        $actualLinesCountAfter = $tokens[$pointerAfter]['line'] - $tokens[$controlStructureEndPointer]['line'] - 1;
        if ($requiredLinesCountAfter === $actualLinesCountAfter) {
            return;
        }
        $fix = $phpcsFile->addFixableError(sprintf('Expected %d line%s after "%s", found %d.', $requiredLinesCountAfter, $requiredLinesCountAfter === 1 ? '' : 's', $tokens[$controlStructurePointer]['content'], $actualLinesCountAfter), $controlStructurePointer, $isLastControlStructure ? self::CODE_INCORRECT_LINES_COUNT_AFTER_LAST_CONTROL_STRUCTURE : self::CODE_INCORRECT_LINES_COUNT_AFTER_CONTROL_STRUCTURE);
        if (!$fix) {
            return;
        }
        $replaceStartPointer = $isCommentAfterOnSameLine ? $notWhitespacePointerAfter : $controlStructureEndPointer;
        $endOfLineBeforeAfterPointer = TokenHelper::findLastTokenOnPreviousLine($phpcsFile, $pointerAfter);
        $phpcsFile->fixer
            ->beginChangeset();
        FixerHelper::removeBetweenIncluding($phpcsFile, $replaceStartPointer + 1, $endOfLineBeforeAfterPointer);
        if ($isCommentAfterOnSameLine) {
            for ($i = 0; $i < $requiredLinesCountAfter; $i++) {
                $phpcsFile->fixer
                    ->addNewline($notWhitespacePointerAfter);
            }
        }
        else {
            $linesToAdd = substr($tokens[$controlStructureEndPointer]['content'], -strlen($phpcsFile->eolChar)) === $phpcsFile->eolChar ? $requiredLinesCountAfter - 1 : $requiredLinesCountAfter;
            for ($i = 0; $i <= $linesToAdd; $i++) {
                $phpcsFile->fixer
                    ->addNewline($controlStructureEndPointer);
            }
        }
        $phpcsFile->fixer
            ->endChangeset();
    }
    
    /**
     * @return array<int, (int|string)>
     */
    private function getTokensToCheck() : array {
        if ($this->tokensToCheck === null) {
            $supportedKeywords = $this->getSupportedKeywords();
            $supportedTokens = [
                self::KEYWORD_IF => T_IF,
                self::KEYWORD_DO => T_DO,
                self::KEYWORD_WHILE => T_WHILE,
                self::KEYWORD_FOR => T_FOR,
                self::KEYWORD_FOREACH => T_FOREACH,
                self::KEYWORD_SWITCH => T_SWITCH,
                self::KEYWORD_CASE => T_CASE,
                self::KEYWORD_DEFAULT => T_DEFAULT,
                self::KEYWORD_TRY => T_TRY,
                self::KEYWORD_PARENT => T_PARENT,
                self::KEYWORD_GOTO => T_GOTO,
                self::KEYWORD_BREAK => T_BREAK,
                self::KEYWORD_CONTINUE => T_CONTINUE,
                self::KEYWORD_RETURN => T_RETURN,
                self::KEYWORD_THROW => T_THROW,
                self::KEYWORD_YIELD => T_YIELD,
                self::KEYWORD_YIELD_FROM => T_YIELD_FROM,
            ];
            $this->tokensToCheck = array_values(array_map(static function (string $keyword) use ($supportedKeywords, $supportedTokens) {
                if (!in_array($keyword, $supportedKeywords, true)) {
                    throw new UnsupportedKeywordException($keyword);
                }
                return $supportedTokens[$keyword];
            }, SniffSettingsHelper::normalizeArray($this->getKeywordsToCheck())));
            if (count($this->tokensToCheck) === 0) {
                $this->tokensToCheck = array_map(static function (string $keyword) use ($supportedTokens) {
                    return $supportedTokens[$keyword];
                }, $supportedKeywords);
            }
        }
        return $this->tokensToCheck;
    }
    private function findControlStructureEnd(File $phpcsFile, int $controlStructurePointer) : int {
        $tokens = $phpcsFile->getTokens();
        if ($tokens[$controlStructurePointer]['code'] === T_IF) {
            if (!array_key_exists('scope_closer', $tokens[$controlStructurePointer])) {
                throw new Exception('"if" without curly braces is not supported.');
            }
            $pointerAfterParenthesisCloser = TokenHelper::findNextEffective($phpcsFile, $tokens[$controlStructurePointer]['parenthesis_closer'] + 1);
            if ($pointerAfterParenthesisCloser !== null && $tokens[$pointerAfterParenthesisCloser]['code'] === T_COLON) {
                throw new Exception('"if" without curly braces is not supported.');
            }
            $controlStructureEndPointer = $tokens[$controlStructurePointer]['scope_closer'];
            do {
                $nextPointer = TokenHelper::findNextEffective($phpcsFile, $controlStructureEndPointer + 1);
                if ($nextPointer === null) {
                    return $controlStructureEndPointer;
                }
                if ($tokens[$nextPointer]['code'] === T_ELSE) {
                    if (!array_key_exists('scope_closer', $tokens[$nextPointer])) {
                        throw new Exception('"else" without curly braces is not supported.');
                    }
                    return $tokens[$nextPointer]['scope_closer'];
                }
                if ($tokens[$nextPointer]['code'] !== T_ELSEIF) {
                    return $controlStructureEndPointer;
                }
                $controlStructureEndPointer = $tokens[$nextPointer]['scope_closer'];
            } while (true);
        }
        if ($tokens[$controlStructurePointer]['code'] === T_DO) {
            $whilePointer = TokenHelper::findNext($phpcsFile, T_WHILE, $tokens[$controlStructurePointer]['scope_closer'] + 1);
            return (int) TokenHelper::findNext($phpcsFile, T_SEMICOLON, $tokens[$whilePointer]['parenthesis_closer'] + 1);
        }
        if ($tokens[$controlStructurePointer]['code'] === T_TRY) {
            $controlStructureEndPointer = $tokens[$controlStructurePointer]['scope_closer'];
            do {
                $nextPointer = TokenHelper::findNextEffective($phpcsFile, $controlStructureEndPointer + 1);
                if ($nextPointer === null) {
                    return $controlStructureEndPointer;
                }
                if (!in_array($tokens[$nextPointer]['code'], [
                    T_CATCH,
                    T_FINALLY,
                ], true)) {
                    return $controlStructureEndPointer;
                }
                $controlStructureEndPointer = $tokens[$nextPointer]['scope_closer'];
            } while (true);
        }
        if (in_array($tokens[$controlStructurePointer]['code'], [
            T_WHILE,
            T_FOR,
            T_FOREACH,
            T_SWITCH,
        ], true)) {
            return $tokens[$controlStructurePointer]['scope_closer'];
        }
        if (in_array($tokens[$controlStructurePointer]['code'], [
            T_CASE,
            T_DEFAULT,
        ], true)) {
            $switchPointer = TokenHelper::findPrevious($phpcsFile, T_SWITCH, $controlStructurePointer - 1);
            $pointers = TokenHelper::findNextAll($phpcsFile, [
                T_CASE,
                T_DEFAULT,
            ], $controlStructurePointer + 1, $tokens[$switchPointer]['scope_closer']);
            foreach ($pointers as $pointer) {
                if (TokenHelper::findPrevious($phpcsFile, T_SWITCH, $pointer - 1) === $switchPointer) {
                    $pointerBeforeCaseOrDefault = TokenHelper::findPreviousNonWhitespace($phpcsFile, $pointer - 1);
                    if (in_array($tokens[$pointerBeforeCaseOrDefault]['code'], Tokens::$commentTokens, true) && $tokens[$pointerBeforeCaseOrDefault]['line'] + 1 === $tokens[$pointer]['line']) {
                        $pointerBeforeCaseOrDefault = TokenHelper::findPreviousExcluding($phpcsFile, T_WHITESPACE, $pointerBeforeCaseOrDefault - 1);
                    }
                    return $pointerBeforeCaseOrDefault;
                }
            }
            return TokenHelper::findPreviousNonWhitespace($phpcsFile, $tokens[$switchPointer]['scope_closer'] - 1);
        }
        $nextPointer = TokenHelper::findNext($phpcsFile, [
            T_SEMICOLON,
            T_ANON_CLASS,
            T_CLOSURE,
            T_FN,
            T_OPEN_SHORT_ARRAY,
        ], $controlStructurePointer + 1);
        if ($tokens[$nextPointer]['code'] === T_SEMICOLON) {
            return $nextPointer;
        }
        $scopeCloserPointer = $tokens[$nextPointer]['code'] === T_OPEN_SHORT_ARRAY ? $tokens[$nextPointer]['bracket_closer'] : $tokens[$nextPointer]['scope_closer'];
        if ($tokens[$scopeCloserPointer]['code'] === T_SEMICOLON) {
            return $scopeCloserPointer;
        }
        $nextPointer = TokenHelper::findNext($phpcsFile, T_SEMICOLON, $scopeCloserPointer + 1);
        $level = $tokens[$controlStructurePointer]['level'];
        while ($level !== $tokens[$nextPointer]['level']) {
            $nextPointer = (int) TokenHelper::findNext($phpcsFile, T_SEMICOLON, $nextPointer + 1);
        }
        return $nextPointer;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
AbstractControlStructureSpacing::$tokensToCheck private property @var array&lt;(string|int)&gt;|null
AbstractControlStructureSpacing::checkLinesAfter protected function 1
AbstractControlStructureSpacing::checkLinesBefore protected function 1
AbstractControlStructureSpacing::CODE_INCORRECT_LINES_COUNT_AFTER_CONTROL_STRUCTURE public constant
AbstractControlStructureSpacing::CODE_INCORRECT_LINES_COUNT_AFTER_LAST_CONTROL_STRUCTURE public constant
AbstractControlStructureSpacing::CODE_INCORRECT_LINES_COUNT_BEFORE_CONTROL_STRUCTURE public constant
AbstractControlStructureSpacing::CODE_INCORRECT_LINES_COUNT_BEFORE_FIRST_CONTROL_STRUCTURE public constant
AbstractControlStructureSpacing::findControlStructureEnd private function
AbstractControlStructureSpacing::getKeywordsToCheck abstract protected function * 3
AbstractControlStructureSpacing::getLinesCountAfter abstract protected function 3
AbstractControlStructureSpacing::getLinesCountAfterLast abstract protected function 3
AbstractControlStructureSpacing::getLinesCountBefore abstract protected function 3
AbstractControlStructureSpacing::getLinesCountBeforeFirst abstract protected function 3
AbstractControlStructureSpacing::getSupportedKeywords abstract protected function * 3
AbstractControlStructureSpacing::getTokensToCheck private function *
AbstractControlStructureSpacing::KEYWORD_BREAK protected constant
AbstractControlStructureSpacing::KEYWORD_CASE protected constant
AbstractControlStructureSpacing::KEYWORD_CONTINUE protected constant
AbstractControlStructureSpacing::KEYWORD_DEFAULT protected constant
AbstractControlStructureSpacing::KEYWORD_DO protected constant
AbstractControlStructureSpacing::KEYWORD_FOR protected constant
AbstractControlStructureSpacing::KEYWORD_FOREACH protected constant
AbstractControlStructureSpacing::KEYWORD_GOTO protected constant
AbstractControlStructureSpacing::KEYWORD_IF protected constant
AbstractControlStructureSpacing::KEYWORD_PARENT protected constant
AbstractControlStructureSpacing::KEYWORD_RETURN protected constant
AbstractControlStructureSpacing::KEYWORD_SWITCH protected constant
AbstractControlStructureSpacing::KEYWORD_THROW protected constant
AbstractControlStructureSpacing::KEYWORD_TRY protected constant
AbstractControlStructureSpacing::KEYWORD_WHILE protected constant
AbstractControlStructureSpacing::KEYWORD_YIELD protected constant
AbstractControlStructureSpacing::KEYWORD_YIELD_FROM protected constant
AbstractControlStructureSpacing::process public function * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*
Overrides Sniff::process 3
AbstractControlStructureSpacing::register public function * Overrides Sniff::register
RSS feed
Powered by Drupal