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

Breadcrumb

  1. Drupal Core 11.1.x

RequireNullCoalesceOperatorSniff.php

Namespace

SlevomatCodingStandard\Sniffs\ControlStructures

File

vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/ControlStructures/RequireNullCoalesceOperatorSniff.php

View source
<?php

declare (strict_types=1);
namespace SlevomatCodingStandard\Sniffs\ControlStructures;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use SlevomatCodingStandard\Helpers\FixerHelper;
use SlevomatCodingStandard\Helpers\IdentificatorHelper;
use SlevomatCodingStandard\Helpers\TernaryOperatorHelper;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function in_array;
use function sprintf;
use function trim;
use const T_BOOLEAN_NOT;
use const T_CLOSE_PARENTHESIS;
use const T_COMMA;
use const T_INLINE_THEN;
use const T_IS_IDENTICAL;
use const T_IS_NOT_IDENTICAL;
use const T_ISSET;
use const T_NULL;
class RequireNullCoalesceOperatorSniff implements Sniff {
    public const CODE_NULL_COALESCE_OPERATOR_NOT_USED = 'NullCoalesceOperatorNotUsed';
    
    /**
     * @return array<int, (int|string)>
     */
    public function register() : array {
        return [
            T_ISSET,
            T_IS_IDENTICAL,
            T_IS_NOT_IDENTICAL,
        ];
    }
    
    /**
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
     * @param int $pointer
     */
    public function process(File $phpcsFile, $pointer) : void {
        $tokens = $phpcsFile->getTokens();
        if ($tokens[$pointer]['code'] === T_ISSET) {
            $this->checkIsset($phpcsFile, $pointer);
        }
        else {
            $this->checkIdenticalOperator($phpcsFile, $pointer);
        }
    }
    public function checkIsset(File $phpcsFile, int $issetPointer) : void {
        $tokens = $phpcsFile->getTokens();
        $previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $issetPointer - 1);
        if ($tokens[$previousPointer]['code'] === T_BOOLEAN_NOT) {
            return;
        }
        if (in_array($tokens[$previousPointer]['code'], Tokens::$booleanOperators, true)) {
            return;
        }
        $openParenthesisPointer = TokenHelper::findNextEffective($phpcsFile, $issetPointer + 1);
        $closeParenthesisPointer = $tokens[$openParenthesisPointer]['parenthesis_closer'];
        
