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

Breadcrumb

  1. Drupal Core 11.1.x

ClassDeclarationSniff.php

Same filename in this branch
  1. 11.1.x vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Classes/ClassDeclarationSniff.php
  2. 11.1.x vendor/squizlabs/php_codesniffer/src/Standards/Squiz/Sniffs/Classes/ClassDeclarationSniff.php
  3. 11.1.x vendor/squizlabs/php_codesniffer/src/Standards/PSR1/Sniffs/Classes/ClassDeclarationSniff.php
  4. 11.1.x vendor/squizlabs/php_codesniffer/src/Standards/PEAR/Sniffs/Classes/ClassDeclarationSniff.php

Namespace

PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes

File

vendor/squizlabs/php_codesniffer/src/Standards/PSR2/Sniffs/Classes/ClassDeclarationSniff.php

View source
<?php


/**
 * Checks the declaration of the class and its inheritance is correct.
 *
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
 */
namespace PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Standards\PEAR\Sniffs\Classes\ClassDeclarationSniff as PEARClassDeclarationSniff;
use PHP_CodeSniffer\Util\Tokens;
class ClassDeclarationSniff extends PEARClassDeclarationSniff {
    
    /**
     * The number of spaces code should be indented.
     *
     * @var integer
     */
    public $indent = 4;
    
    /**
     * Processes this test, 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 stack passed in $tokens.
     *
     * @return void
     */
    public function process(File $phpcsFile, $stackPtr) {
        // We want all the errors from the PEAR standard, plus some of our own.
        parent::process($phpcsFile, $stackPtr);
        // Just in case.
        $tokens = $phpcsFile->getTokens();
        if (isset($tokens[$stackPtr]['scope_opener']) === false) {
            return;
        }
        $this->processOpen($phpcsFile, $stackPtr);
        $this->processClose($phpcsFile, $stackPtr);
    }
    
    //end process()
    
    /**
     * Processes the opening section of a class declaration.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
     * @param int                         $stackPtr  The position of the current token
     *                                               in the stack passed in $tokens.
     *
     * @return void
     */
    public function processOpen(File $phpcsFile, $stackPtr) {
        $tokens = $phpcsFile->getTokens();
        $stackPtrType = strtolower($tokens[$stackPtr]['content']);
        // Check alignment of the keyword and braces.
        $classModifiers = [
            T_ABSTRACT => T_ABSTRACT,
            T_FINAL => T_FINAL,
            T_READONLY => T_READONLY,
        ];
        $prevNonSpace = $phpcsFile->findPrevious(T_WHITESPACE, $stackPtr - 1, null, true);
        $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true);
        if (isset($classModifiers[$tokens[$prevNonEmpty]['code']]) === true) {
            $spaces = 0;
            $errorCode = 'SpaceBeforeKeyword';
            if ($tokens[$prevNonEmpty]['line'] !== $tokens[$stackPtr]['line']) {
                $spaces = 'newline';
                $errorCode = 'NewlineBeforeKeyword';
            }
            else {
                if ($tokens[$stackPtr - 1]['code'] === T_WHITESPACE) {
                    $spaces = $tokens[$stackPtr - 1]['length'];
                }
            }
            if ($spaces !== 1) {
                $error = 'Expected 1 space between %s and %s keywords; %s found';
                $data = [
                    strtolower($tokens[$prevNonEmpty]['content']),
                    $stackPtrType,
                    $spaces,
                ];
                if ($prevNonSpace !== $prevNonEmpty) {
                    // Comment found between modifier and class keyword. Do not auto-fix.
                    $phpcsFile->addError($error, $stackPtr, $errorCode, $data);
                }
                else {
                    $fix = $phpcsFile->addFixableError($error, $stackPtr, $errorCode, $data);
                    if ($fix === true) {
                        if ($spaces === 0) {
                            $phpcsFile->fixer
                                ->addContentBefore($stackPtr, ' ');
                        }
                        else {
                            $phpcsFile->fixer
                                ->beginChangeset();
                            $phpcsFile->fixer
                                ->replaceToken($stackPtr - 1, ' ');
                            for ($i = $stackPtr - 2; $i > $prevNonSpace; $i--) {
                                $phpcsFile->fixer
                                    ->replaceToken($i, ' ');
                            }
                            $phpcsFile->fixer
                                ->endChangeset();
                        }
                    }
                }
            }
            
            //end if
        }
        
        //end if
        // We'll need the indent of the class/interface declaration for later.
        $classIndent = 0;
        for ($i = $stackPtr - 1; $i > 0; $i--) {
            if ($tokens[$i]['line'] === $tokens[$stackPtr]['line']) {
                continue;
            }
            // We changed lines.
            if ($tokens[$i + 1]['code'] === T_WHITESPACE) {
                $classIndent = $tokens[$i + 1]['length'];
            }
            break;
        }
        $className = null;
        $checkSpacing = true;
        if ($tokens[$stackPtr]['code'] !== T_ANON_CLASS) {
            $className = $phpcsFile->findNext(T_STRING, $stackPtr);
        }
        else {
            // Ignore the spacing check if this is a simple anon class.
            $next = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true);
            if ($next === $tokens[$stackPtr]['scope_opener'] && $tokens[$next]['line'] > $tokens[$stackPtr]['line']) {
                $checkSpacing = false;
            }
        }
        if ($checkSpacing === true) {
            // Spacing of the keyword.
            if ($tokens[$stackPtr + 1]['code'] !== T_WHITESPACE) {
                $gap = 0;
            }
            else {
                if ($tokens[$stackPtr + 2]['line'] !== $tokens[$stackPtr]['line']) {
                    $gap = 'newline';
                }
                else {
                    $gap = $tokens[$stackPtr + 1]['length'];
                }
            }
            if ($gap !== 1) {
                $error = 'Expected 1 space after %s keyword; %s found';
                $data = [
                    $stackPtrType,
                    $gap,
                ];
                $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data);
                if ($fix === true) {
                    if ($gap === 0) {
                        $phpcsFile->fixer
                            ->addContent($stackPtr, ' ');
                    }
                    else {
                        $phpcsFile->fixer
                            ->replaceToken($stackPtr + 1, ' ');
                    }
                }
            }
        }
        
        //end if
        // Check after the class/interface name.
        if ($className !== null && $tokens[$className + 2]['line'] === $tokens[$className]['line']) {
            $gap = $tokens[$className + 1]['content'];
            if (strlen($gap) !== 1) {
                $found = strlen($gap);
                $error = 'Expected 1 space after %s name; %s found';
                $data = [
                    $stackPtrType,
                    $found,
                ];
                $fix = $phpcsFile->addFixableError($error, $className, 'SpaceAfterName', $data);
                if ($fix === true) {
                    $phpcsFile->fixer
                        ->replaceToken($className + 1, ' ');
                }
            }
        }
        $openingBrace = $tokens[$stackPtr]['scope_opener'];
        // Check positions of the extends and implements keywords.
        $compareToken = $stackPtr;
        $compareType = 'name';
        if ($tokens[$stackPtr]['code'] === T_ANON_CLASS) {
            if (isset($tokens[$stackPtr]['parenthesis_opener']) === true) {
                $compareToken = $tokens[$stackPtr]['parenthesis_closer'];
                $compareType = 'closing parenthesis';
            }
            else {
                $compareType = 'keyword';
            }
        }
        foreach ([
            'extends',
            'implements',
        ] as $keywordType) {
            $keyword = $phpcsFile->findNext(constant('T_' . strtoupper($keywordType)), $compareToken + 1, $openingBrace);
            if ($keyword !== false) {
                if ($tokens[$keyword]['line'] !== $tokens[$compareToken]['line']) {
                    $error = 'The ' . $keywordType . ' keyword must be on the same line as the %s ' . $compareType;
                    $data = [
                        $stackPtrType,
                    ];
                    $fix = $phpcsFile->addFixableError($error, $keyword, ucfirst($keywordType) . 'Line', $data);
                    if ($fix === true) {
                        $phpcsFile->fixer
                            ->beginChangeset();
                        $comments = [];
                        for ($i = $compareToken + 1; $i < $keyword; ++$i) {
                            if ($tokens[$i]['code'] === T_COMMENT) {
                                $comments[] = trim($tokens[$i]['content']);
                            }
                            if ($tokens[$i]['code'] === T_WHITESPACE || $tokens[$i]['code'] === T_COMMENT) {
                                $phpcsFile->fixer
                                    ->replaceToken($i, ' ');
                            }
                        }
                        $phpcsFile->fixer
                            ->addContent($compareToken, ' ');
                        if (empty($comments) === false) {
                            $i = $keyword;
                            while ($tokens[$i + 1]['line'] === $tokens[$keyword]['line']) {
                                ++$i;
                            }
                            $phpcsFile->fixer
                                ->addContentBefore($i, ' ' . implode(' ', $comments));
                        }
                        $phpcsFile->fixer
                            ->endChangeset();
                    }
                    
                    //end if
                }
                else {
                    // Check the whitespace before. Whitespace after is checked
                    // later by looking at the whitespace before the first class name
                    // in the list.
                    $gap = $tokens[$keyword - 1]['length'];
                    if ($gap !== 1) {
                        $error = 'Expected 1 space before ' . $keywordType . ' keyword; %s found';
                        $data = [
                            $gap,
                        ];
                        $fix = $phpcsFile->addFixableError($error, $keyword, 'SpaceBefore' . ucfirst($keywordType), $data);
                        if ($fix === true) {
                            $phpcsFile->fixer
                                ->replaceToken($keyword - 1, ' ');
                        }
                    }
                }
                
                //end if
            }
            
            //end if
        }
        
        //end foreach
        // Check each of the extends/implements class names. If the extends/implements
        // keyword is the last content on the line, it means we need to check for
        // the multi-line format, so we do not include the class names
        // from the extends/implements list in the following check.
        // Note that classes can only extend one other class, so they can't use a
        // multi-line extends format, whereas an interface can extend multiple
        // other interfaces, and so uses a multi-line extends format.
        if ($tokens[$stackPtr]['code'] === T_INTERFACE) {
            $keywordTokenType = T_EXTENDS;
        }
        else {
            $keywordTokenType = T_IMPLEMENTS;
        }
        $implements = $phpcsFile->findNext($keywordTokenType, $stackPtr + 1, $openingBrace);
        $multiLineImplements = false;
        if ($implements !== false) {
            $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, $openingBrace - 1, $implements, true);
            if ($tokens[$prev]['line'] !== $tokens[$implements]['line']) {
                $multiLineImplements = true;
            }
        }
        $find = [
            T_STRING,
            $keywordTokenType,
        ];
        if ($className !== null) {
            $start = $className;
        }
        else {
            if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
                $start = $tokens[$stackPtr]['parenthesis_closer'];
            }
            else {
                $start = $stackPtr;
            }
        }
        $classNames = [];
        $nextClass = $phpcsFile->findNext($find, $start + 2, $openingBrace - 1);
        while ($nextClass !== false) {
            $classNames[] = $nextClass;
            $nextClass = $phpcsFile->findNext($find, $nextClass + 1, $openingBrace - 1);
        }
        $classCount = count($classNames);
        $checkingImplements = false;
        $implementsToken = null;
        foreach ($classNames as $n => $className) {
            if ($tokens[$className]['code'] === $keywordTokenType) {
                $checkingImplements = true;
                $implementsToken = $className;
                continue;
            }
            if ($checkingImplements === true && $multiLineImplements === true && ($tokens[$className - 1]['code'] !== T_NS_SEPARATOR || $tokens[$className - 2]['code'] !== T_STRING && $tokens[$className - 2]['code'] !== T_NAMESPACE)) {
                $prev = $phpcsFile->findPrevious([
                    T_NS_SEPARATOR,
                    T_WHITESPACE,
                ], $className - 1, $implements, true);
                if ($prev === $implementsToken && $tokens[$className]['line'] !== $tokens[$prev]['line'] + 1) {
                    if ($keywordTokenType === T_EXTENDS) {
                        $error = 'The first item in a multi-line extends list must be on the line following the extends keyword';
                        $fix = $phpcsFile->addFixableError($error, $className, 'FirstExtendsInterfaceSameLine');
                    }
                    else {
                        $error = 'The first item in a multi-line implements list must be on the line following the implements keyword';
                        $fix = $phpcsFile->addFixableError($error, $className, 'FirstInterfaceSameLine');
                    }
                    if ($fix === true) {
                        $phpcsFile->fixer
                            ->beginChangeset();
                        for ($i = $prev + 1; $i < $className; $i++) {
                            if ($tokens[$i]['code'] !== T_WHITESPACE) {
                                break;
                            }
                            $phpcsFile->fixer
                                ->replaceToken($i, '');
                        }
                        $phpcsFile->fixer
                            ->addNewline($prev);
                        $phpcsFile->fixer
                            ->endChangeset();
                    }
                }
                else {
                    if (isset(Tokens::$commentTokens[$tokens[$prev]['code']]) === false && $tokens[$prev]['line'] !== $tokens[$className]['line'] - 1 || $tokens[$prev]['line'] === $tokens[$className]['line']) {
                        if ($keywordTokenType === T_EXTENDS) {
                            $error = 'Only one interface may be specified per line in a multi-line extends declaration';
                            $fix = $phpcsFile->addFixableError($error, $className, 'ExtendsInterfaceSameLine');
                        }
                        else {
                            $error = 'Only one interface may be specified per line in a multi-line implements declaration';
                            $fix = $phpcsFile->addFixableError($error, $className, 'InterfaceSameLine');
                        }
                        if ($fix === true) {
                            $phpcsFile->fixer
                                ->beginChangeset();
                            for ($i = $prev + 1; $i < $className; $i++) {
                                if ($tokens[$i]['code'] !== T_WHITESPACE) {
                                    break;
                                }
                                $phpcsFile->fixer
                                    ->replaceToken($i, '');
                            }
                            $phpcsFile->fixer
                                ->addNewline($prev);
                            $phpcsFile->fixer
                                ->endChangeset();
                        }
                    }
                    else {
                        $prev = $phpcsFile->findPrevious(T_WHITESPACE, $className - 1, $implements);
                        if ($tokens[$prev]['line'] !== $tokens[$className]['line']) {
                            $found = 0;
                        }
                        else {
                            $found = $tokens[$prev]['length'];
                        }
                        $expected = $classIndent + $this->indent;
                        if ($found !== $expected) {
                            $error = 'Expected %s spaces before interface name; %s found';
                            $data = [
                                $expected,
                                $found,
                            ];
                            $fix = $phpcsFile->addFixableError($error, $className, 'InterfaceWrongIndent', $data);
                            if ($fix === true) {
                                $padding = str_repeat(' ', $expected);
                                if ($found === 0) {
                                    $phpcsFile->fixer
                                        ->addContent($prev, $padding);
                                }
                                else {
                                    $phpcsFile->fixer
                                        ->replaceToken($prev, $padding);
                                }
                            }
                        }
                    }
                }
                
                //end if
            }
            else {
                if ($tokens[$className - 1]['code'] !== T_NS_SEPARATOR || $tokens[$className - 2]['code'] !== T_STRING && $tokens[$className - 2]['code'] !== T_NAMESPACE) {
                    // Not part of a longer fully qualified or namespace relative class name.
                    if ($tokens[$className - 1]['code'] === T_COMMA || $tokens[$className - 1]['code'] === T_NS_SEPARATOR && $tokens[$className - 2]['code'] === T_COMMA) {
                        $error = 'Expected 1 space before "%s"; 0 found';
                        $data = [
                            $tokens[$className]['content'],
                        ];
                        $fix = $phpcsFile->addFixableError($error, $nextComma + 1, 'NoSpaceBeforeName', $data);
                        if ($fix === true) {
                            $phpcsFile->fixer
                                ->addContentBefore($nextComma + 1, ' ');
                        }
                    }
                    else {
                        if ($tokens[$className - 1]['code'] === T_NS_SEPARATOR) {
                            $prev = $className - 2;
                        }
                        else {
                            $prev = $className - 1;
                        }
                        $last = $phpcsFile->findPrevious(T_WHITESPACE, $prev, null, true);
                        $content = $phpcsFile->getTokensAsString($last + 1, $prev - $last);
                        if ($content !== ' ') {
                            $found = strlen($content);
                            $error = 'Expected 1 space before "%s"; %s found';
                            $data = [
                                $tokens[$className]['content'],
                                $found,
                            ];
                            $fix = $phpcsFile->addFixableError($error, $className, 'SpaceBeforeName', $data);
                            if ($fix === true) {
                                if ($tokens[$prev]['code'] === T_WHITESPACE) {
                                    $phpcsFile->fixer
                                        ->beginChangeset();
                                    $phpcsFile->fixer
                                        ->replaceToken($prev, ' ');
                                    while ($tokens[--$prev]['code'] === T_WHITESPACE) {
                                        $phpcsFile->fixer
                                            ->replaceToken($prev, ' ');
                                    }
                                    $phpcsFile->fixer
                                        ->endChangeset();
                                }
                                else {
                                    $phpcsFile->fixer
                                        ->addContent($prev, ' ');
                                }
                            }
                        }
                        
                        //end if
                    }
                    
                    //end if
                }
            }
            
            //end if
            if ($checkingImplements === true && $tokens[$className + 1]['code'] !== T_NS_SEPARATOR && $tokens[$className + 1]['code'] !== T_COMMA) {
                if ($n !== $classCount - 1) {
                    // This is not the last class name, and the comma
                    // is not where we expect it to be.
                    if ($tokens[$className + 2]['code'] !== $keywordTokenType) {
                        $error = 'Expected 0 spaces between "%s" and comma; %s found';
                        $data = [
                            $tokens[$className]['content'],
                            $tokens[$className + 1]['length'],
                        ];
                        $fix = $phpcsFile->addFixableError($error, $className, 'SpaceBeforeComma', $data);
                        if ($fix === true) {
                            $phpcsFile->fixer
                                ->replaceToken($className + 1, '');
                        }
                    }
                }
                $nextComma = $phpcsFile->findNext(T_COMMA, $className);
            }
            else {
                $nextComma = $className + 1;
            }
            
            //end if
        }
        
        //end foreach
    }
    
    //end processOpen()
    
    /**
     * Processes the closing section of a class declaration.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
     * @param int                         $stackPtr  The position of the current token
     *                                               in the stack passed in $tokens.
     *
     * @return void
     */
    public function processClose(File $phpcsFile, $stackPtr) {
        $tokens = $phpcsFile->getTokens();
        // Check that the closing brace comes right after the code body.
        $closeBrace = $tokens[$stackPtr]['scope_closer'];
        $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, $closeBrace - 1, null, true);
        if ($prevContent !== $tokens[$stackPtr]['scope_opener'] && $tokens[$prevContent]['line'] !== $tokens[$closeBrace]['line'] - 1) {
            $error = 'The closing brace for the %s must go on the next line after the body';
            $data = [
                $tokens[$stackPtr]['content'],
            ];
            $fix = $phpcsFile->addFixableError($error, $closeBrace, 'CloseBraceAfterBody', $data);
            if ($fix === true) {
                $phpcsFile->fixer
                    ->beginChangeset();
                for ($i = $prevContent + 1; $tokens[$i]['line'] !== $tokens[$closeBrace]['line']; $i++) {
                    $phpcsFile->fixer
                        ->replaceToken($i, '');
                }
                if (strpos($tokens[$prevContent]['content'], $phpcsFile->eolChar) === false) {
                    $phpcsFile->fixer
                        ->addNewline($prevContent);
                }
                $phpcsFile->fixer
                    ->endChangeset();
            }
        }
        
        //end if
        if ($tokens[$stackPtr]['code'] !== T_ANON_CLASS) {
            // Check the closing brace is on it's own line, but allow
            // for comments like "//end class".
            $ignoreTokens = Tokens::$phpcsCommentTokens;
            $ignoreTokens[] = T_WHITESPACE;
            $ignoreTokens[] = T_COMMENT;
            $ignoreTokens[] = T_SEMICOLON;
            $nextContent = $phpcsFile->findNext($ignoreTokens, $closeBrace + 1, null, true);
            if ($tokens[$nextContent]['line'] === $tokens[$closeBrace]['line']) {
                $type = strtolower($tokens[$stackPtr]['content']);
                $error = 'Closing %s brace must be on a line by itself';
                $data = [
                    $type,
                ];
                $phpcsFile->addError($error, $closeBrace, 'CloseBraceSameLine', $data);
            }
        }
    }
    
    //end processClose()

}

//end class

Classes

Title Deprecated Summary
ClassDeclarationSniff

API Navigation

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