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

Breadcrumb

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

class SideEffectsSniff

Hierarchy

  • class \PHP_CodeSniffer\Standards\PSR1\Sniffs\Files\SideEffectsSniff implements \PHP_CodeSniffer\Sniffs\Sniff

Expanded class hierarchy of SideEffectsSniff

File

vendor/squizlabs/php_codesniffer/src/Standards/PSR1/Sniffs/Files/SideEffectsSniff.php, line 16

Namespace

PHP_CodeSniffer\Standards\PSR1\Sniffs\Files
View source
class SideEffectsSniff implements Sniff {
    
    /**
     * Returns an array of tokens this test wants to listen for.
     *
     * @return array<int|string>
     */
    public function register() {
        return [
            T_OPEN_TAG,
        ];
    }
    
    //end register()
    
    /**
     * Processes this sniff, when one of its tokens is encountered.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
     * @param int                         $stackPtr  The position of the current token in
     *                                               the token stack.
     *
     * @return int
     */
    public function process(File $phpcsFile, $stackPtr) {
        $tokens = $phpcsFile->getTokens();
        $result = $this->searchForConflict($phpcsFile, 0, $phpcsFile->numTokens - 1, $tokens);
        if ($result['symbol'] !== null && $result['effect'] !== null) {
            $error = 'A file should declare new symbols (classes, functions, constants, etc.) and cause no other side effects, or it should execute logic with side effects, but should not do both. The first symbol is defined on line %s and the first side effect is on line %s.';
            $data = [
                $tokens[$result['symbol']]['line'],
                $tokens[$result['effect']]['line'],
            ];
            $phpcsFile->addWarning($error, 0, 'FoundWithSymbols', $data);
            $phpcsFile->recordMetric($stackPtr, 'Declarations and side effects mixed', 'yes');
        }
        else {
            $phpcsFile->recordMetric($stackPtr, 'Declarations and side effects mixed', 'no');
        }
        // Ignore the rest of the file.
        return $phpcsFile->numTokens;
    }
    
    //end process()
    
    /**
     * Searches for symbol declarations and side effects.
     *
     * Returns the positions of both the first symbol declared and the first
     * side effect in the file. A NULL value for either indicates nothing was
     * found.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
     * @param int                         $start     The token to start searching from.
     * @param int                         $end       The token to search to.
     * @param array                       $tokens    The stack of tokens that make up
     *                                               the file.
     *
     * @return array
     */
    private function searchForConflict($phpcsFile, $start, $end, $tokens) {
        $symbols = [
            T_CLASS => T_CLASS,
            T_INTERFACE => T_INTERFACE,
            T_TRAIT => T_TRAIT,
            T_ENUM => T_ENUM,
            T_FUNCTION => T_FUNCTION,
        ];
        $conditions = [
            T_IF => T_IF,
            T_ELSE => T_ELSE,
            T_ELSEIF => T_ELSEIF,
        ];
        $checkAnnotations = $phpcsFile->config->annotations;
        $firstSymbol = null;
        $firstEffect = null;
        for ($i = $start; $i <= $end; $i++) {
            // Respect phpcs:disable comments.
            if ($checkAnnotations === true && $tokens[$i]['code'] === T_PHPCS_DISABLE && (empty($tokens[$i]['sniffCodes']) === true || isset($tokens[$i]['sniffCodes']['PSR1']) === true || isset($tokens[$i]['sniffCodes']['PSR1.Files']) === true || isset($tokens[$i]['sniffCodes']['PSR1.Files.SideEffects']) === true || isset($tokens[$i]['sniffCodes']['PSR1.Files.SideEffects.FoundWithSymbols']) === true)) {
                do {
                    $i = $phpcsFile->findNext(T_PHPCS_ENABLE, $i + 1);
                } while ($i !== false && empty($tokens[$i]['sniffCodes']) === false && isset($tokens[$i]['sniffCodes']['PSR1']) === false && isset($tokens[$i]['sniffCodes']['PSR1.Files']) === false && isset($tokens[$i]['sniffCodes']['PSR1.Files.SideEffects']) === false && isset($tokens[$i]['sniffCodes']['PSR1.Files.SideEffects.FoundWithSymbols']) === false);
                if ($i === false) {
                    // The entire rest of the file is disabled,
                    // so return what we have so far.
                    break;
                }
                continue;
            }
            // Ignore whitespace and comments.
            if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
                continue;
            }
            // Ignore PHP tags.
            if ($tokens[$i]['code'] === T_OPEN_TAG || $tokens[$i]['code'] === T_CLOSE_TAG) {
                continue;
            }
            // Ignore shebang.
            if (substr($tokens[$i]['content'], 0, 2) === '#!') {
                continue;
            }
            // Ignore logical operators.
            if (isset(Tokens::$booleanOperators[$tokens[$i]['code']]) === true) {
                continue;
            }
            // Ignore entire namespace, declare, const and use statements.
            if ($tokens[$i]['code'] === T_NAMESPACE || $tokens[$i]['code'] === T_USE || $tokens[$i]['code'] === T_DECLARE || $tokens[$i]['code'] === T_CONST) {
                if (isset($tokens[$i]['scope_opener']) === true) {
                    $i = $tokens[$i]['scope_closer'];
                    if ($tokens[$i]['code'] === T_ENDDECLARE) {
                        $semicolon = $phpcsFile->findNext(Tokens::$emptyTokens, $i + 1, null, true);
                        if ($semicolon !== false && $tokens[$semicolon]['code'] === T_SEMICOLON) {
                            $i = $semicolon;
                        }
                    }
                }
                else {
                    $semicolon = $phpcsFile->findNext(T_SEMICOLON, $i + 1);
                    if ($semicolon !== false) {
                        $i = $semicolon;
                    }
                }
                continue;
            }
            // Ignore function/class prefixes.
            if (isset(Tokens::$methodPrefixes[$tokens[$i]['code']]) === true || $tokens[$i]['code'] === T_READONLY) {
                continue;
            }
            // Ignore anon classes.
            if ($tokens[$i]['code'] === T_ANON_CLASS) {
                $i = $tokens[$i]['scope_closer'];
                continue;
            }
            // Ignore attributes.
            if ($tokens[$i]['code'] === T_ATTRIBUTE && isset($tokens[$i]['attribute_closer']) === true) {
                $i = $tokens[$i]['attribute_closer'];
                continue;
            }
            // Detect and skip over symbols.
            if (isset($symbols[$tokens[$i]['code']]) === true && isset($tokens[$i]['scope_closer']) === true) {
                if ($firstSymbol === null) {
                    $firstSymbol = $i;
                }
                $i = $tokens[$i]['scope_closer'];
                continue;
            }
            else {
                if ($tokens[$i]['code'] === T_STRING && strtolower($tokens[$i]['content']) === 'define') {
                    $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, $i - 1, null, true);
                    if ($tokens[$prev]['code'] !== T_OBJECT_OPERATOR && $tokens[$prev]['code'] !== T_NULLSAFE_OBJECT_OPERATOR && $tokens[$prev]['code'] !== T_DOUBLE_COLON && $tokens[$prev]['code'] !== T_FUNCTION) {
                        if ($firstSymbol === null) {
                            $firstSymbol = $i;
                        }
                        $semicolon = $phpcsFile->findNext(T_SEMICOLON, $i + 1);
                        if ($semicolon !== false) {
                            $i = $semicolon;
                        }
                        continue;
                    }
                }
            }
            
