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

Breadcrumb

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

class PropertyDeclarationSniff

Same name in this branch
  1. 11.1.x vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Classes/PropertyDeclarationSniff.php \Drupal\Sniffs\Classes\PropertyDeclarationSniff
  2. 11.1.x vendor/squizlabs/php_codesniffer/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php \PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes\PropertyDeclarationSniff

Hierarchy

  • class \SlevomatCodingStandard\Sniffs\Classes\PropertyDeclarationSniff implements \PHP_CodeSniffer\Sniffs\Sniff

Expanded class hierarchy of PropertyDeclarationSniff

File

vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/PropertyDeclarationSniff.php, line 37

Namespace

SlevomatCodingStandard\Sniffs\Classes
View source
class PropertyDeclarationSniff implements Sniff {
    public const CODE_NO_SPACE_BEFORE_NULLABILITY_SYMBOL = 'NoSpaceBeforeNullabilitySymbol';
    public const CODE_MULTIPLE_SPACES_BEFORE_NULLABILITY_SYMBOL = 'MultipleSpacesBeforeNullabilitySymbol';
    public const CODE_MULTIPLE_SPACES_BEFORE_TYPE_HINT = 'MultipleSpacesBeforeTypeHint';
    public const CODE_NO_SPACE_BETWEEN_TYPE_HINT_AND_PROPERTY = 'NoSpaceBetweenTypeHintAndProperty';
    public const CODE_MULTIPLE_SPACES_BETWEEN_TYPE_HINT_AND_PROPERTY = 'MultipleSpacesBetweenTypeHintAndProperty';
    public const CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL = 'WhitespaceAfterNullabilitySymbol';
    public const CODE_INCORRECT_ORDER_OF_MODIFIERS = 'IncorrectOrderOfModifiers';
    public const CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS = 'MultipleSpacesBetweenModifiers';
    
    /** @var list<string>|null */
    public $modifiersOrder = [];
    
    /** @var bool */
    public $checkPromoted = false;
    
    /** @var bool */
    public $enableMultipleSpacesBetweenModifiersCheck = false;
    
    /** @var array<int, array<int, (int|string)>>|null */
    private $normalizedModifiersOrder = null;
    
    /**
     * @return array<int, (int|string)>
     */
    public function register() : array {
        return TokenHelper::$propertyModifiersTokenCodes;
    }
    
