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

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