            //end if
            // Special case for defined() as it can be used to see
            // if a constant (a symbol) should be defined or not and
            // doesn't need to use a full conditional block.
            if ($tokens[$i]['code'] === T_STRING && strtolower($tokens[$i]['content']) === 'defined') {
                $openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, $i + 1, null, true);
                if ($openBracket !== false && $tokens[$openBracket]['code'] === T_OPEN_PARENTHESIS && isset($tokens[$openBracket]['parenthesis_closer']) === true) {
                    $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, $i - 1, null, true);
                    if ($tokens[$prev]['code'] !== T_OBJECT_OPERATOR && $tokens[$prev]['code'] !== T_NULLSAFE_OBJECT_OPERATOR && $tokens[$prev]['code'] !== T_DOUBLE_COLON && $tokens[$prev]['code'] !== T_FUNCTION) {
                        $i = $tokens[$openBracket]['parenthesis_closer'];
                        continue;
                    }
                }
            }
            
            //end if
            // Conditional statements are allowed in symbol files as long as the
            // contents is only a symbol definition. So don't count these as effects
            // in this case.
            if (isset($conditions[$tokens[$i]['code']]) === true) {
                if (isset($tokens[$i]['scope_opener']) === false) {
                    // Probably an "else if", so just ignore.
                    continue;
                }
                $result = $this->searchForConflict($phpcsFile, $tokens[$i]['scope_opener'] + 1, $tokens[$i]['scope_closer'] - 1, $tokens);
                if ($result['symbol'] !== null) {
                    if ($firstSymbol === null) {
                        $firstSymbol = $result['symbol'];
                    }
                    if ($result['effect'] !== null) {
                        // Found a conflict.
                        $firstEffect = $result['effect'];
                        break;
                    }
                }
                if ($firstEffect === null) {
                    $firstEffect = $result['effect'];
                }
                $i = $tokens[$i]['scope_closer'];
                continue;
            }
            
            //end if
            if ($firstEffect === null) {
                $firstEffect = $i;
            }
            if ($firstSymbol !== null) {
                // We have a conflict we have to report, so no point continuing.
                break;
            }
        }
        
        //end for
        return [
            'symbol' => $firstSymbol,
            'effect' => $firstEffect,
        ];
    }
    
    //end searchForConflict()

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
SideEffectsSniff::process public function Processes this sniff, when one of its tokens is encountered. Overrides Sniff::process
SideEffectsSniff::register public function Returns an array of tokens this test wants to listen for. Overrides Sniff::register
SideEffectsSniff::searchForConflict private function Searches for symbol declarations and side effects.
RSS feed
Powered by Drupal