    /**
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
     * @param int $modifierPointer
     */
    public function process(File $phpcsFile, $modifierPointer) : void {
        $tokens = $phpcsFile->getTokens();
        $asPointer = TokenHelper::findPreviousEffective($phpcsFile, $modifierPointer - 1);
        if ($tokens[$asPointer]['code'] === T_AS) {
            return;
        }
        $nextPointer = TokenHelper::findNextEffective($phpcsFile, $modifierPointer + 1);
        if (in_array($tokens[$nextPointer]['code'], TokenHelper::$propertyModifiersTokenCodes, true)) {
            // We don't want to report the same property twice
            return;
        }
        if ($tokens[$nextPointer]['code'] === T_DOUBLE_COLON) {
            // Ignore static::
            return;
        }
        $propertyPointer = TokenHelper::findNext($phpcsFile, [
            T_FUNCTION,
            T_CONST,
            T_VARIABLE,
        ], $modifierPointer + 1);
        if ($propertyPointer === null || $tokens[$propertyPointer]['code'] !== T_VARIABLE) {
            return;
        }
        if (!PropertyHelper::isProperty($phpcsFile, $propertyPointer, $this->checkPromoted)) {
            return;
        }
        $firstModifierPointer = $modifierPointer;
        do {
            $previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $firstModifierPointer - 1);
            if (!in_array($tokens[$previousPointer]['code'], TokenHelper::$propertyModifiersTokenCodes, true)) {
                break;
            }
            $firstModifierPointer = $previousPointer;
        } while (true);
        $this->checkModifiersOrder($phpcsFile, $propertyPointer, $firstModifierPointer, $modifierPointer);
        $this->checkSpacesBetweenModifiers($phpcsFile, $propertyPointer, $firstModifierPointer, $modifierPointer);
        $this->checkTypeHintSpacing($phpcsFile, $propertyPointer, $modifierPointer);
    }
    private function checkModifiersOrder(File $phpcsFile, int $propertyPointer, int $firstModifierPointer, int $lastModifierPointer) : void {
        $modifiersPointers = TokenHelper::findNextAll($phpcsFile, TokenHelper::$propertyModifiersTokenCodes, $firstModifierPointer, $lastModifierPointer + 1);
        if (count($modifiersPointers) < 2) {
            return;
        }
        $tokens = $phpcsFile->getTokens();
        $modifiersGroups = $this->getNormalizedModifiersOrder();
        $expectedModifiersPositions = [];
        foreach ($modifiersPointers as $modifierPointer) {
            for ($i = 0; $i < count($modifiersGroups); $i++) {
                if (in_array($tokens[$modifierPointer]['code'], $modifiersGroups[$i], true)) {
                    $expectedModifiersPositions[$modifierPointer] = $i;
                    continue 2;
                }
            }
            // Modifier position is not defined so add it to the end
            $expectedModifiersPositions[$modifierPointer] = count($modifiersGroups);
        }
        $error = false;
        for ($i = 1; $i < count($modifiersPointers); $i++) {
            for ($j = 0; $j < $i; $j++) {
                if ($expectedModifiersPositions[$modifiersPointers[$i]] < $expectedModifiersPositions[$modifiersPointers[$j]]) {
                    $error = true;
                    break;
                }
            }
        }
        if (!$error) {
            return;
        }
        $actualModifiers = array_map(static function (int $modifierPointer) use ($tokens) : string {
            return $tokens[$modifierPointer]['content'];
        }, $modifiersPointers);
        $actualModifiersFormatted = implode(' ', $actualModifiers);
        asort($expectedModifiersPositions);
        $expectedModifiers = array_map(static function (int $modifierPointer) use ($tokens) : string {
            return $tokens[$modifierPointer]['content'];
        }, array_keys($expectedModifiersPositions));
        $expectedModifiersFormatted = implode(' ', $expectedModifiers);
        $fix = $phpcsFile->addFixableError(sprintf('Incorrect order of modifiers "%s" of property %s, expected "%s".', $actualModifiersFormatted, $tokens[$propertyPointer]['content'], $expectedModifiersFormatted), $firstModifierPointer, self::CODE_INCORRECT_ORDER_OF_MODIFIERS);
        if (!$fix) {
            return;
        }
        $phpcsFile->fixer
            ->beginChangeset();
        FixerHelper::change($phpcsFile, $firstModifierPointer, $lastModifierPointer, $expectedModifiersFormatted);
        $phpcsFile->fixer
            ->endChangeset();
    }
    private function checkSpacesBetweenModifiers(File $phpcsFile, int $propertyPointer, int $firstModifierPointer, int $lastModifierPointer) : void {
        if (!$this->enableMultipleSpacesBetweenModifiersCheck) {
            return;
        }
        $modifiersPointers = TokenHelper::findNextAll($phpcsFile, TokenHelper::$propertyModifiersTokenCodes, $firstModifierPointer, $lastModifierPointer + 1);
        if (count($modifiersPointers) < 2) {
            return;
        }
        $tokens = $phpcsFile->getTokens();
        $error = false;
        for ($i = 0; $i < count($modifiersPointers) - 1; $i++) {
            $whitespace = TokenHelper::getContent($phpcsFile, $modifiersPointers[$i] + 1, $modifiersPointers[$i + 1] - 1);
            if ($whitespace !== ' ') {
                $error = true;
                break;
            }
        }
        if (!$error) {
            return;
        }
        $fix = $phpcsFile->addFixableError(sprintf('There must be exactly one space between modifiers of property %s.', $tokens[$propertyPointer]['content']), $firstModifierPointer, self::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS);
        if (!$fix) {
            return;
        }
        $expectedModifiers = array_map(static function (int $modifierPointer) use ($tokens) : string {
            return $tokens[$modifierPointer]['content'];
        }, $modifiersPointers);
        $expectedModifiersFormatted = implode(' ', $expectedModifiers);
        $phpcsFile->fixer
            ->beginChangeset();
        FixerHelper::change($phpcsFile, $firstModifierPointer, $lastModifierPointer, $expectedModifiersFormatted);
        $phpcsFile->fixer
            ->endChangeset();
    }
    private function checkTypeHintSpacing(File $phpcsFile, int $propertyPointer, int $lastModifierPointer) : void {
        $typeHintEndPointer = TokenHelper::findPrevious($phpcsFile, TokenHelper::getTypeHintTokenCodes(), $propertyPointer - 1, $lastModifierPointer);
        if ($typeHintEndPointer === null) {
            return;
        }
        $tokens = $phpcsFile->getTokens();
        $typeHintStartPointer = TypeHintHelper::getStartPointer($phpcsFile, $typeHintEndPointer);
        $previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $typeHintStartPointer - 1, $lastModifierPointer);
        $nullabilitySymbolPointer = $previousPointer !== null && $tokens[$previousPointer]['code'] === T_NULLABLE ? $previousPointer : null;
        $propertyName = $tokens[$propertyPointer]['content'];
        if ($tokens[$lastModifierPointer + 1]['code'] !== T_WHITESPACE) {
            $errorMessage = sprintf('There must be exactly one space before type hint nullability symbol of property %s.', $propertyName);
            $errorCode = self::CODE_NO_SPACE_BEFORE_NULLABILITY_SYMBOL;
            $fix = $phpcsFile->addFixableError($errorMessage, $typeHintEndPointer, $errorCode);
            if ($fix) {
                $phpcsFile->fixer
                    ->beginChangeset();
                $phpcsFile->fixer
                    ->addContent($lastModifierPointer, ' ');
                $phpcsFile->fixer
                    ->endChangeset();
            }
        }
        elseif ($tokens[$lastModifierPointer + 1]['content'] !== ' ') {
            if ($nullabilitySymbolPointer !== null) {
                $errorMessage = sprintf('There must be exactly one space before type hint nullability symbol of property %s.', $propertyName);
                $errorCode = self::CODE_MULTIPLE_SPACES_BEFORE_NULLABILITY_SYMBOL;
            }
            else {
                $errorMessage = sprintf('There must be exactly one space before type hint of property %s.', $propertyName);
                $errorCode = self::CODE_MULTIPLE_SPACES_BEFORE_TYPE_HINT;
            }
            $fix = $phpcsFile->addFixableError($errorMessage, $lastModifierPointer, $errorCode);
            if ($fix) {
                $phpcsFile->fixer
                    ->beginChangeset();
                $phpcsFile->fixer
                    ->replaceToken($lastModifierPointer + 1, ' ');
                $phpcsFile->fixer
                    ->endChangeset();
            }
        }
        if ($tokens[$typeHintEndPointer + 1]['code'] !== T_WHITESPACE) {
            $fix = $phpcsFile->addFixableError(sprintf('There must be exactly one space between type hint and property %s.', $propertyName), $typeHintEndPointer, self::CODE_NO_SPACE_BETWEEN_TYPE_HINT_AND_PROPERTY);
            if ($fix) {
                $phpcsFile->fixer
                    ->beginChangeset();
                $phpcsFile->fixer
                    ->addContent($typeHintEndPointer, ' ');
                $phpcsFile->fixer
                    ->endChangeset();
            }
        }
        elseif ($tokens[$typeHintEndPointer + 1]['content'] !== ' ') {
            $fix = $phpcsFile->addFixableError(sprintf('There must be exactly one space between type hint and property %s.', $propertyName), $typeHintEndPointer, self::CODE_MULTIPLE_SPACES_BETWEEN_TYPE_HINT_AND_PROPERTY);
            if ($fix) {
                $phpcsFile->fixer
                    ->beginChangeset();
                $phpcsFile->fixer
                    ->replaceToken($typeHintEndPointer + 1, ' ');
                $phpcsFile->fixer
                    ->endChangeset();
            }
        }
        if ($nullabilitySymbolPointer === null) {
            return;
        }
        if ($nullabilitySymbolPointer + 1 === $typeHintStartPointer) {
            return;
        }
        $fix = $phpcsFile->addFixableError(sprintf('There must be no whitespace between type hint nullability symbol and type hint of property %s.', $propertyName), $typeHintStartPointer, self::CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL);
        if (!$fix) {
            return;
        }
        $phpcsFile->fixer
            ->beginChangeset();
        $phpcsFile->fixer
            ->replaceToken($nullabilitySymbolPointer + 1, '');
        $phpcsFile->fixer
            ->endChangeset();
    }
    
    /**
     * @return array<int, array<int, (int|string)>>
     */
    private function getNormalizedModifiersOrder() : array {
        if ($this->normalizedModifiersOrder === null) {
            $modifiersGroups = SniffSettingsHelper::normalizeArray($this->modifiersOrder);
            if ($modifiersGroups === []) {
                $modifiersGroups = [
                    'var, public, protected, private',
                    'static, readonly',
                ];
            }
            $this->normalizedModifiersOrder = [];
            $mapping = [
                'var' => T_VAR,
                'public' => T_PUBLIC,
                'protected' => T_PROTECTED,
                'private' => T_PRIVATE,
                'static' => T_STATIC,
                'readonly' => T_READONLY,
            ];
            foreach ($modifiersGroups as $modifiersGroupNo => $modifiersGroup) {
                $this->normalizedModifiersOrder[$modifiersGroupNo] = [];
                
                /** @var list<string> $modifiers */
                $modifiers = preg_split('~\\s*,\\s*~', strtolower($modifiersGroup));
                foreach ($modifiers as $modifier) {
                    if (!array_key_exists($modifier, $mapping)) {
                        throw new UnexpectedValueException(sprintf('Unknown property modifier "%s".', $modifier));
                    }
                    $this->normalizedModifiersOrder[$modifiersGroupNo][] = $mapping[$modifier];
                }
            }
        }
        return $this->normalizedModifiersOrder;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
PropertyDeclarationSniff::$checkPromoted public property @var bool
PropertyDeclarationSniff::$enableMultipleSpacesBetweenModifiersCheck public property @var bool
PropertyDeclarationSniff::$modifiersOrder public property @var list&lt;string&gt;|null
PropertyDeclarationSniff::$normalizedModifiersOrder private property @var array&lt;int, array&lt;int, (int|string)&gt;&gt;|null
PropertyDeclarationSniff::checkModifiersOrder private function
PropertyDeclarationSniff::checkSpacesBetweenModifiers private function
PropertyDeclarationSniff::checkTypeHintSpacing private function
PropertyDeclarationSniff::CODE_INCORRECT_ORDER_OF_MODIFIERS public constant
PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BEFORE_NULLABILITY_SYMBOL public constant
PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BEFORE_TYPE_HINT public constant
PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_MODIFIERS public constant
PropertyDeclarationSniff::CODE_MULTIPLE_SPACES_BETWEEN_TYPE_HINT_AND_PROPERTY public constant
PropertyDeclarationSniff::CODE_NO_SPACE_BEFORE_NULLABILITY_SYMBOL public constant
PropertyDeclarationSniff::CODE_NO_SPACE_BETWEEN_TYPE_HINT_AND_PROPERTY public constant
PropertyDeclarationSniff::CODE_WHITESPACE_AFTER_NULLABILITY_SYMBOL public constant
PropertyDeclarationSniff::getNormalizedModifiersOrder private function *
PropertyDeclarationSniff::process public function * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*
Overrides Sniff::process
PropertyDeclarationSniff::register public function * Overrides Sniff::register

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal