PHP_CodeSniffer
[ class tree: PHP_CodeSniffer ] [ index: PHP_CodeSniffer ] [ all elements ]

Source for file ClassDeclarationSniff.php

Documentation is available at ClassDeclarationSniff.php

  1. <?php
  2. /**
  3.  * Checks the declaration of the class and its inheritance is correct.
  4.  *
  5.  * @author    Greg Sherwood <gsherwood@squiz.net>
  6.  * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  7.  * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  8.  */
  9.  
  10. namespace PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes;
  11.  
  12. use PHP_CodeSniffer\Standards\PEAR\Sniffs\Classes\ClassDeclarationSniff as PEARClassDeclarationSniff;
  13. use PHP_CodeSniffer\Util\Tokens;
  14. use PHP_CodeSniffer\Files\File;
  15.  
  16. class ClassDeclarationSniff extends PEARClassDeclarationSniff
  17. {
  18.  
  19.     /**
  20.      * The number of spaces code should be indented.
  21.      *
  22.      * @var integer 
  23.      */
  24.     public $indent = 4;
  25.  
  26.  
  27.     /**
  28.      * Processes this test, when one of its tokens is encountered.
  29.      *
  30.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  31.      * @param int                         $stackPtr  The position of the current token
  32.      *                                                in the stack passed in $tokens.
  33.      *
  34.      * @return void 
  35.      */
  36.     public function process(File $phpcsFile$stackPtr)
  37.     {
  38.         // We want all the errors from the PEAR standard, plus some of our own.
  39.         parent::process($phpcsFile$stackPtr);
  40.  
  41.         // Just in case.
  42.         $tokens $phpcsFile->getTokens();
  43.         if (isset($tokens[$stackPtr]['scope_opener']=== false{
  44.             return;
  45.         }
  46.  
  47.         $this->processOpen($phpcsFile$stackPtr);
  48.         $this->processClose($phpcsFile$stackPtr);
  49.  
  50.     }//end process()
  51.  
  52.  
  53.     /**
  54.      * Processes the opening section of a class declaration.
  55.      *
  56.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  57.      * @param int                         $stackPtr  The position of the current token
  58.      *                                                in the stack passed in $tokens.
  59.      *
  60.      * @return void 
  61.      */
  62.     public function processOpen(File $phpcsFile$stackPtr)
  63.     {
  64.         $tokens       $phpcsFile->getTokens();
  65.         $stackPtrType strtolower($tokens[$stackPtr]['content']);
  66.  
  67.         // Check alignment of the keyword and braces.
  68.         if ($tokens[($stackPtr - 1)]['code'=== T_WHITESPACE{
  69.             $prevContent $tokens[($stackPtr - 1)]['content'];
  70.             if ($prevContent !== $phpcsFile->eolChar{
  71.                 $blankSpace substr($prevContentstrpos($prevContent$phpcsFile->eolChar));
  72.                 $spaces     strlen($blankSpace);
  73.  
  74.                 if (in_array($tokens[($stackPtr - 2)]['code']array(T_ABSTRACTT_FINAL)) === true
  75.                     && $spaces !== 1
  76.                 {
  77.                     $prevContent strtolower($tokens[($stackPtr - 2)]['content']);
  78.                     $error       'Expected 1 space between %s and %s keywords; %s found';
  79.                     $data        = array(
  80.                                     $prevContent,
  81.                                     $stackPtrType,
  82.                                     $spaces,
  83.                                    );
  84.  
  85.                     $fix $phpcsFile->addFixableError($error$stackPtr'SpaceBeforeKeyword'$data);
  86.                     if ($fix === true{
  87.                         $phpcsFile->fixer->replaceToken(($stackPtr - 1)' ');
  88.                     }
  89.                 }
  90.             else if ($tokens[($stackPtr - 2)]['code'=== T_ABSTRACT
  91.                 || $tokens[($stackPtr - 2)]['code'=== T_FINAL
  92.             {
  93.                 $prevContent strtolower($tokens[($stackPtr - 2)]['content']);
  94.                 $error       'Expected 1 space between %s and %s keywords; newline found';
  95.                 $data        = array(
  96.                                 $prevContent,
  97.                                 $stackPtrType,
  98.                                );
  99.  
  100.                 $fix $phpcsFile->addFixableError($error$stackPtr'NewlineBeforeKeyword'$data);
  101.                 if ($fix === true{
  102.                     $phpcsFile->fixer->replaceToken(($stackPtr - 1)' ');
  103.                 }
  104.             }//end if
  105.         }//end if
  106.  
  107.         // We'll need the indent of the class/interface declaration for later.
  108.         $classIndent = 0;
  109.         for ($i ($stackPtr - 1)$i > 0; $i--{
  110.             if ($tokens[$i]['line'=== $tokens[$stackPtr]['line']{
  111.                 continue;
  112.             }
  113.  
  114.             // We changed lines.
  115.             if ($tokens[($i + 1)]['code'=== T_WHITESPACE{
  116.                 $classIndent strlen($tokens[($i + 1)]['content']);
  117.             }
  118.  
  119.             break;
  120.         }
  121.  
  122.         $className $phpcsFile->findNext(T_STRING$stackPtr);
  123.  
  124.         // Spacing of the keyword.
  125.         $gap $tokens[($stackPtr + 1)]['content'];
  126.         if (strlen($gap!== 1{
  127.             $found strlen($gap);
  128.             $error 'Expected 1 space between %s keyword and %s name; %s found';
  129.             $data  = array(
  130.                       $stackPtrType,
  131.                       $stackPtrType,
  132.                       $found,
  133.                      );
  134.  
  135.             $fix $phpcsFile->addFixableError($error$stackPtr'SpaceAfterKeyword'$data);
  136.             if ($fix === true{
  137.                 $phpcsFile->fixer->replaceToken(($stackPtr + 1)' ');
  138.             }
  139.         }
  140.  
  141.         // Check after the class/interface name.
  142.         if ($tokens[($className + 2)]['line'=== $tokens[$className]['line']{
  143.             $gap $tokens[($className + 1)]['content'];
  144.             if (strlen($gap!== 1{
  145.                 $found strlen($gap);
  146.                 $error 'Expected 1 space after %s name; %s found';
  147.                 $data  = array(
  148.                           $stackPtrType,
  149.                           $found,
  150.                          );
  151.  
  152.                 $fix $phpcsFile->addFixableError($error$className'SpaceAfterName'$data);
  153.                 if ($fix === true{
  154.                     $phpcsFile->fixer->replaceToken(($className + 1)' ');
  155.                 }
  156.             }
  157.         }
  158.  
  159.         $openingBrace $tokens[$stackPtr]['scope_opener'];
  160.  
  161.         // Check positions of the extends and implements keywords.
  162.         foreach (array('extends''implements'as $keywordType{
  163.             $keyword $phpcsFile->findNext(constant('T_'.strtoupper($keywordType))($stackPtr + 1)$openingBrace);
  164.             if ($keyword !== false{
  165.                 if ($tokens[$keyword]['line'!== $tokens[$stackPtr]['line']{
  166.                     $error 'The '.$keywordType.' keyword must be on the same line as the %s name';
  167.                     $data  = array($stackPtrType);
  168.                     $fix   $phpcsFile->addFixableError($error$keyworducfirst($keywordType).'Line'$data);
  169.                     if ($fix === true{
  170.                         $phpcsFile->fixer->beginChangeset();
  171.                         for ($i ($stackPtr + 1)$i $keyword$i++{
  172.                             if ($tokens[$i]['line'!== $tokens[($i + 1)]['line']{
  173.                                 $phpcsFile->fixer->substrToken($i0(strlen($phpcsFile->eolChar* -1));
  174.                             }
  175.                         }
  176.  
  177.                         $phpcsFile->fixer->addContentBefore($keyword' ');
  178.                         $phpcsFile->fixer->endChangeset();
  179.                     }
  180.                 else {
  181.                     // Check the whitespace before. Whitespace after is checked
  182.                     // later by looking at the whitespace before the first class name
  183.                     // in the list.
  184.                     $gap strlen($tokens[($keyword - 1)]['content']);
  185.                     if ($gap !== 1{
  186.                         $error 'Expected 1 space before '.$keywordType.' keyword; %s found';
  187.                         $data  = array($gap);
  188.                         $fix   $phpcsFile->addFixableError($error$keyword'SpaceBefore'.ucfirst($keywordType)$data);
  189.                         if ($fix === true{
  190.                             $phpcsFile->fixer->replaceToken(($keyword - 1)' ');
  191.                         }
  192.                     }
  193.                 }//end if
  194.             }//end if
  195.         }//end foreach
  196.  
  197.         // Check each of the extends/implements class names. If the extends/implements
  198.         // keyword is the last content on the line, it means we need to check for
  199.         // the multi-line format, so we do not include the class names
  200.         // from the extends/implements list in the following check.
  201.         // Note that classes can only extend one other class, so they can't use a
  202.         // multi-line extends format, whereas an interface can extend multiple
  203.         // other interfaces, and so uses a multi-line extends format.
  204.         if ($tokens[$stackPtr]['code'=== T_INTERFACE{
  205.             $keywordTokenType = T_EXTENDS;
  206.         else {
  207.             $keywordTokenType = T_IMPLEMENTS;
  208.         }
  209.  
  210.         $implements          $phpcsFile->findNext($keywordTokenType($stackPtr + 1)$openingBrace);
  211.         $multiLineImplements = false;
  212.         if ($implements !== false{
  213.             $prev $phpcsFile->findPrevious(Tokens::$emptyTokens($openingBrace - 1)$implementstrue);
  214.             if ($tokens[$prev]['line'!== $tokens[$implements]['line']{
  215.                 $multiLineImplements = true;
  216.             }
  217.         }
  218.  
  219.         $find = array(
  220.                  T_STRING,
  221.                  $keywordTokenType,
  222.                 );
  223.  
  224.         $classNames = array();
  225.         $nextClass  $phpcsFile->findNext($find($className + 2)($openingBrace - 1));
  226.         while ($nextClass !== false{
  227.             $classNames[$nextClass;
  228.             $nextClass    $phpcsFile->findNext($find($nextClass + 1)($openingBrace - 1));
  229.         }
  230.  
  231.         $classCount         count($classNames);
  232.         $checkingImplements = false;
  233.         $implementsToken    = null;
  234.         foreach ($classNames as $i => $className{
  235.             if ($tokens[$className]['code'=== $keywordTokenType{
  236.                 $checkingImplements = true;
  237.                 $implementsToken    $className;
  238.                 continue;
  239.             }
  240.  
  241.             if ($checkingImplements === true
  242.                 && $multiLineImplements === true
  243.                 && ($tokens[($className - 1)]['code'!== T_NS_SEPARATOR
  244.                 || $tokens[($className - 2)]['code'!== T_STRING)
  245.             {
  246.                 $prev $phpcsFile->findPrevious(
  247.                     array(
  248.                      T_NS_SEPARATOR,
  249.                      T_WHITESPACE,
  250.                     ),
  251.                     ($className - 1),
  252.                     $implements,
  253.                     true
  254.                 );
  255.  
  256.                 if ($prev === $implementsToken && $tokens[$className]['line'!== ($tokens[$prev]['line'+ 1)) {
  257.                     if ($keywordTokenType === T_EXTENDS{
  258.                         $error 'The first item in a multi-line extends list must be on the line following the extends keyword';
  259.                         $fix   $phpcsFile->addFixableError($error$className'FirstExtendsInterfaceSameLine');
  260.                     else {
  261.                         $error 'The first item in a multi-line implements list must be on the line following the implements keyword';
  262.                         $fix   $phpcsFile->addFixableError($error$className'FirstInterfaceSameLine');
  263.                     }
  264.  
  265.                     if ($fix === true{
  266.                         $phpcsFile->fixer->beginChangeset();
  267.                         for ($i ($prev + 1)$i $className$i++{
  268.                             if ($tokens[$i]['code'!== T_WHITESPACE{
  269.                                 break;
  270.                             }
  271.  
  272.                             $phpcsFile->fixer->replaceToken($i'');
  273.                         }
  274.  
  275.                         $phpcsFile->fixer->addNewline($prev);
  276.                         $phpcsFile->fixer->endChangeset();
  277.                     }
  278.                 else if ($tokens[$prev]['line'!== ($tokens[$className]['line'- 1)) {
  279.                     if ($keywordTokenType === T_EXTENDS{
  280.                         $error 'Only one interface may be specified per line in a multi-line extends declaration';
  281.                         $fix   $phpcsFile->addFixableError($error$className'ExtendsInterfaceSameLine');
  282.                     else {
  283.                         $error 'Only one interface may be specified per line in a multi-line implements declaration';
  284.                         $fix   $phpcsFile->addFixableError($error$className'InterfaceSameLine');
  285.                     }
  286.  
  287.                     if ($fix === true{
  288.                         $phpcsFile->fixer->beginChangeset();
  289.                         for ($i ($prev + 1)$i $className$i++{
  290.                             if ($tokens[$i]['code'!== T_WHITESPACE{
  291.                                 break;
  292.                             }
  293.  
  294.                             $phpcsFile->fixer->replaceToken($i'');
  295.                         }
  296.  
  297.                         $phpcsFile->fixer->addNewline($prev);
  298.                         $phpcsFile->fixer->endChangeset();
  299.                     }
  300.                 else {
  301.                     $prev $phpcsFile->findPrevious(T_WHITESPACE($className - 1)$implements);
  302.                     if ($tokens[$prev]['line'!== $tokens[$className]['line']{
  303.                         $found = 0;
  304.                     else {
  305.                         $found strlen($tokens[$prev]['content']);
  306.                     }
  307.  
  308.                     $expected ($classIndent $this->indent);
  309.                     if ($found !== $expected{
  310.                         $error 'Expected %s spaces before interface name; %s found';
  311.                         $data  = array(
  312.                                   $expected,
  313.                                   $found,
  314.                                  );
  315.                         $fix   $phpcsFile->addFixableError($error$className'InterfaceWrongIndent'$data);
  316.                         if ($fix === true{
  317.                             $padding str_repeat(' '$expected);
  318.                             if ($found === 0{
  319.                                 $phpcsFile->fixer->addContent($prev$padding);
  320.                             else {
  321.                                 $phpcsFile->fixer->replaceToken($prev$padding);
  322.                             }
  323.                         }
  324.                     }
  325.                 }//end if
  326.             else if ($tokens[($className - 1)]['code'!== T_NS_SEPARATOR
  327.                 || $tokens[($className - 2)]['code'!== T_STRING
  328.             {
  329.                 // Not part of a longer fully qualified class name.
  330.                 if ($tokens[($className - 1)]['code'=== T_COMMA
  331.                     || ($tokens[($className - 1)]['code'=== T_NS_SEPARATOR
  332.                     && $tokens[($className - 2)]['code'=== T_COMMA)
  333.                 {
  334.                     $error 'Expected 1 space before "%s"; 0 found';
  335.                     $data  = array($tokens[$className]['content']);
  336.                     $fix   $phpcsFile->addFixableError($error($nextComma + 1)'NoSpaceBeforeName'$data);
  337.                     if ($fix === true{
  338.                         $phpcsFile->fixer->addContentBefore(($nextComma + 1)' ');
  339.                     }
  340.                 else {
  341.                     if ($tokens[($className - 1)]['code'=== T_NS_SEPARATOR{
  342.                         $prev ($className - 2);
  343.                     else {
  344.                         $prev ($className - 1);
  345.                     }
  346.  
  347.                     $spaceBefore strlen($tokens[$prev]['content']);
  348.                     if ($spaceBefore !== 1{
  349.                         $error 'Expected 1 space before "%s"; %s found';
  350.                         $data  = array(
  351.                                   $tokens[$className]['content'],
  352.                                   $spaceBefore,
  353.                                  );
  354.  
  355.                         $fix $phpcsFile->addFixableError($error$className'SpaceBeforeName'$data);
  356.                         if ($fix === true{
  357.                             $phpcsFile->fixer->replaceToken($prev' ');
  358.                         }
  359.                     }
  360.                 }//end if
  361.             }//end if
  362.  
  363.             if ($checkingImplements === true
  364.                 && $tokens[($className + 1)]['code'!== T_NS_SEPARATOR
  365.                 && $tokens[($className + 1)]['code'!== T_COMMA
  366.             {
  367.                 if ($i !== ($classCount - 1)) {
  368.                     // This is not the last class name, and the comma
  369.                     // is not where we expect it to be.
  370.                     if ($tokens[($className + 2)]['code'!== $keywordTokenType{
  371.                         $error 'Expected 0 spaces between "%s" and comma; %s found';
  372.                         $data  = array(
  373.                                   $tokens[$className]['content'],
  374.                                   strlen($tokens[($className + 1)]['content']),
  375.                                  );
  376.  
  377.                         $fix $phpcsFile->addFixableError($error$className'SpaceBeforeComma'$data);
  378.                         if ($fix === true{
  379.                             $phpcsFile->fixer->replaceToken(($className + 1)'');
  380.                         }
  381.                     }
  382.                 }
  383.  
  384.                 $nextComma $phpcsFile->findNext(T_COMMA$className);
  385.             else {
  386.                 $nextComma ($className + 1);
  387.             }//end if
  388.         }//end foreach
  389.  
  390.     }//end processOpen()
  391.  
  392.  
  393.     /**
  394.      * Processes the closing section of a class declaration.
  395.      *
  396.      * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
  397.      * @param int                         $stackPtr  The position of the current token
  398.      *                                                in the stack passed in $tokens.
  399.      *
  400.      * @return void 
  401.      */
  402.     public function processClose(File $phpcsFile$stackPtr)
  403.     {
  404.         $tokens $phpcsFile->getTokens();
  405.  
  406.         // Check that the closing brace comes right after the code body.
  407.         $closeBrace  $tokens[$stackPtr]['scope_closer'];
  408.         $prevContent $phpcsFile->findPrevious(T_WHITESPACE($closeBrace - 1)nulltrue);
  409.         if ($prevContent !== $tokens[$stackPtr]['scope_opener']
  410.             && $tokens[$prevContent]['line'!== ($tokens[$closeBrace]['line'- 1)
  411.         {
  412.             $error 'The closing brace for the %s must go on the next line after the body';
  413.             $data  = array($tokens[$stackPtr]['content']);
  414.             $fix   $phpcsFile->addFixableError($error$closeBrace'CloseBraceAfterBody'$data);
  415.  
  416.             if ($fix === true{
  417.                 $phpcsFile->fixer->beginChangeset();
  418.                 for ($i ($prevContent + 1)$i $closeBrace$i++{
  419.                     $phpcsFile->fixer->replaceToken($i'');
  420.                 }
  421.  
  422.                 if (strpos($tokens[$prevContent]['content']$phpcsFile->eolChar=== false{
  423.                     $phpcsFile->fixer->replaceToken($closeBrace$phpcsFile->eolChar.$tokens[$closeBrace]['content']);
  424.                 }
  425.  
  426.                 $phpcsFile->fixer->endChangeset();
  427.             }
  428.         }//end if
  429.  
  430.         // Check the closing brace is on it's own line, but allow
  431.         // for comments like "//end class".
  432.         $nextContent $phpcsFile->findNext(array(T_WHITESPACET_COMMENT)($closeBrace + 1)nulltrue);
  433.         if ($tokens[$nextContent]['content'!== $phpcsFile->eolChar
  434.             && $tokens[$nextContent]['line'=== $tokens[$closeBrace]['line']
  435.         {
  436.             $type  strtolower($tokens[$stackPtr]['content']);
  437.             $error 'Closing %s brace must be on a line by itself';
  438.             $data  = array($type);
  439.             $phpcsFile->addError($error$closeBrace'CloseBraceSameLine'$data);
  440.         }
  441.  
  442.     }//end processClose()
  443.  
  444.  
  445. }//end class

Documentation generated on Mon, 11 Mar 2019 15:27:17 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.