        /** @var int $inlineThenPointer */
        $inlineThenPointer = TokenHelper::findNextEffective($phpcsFile, $closeParenthesisPointer + 1);
        if ($tokens[$inlineThenPointer]['code'] !== T_INLINE_THEN) {
            return;
        }
        $commaPointer = TokenHelper::findNext($phpcsFile, T_COMMA, $openParenthesisPointer + 1, $closeParenthesisPointer);
        if ($commaPointer !== null) {
            return;
        }
        $inlineElsePointer = TernaryOperatorHelper::getElsePointer($phpcsFile, $inlineThenPointer);
        $variableContent = IdentificatorHelper::getContent($phpcsFile, $openParenthesisPointer + 1, $closeParenthesisPointer - 1);
        $thenContent = IdentificatorHelper::getContent($phpcsFile, $inlineThenPointer + 1, $inlineElsePointer - 1);
        if ($variableContent !== $thenContent) {
            return;
        }
        $fix = $phpcsFile->addFixableError('Use null coalesce operator instead of ternary operator.', $inlineThenPointer, self::CODE_NULL_COALESCE_OPERATOR_NOT_USED);
        if (!$fix) {
            return;
        }
        $startPointer = $issetPointer;
        if (in_array($tokens[$previousPointer]['code'], Tokens::$castTokens, true)) {
            $startPointer = $previousPointer;
        }
        $phpcsFile->fixer
            ->beginChangeset();
        FixerHelper::change($phpcsFile, $startPointer, $inlineElsePointer, sprintf('%s ??', $variableContent));
        $phpcsFile->fixer
            ->endChangeset();
    }
    public function checkIdenticalOperator(File $phpcsFile, int $identicalOperator) : void {
        $tokens = $phpcsFile->getTokens();
        
        /** @var int $pointerBeforeIdenticalOperator */
        $pointerBeforeIdenticalOperator = TokenHelper::findPreviousEffective($phpcsFile, $identicalOperator - 1);
        
        /** @var int $pointerAfterIdenticalOperator */
        $pointerAfterIdenticalOperator = TokenHelper::findNextEffective($phpcsFile, $identicalOperator + 1);
        if ($tokens[$pointerBeforeIdenticalOperator]['code'] !== T_NULL && $tokens[$pointerAfterIdenticalOperator]['code'] !== T_NULL) {
            return;
        }
        $isYodaCondition = $tokens[$pointerBeforeIdenticalOperator]['code'] === T_NULL;
        $variableEndPointer = $isYodaCondition ? $pointerAfterIdenticalOperator : $pointerBeforeIdenticalOperator;
        $tmpPointer = $variableEndPointer;
        while ($tokens[$tmpPointer]['code'] === T_CLOSE_PARENTHESIS) {
            
            /** @var int $tmpPointer */
            $tmpPointer = TokenHelper::findPreviousEffective($phpcsFile, $tokens[$tmpPointer]['parenthesis_opener'] - 1);
        }
        $variableStartPointer = IdentificatorHelper::findStartPointer($phpcsFile, $tmpPointer);
        if ($variableStartPointer === null) {
            return;
        }
        $pointerBeforeCondition = TokenHelper::findPreviousEffective($phpcsFile, ($isYodaCondition ? $pointerBeforeIdenticalOperator : $variableStartPointer) - 1);
        if (in_array($tokens[$pointerBeforeCondition]['code'], Tokens::$booleanOperators, true)) {
            return;
        }
        
        /** @var int $inlineThenPointer */
        $inlineThenPointer = TokenHelper::findNextEffective($phpcsFile, ($isYodaCondition ? $variableEndPointer : $pointerAfterIdenticalOperator) + 1);
        if ($tokens[$inlineThenPointer]['code'] !== T_INLINE_THEN) {
            return;
        }
        $inlineElsePointer = TernaryOperatorHelper::getElsePointer($phpcsFile, $inlineThenPointer);
        $inlineElseEndPointer = TernaryOperatorHelper::getEndPointer($phpcsFile, $inlineThenPointer, $inlineElsePointer);
        $pointerAfterInlineElseEnd = TokenHelper::findNextEffective($phpcsFile, $inlineElseEndPointer + 1);
        $variableContent = IdentificatorHelper::getContent($phpcsFile, $variableStartPointer, $variableEndPointer);
        
        /** @var int $compareToStartPointer */
        $compareToStartPointer = TokenHelper::findNextEffective($phpcsFile, ($tokens[$identicalOperator]['code'] === T_IS_IDENTICAL ? $inlineElsePointer : $inlineThenPointer) + 1);
        
        /** @var int $compareToEndPointer */
        $compareToEndPointer = TokenHelper::findPreviousEffective($phpcsFile, ($tokens[$identicalOperator]['code'] === T_IS_IDENTICAL ? $pointerAfterInlineElseEnd : $inlineElsePointer) - 1);
        $compareToContent = IdentificatorHelper::getContent($phpcsFile, $compareToStartPointer, $compareToEndPointer);
        if ($compareToContent !== $variableContent) {
            return;
        }
        $fix = $phpcsFile->addFixableError('Use null coalesce operator instead of ternary operator.', $inlineThenPointer, self::CODE_NULL_COALESCE_OPERATOR_NOT_USED);
        if (!$fix) {
            return;
        }
        
        /** @var int $conditionStart */
        $conditionStart = $isYodaCondition ? $pointerBeforeIdenticalOperator : $variableStartPointer;
        $variableContent = trim(TokenHelper::getContent($phpcsFile, $variableStartPointer, $variableEndPointer));
        $phpcsFile->fixer
            ->beginChangeset();
        $phpcsFile->fixer
            ->replaceToken($conditionStart, sprintf('%s ??', $variableContent));
        if ($tokens[$identicalOperator]['code'] === T_IS_IDENTICAL) {
            FixerHelper::removeBetweenIncluding($phpcsFile, $conditionStart + 1, $inlineThenPointer);
            $pointerBeforeInlineElse = TokenHelper::findPreviousEffective($phpcsFile, $inlineElsePointer - 1);
            FixerHelper::removeBetweenIncluding($phpcsFile, $pointerBeforeInlineElse + 1, $inlineElseEndPointer);
        }
        else {
            FixerHelper::removeBetweenIncluding($phpcsFile, $conditionStart + 1, $inlineElsePointer);
        }
        $phpcsFile->fixer
            ->endChangeset();
    }

}

Classes

Title Deprecated Summary
RequireNullCoalesceOperatorSniff

API Navigation

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