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

Source for file AbstractPatternSniff.php

Documentation is available at AbstractPatternSniff.php

  1. <?php
  2. /**
  3.  * Processes pattern strings and checks that the code conforms to the pattern.
  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\Sniffs;
  11.  
  12. use PHP_CodeSniffer\Files\File;
  13. use PHP_CodeSniffer\Config;
  14. use PHP_CodeSniffer\Util\Tokens;
  15. use PHP_CodeSniffer\Tokenizers\PHP;
  16. use PHP_CodeSniffer\Exceptions\RuntimeException;
  17.  
  18. abstract class AbstractPatternSniff implements Sniff
  19. {
  20.  
  21.     /**
  22.      * If true, comments will be ignored if they are found in the code.
  23.      *
  24.      * @var boolean 
  25.      */
  26.     public $ignoreComments = false;
  27.  
  28.     /**
  29.      * The current file being checked.
  30.      *
  31.      * @var string 
  32.      */
  33.     protected $currFile '';
  34.  
  35.     /**
  36.      * The parsed patterns array.
  37.      *
  38.      * @var array 
  39.      */
  40.     private $parsedPatterns = array();
  41.  
  42.     /**
  43.      * Tokens that this sniff wishes to process outside of the patterns.
  44.      *
  45.      * @var int[] 
  46.      * @see registerSupplementary()
  47.      * @see processSupplementary()
  48.      */
  49.     private $supplementaryTokens = array();
  50.  
  51.     /**
  52.      * Positions in the stack where errors have occurred.
  53.      *
  54.      * @var array<int, bool>
  55.      */
  56.     private $errorPos = array();
  57.  
  58.  
  59.     /**
  60.      * Constructs a PHP_CodeSniffer_Standards_AbstractPatternSniff.
  61.      *
  62.      * @param boolean $ignoreComments If true, comments will be ignored.
  63.      */
  64.     public function __construct($ignoreComments=null)
  65.     {
  66.         // This is here for backwards compatibility.
  67.         if ($ignoreComments !== null{
  68.             $this->ignoreComments $ignoreComments;
  69.         }
  70.  
  71.         $this->supplementaryTokens $this->registerSupplementary();
  72.  
  73.     }//end __construct()
  74.  
  75.  
  76.     /**
  77.      * Registers the tokens to listen to.
  78.      *
  79.      * Classes extending <i>AbstractPatternTest</i> should implement the
  80.      * <i>getPatterns()</i> method to register the patterns they wish to test.
  81.      *
  82.      * @return int[] 
  83.      * @see    process()
  84.      */
  85.     final public function register()
  86.     {
  87.         $listenTypes = array();
  88.         $patterns    $this->getPatterns();
  89.  
  90.         foreach ($patterns as $pattern{
  91.             $parsedPattern $this->parse($pattern);
  92.  
  93.             // Find a token position in the pattern that we can use
  94.             // for a listener token.
  95.             $pos           $this->getListenerTokenPos($parsedPattern);
  96.             $tokenType     $parsedPattern[$pos]['token'];
  97.             $listenTypes[$tokenType;
  98.  
  99.             $patternArray = array(
  100.                              'listen_pos'   => $pos,
  101.                              'pattern'      => $parsedPattern,
  102.                              'pattern_code' => $pattern,
  103.                             );
  104.  
  105.             if (isset($this->parsedPatterns[$tokenType]=== false{
  106.                 $this->parsedPatterns[$tokenType= array();
  107.             }
  108.  
  109.             $this->parsedPatterns[$tokenType][$patternArray;
  110.         }//end foreach
  111.  
  112.         return array_unique(array_merge($listenTypes$this->supplementaryTokens));
  113.  
  114.     }//end register()
  115.  
  116.  
  117.     /**
  118.      * Returns the token types that the specified pattern is checking for.
  119.      *
  120.      * Returned array is in the format:
  121.      * <code>
  122.      *   array(
  123.      *      T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token
  124.      *                         // should occur in the pattern.
  125.      *   );
  126.      * </code>
  127.      *
  128.      * @param array $pattern The parsed pattern to find the acquire the token
  129.      *                        types from.
  130.      *
  131.      * @return array<int, int>
  132.      */
  133.     private function getPatternTokenTypes($pattern)
  134.     {
  135.         $tokenTypes = array();
  136.         foreach ($pattern as $pos => $patternInfo{
  137.             if ($patternInfo['type'=== 'token'{
  138.                 if (isset($tokenTypes[$patternInfo['token']]=== false{
  139.                     $tokenTypes[$patternInfo['token']] $pos;
  140.                 }
  141.             }
  142.         }
  143.  
  144.         return $tokenTypes;
  145.  
  146.     }//end getPatternTokenTypes()
  147.  
  148.  
  149.     /**
  150.      * Returns the position in the pattern that this test should register as
  151.      * a listener for the pattern.
  152.      *
  153.      * @param array $pattern The pattern to acquire the listener for.
  154.      *
  155.      * @return int The position in the pattern that this test should register
  156.      *              as the listener.
  157.      * @throws PHP_CodeSniffer_Exception If we could not determine a token
  158.      *                                          to listen for.
  159.      */
  160.     private function getListenerTokenPos($pattern)
  161.     {
  162.         $tokenTypes $this->getPatternTokenTypes($pattern);
  163.         $tokenCodes array_keys($tokenTypes);
  164.         $token      = Tokens::getHighestWeightedToken($tokenCodes);
  165.  
  166.         // If we could not get a token.
  167.         if ($token === false{
  168.             $error 'Could not determine a token to listen for';
  169.             throw new RuntimeException($error);
  170.         }
  171.  
  172.         return $tokenTypes[$token];
  173.  
  174.     }//end getListenerTokenPos()
  175.  
  176.  
  177.     /**
  178.      * Processes the test.
  179.      *
  180.      * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the
  181.      *                                         token occurred.
  182.      * @param int                  $stackPtr  The position in the tokens stack
  183.      *                                         where the listening token type was
  184.      *                                         found.
  185.      *
  186.      * @return void 
  187.      * @see    register()
  188.      */
  189.     final public function process(File $phpcsFile$stackPtr)
  190.     {
  191.         $file $phpcsFile->getFilename();
  192.         if ($this->currFile !== $file{
  193.             // We have changed files, so clean up.
  194.             $this->errorPos = array();
  195.             $this->currFile $file;
  196.         }
  197.  
  198.         $tokens $phpcsFile->getTokens();
  199.  
  200.         if (in_array($tokens[$stackPtr]['code']$this->supplementaryTokens=== true{
  201.             $this->processSupplementary($phpcsFile$stackPtr);
  202.         }
  203.  
  204.         $type $tokens[$stackPtr]['code'];
  205.  
  206.         // If the type is not set, then it must have been a token registered
  207.         // with registerSupplementary().
  208.         if (isset($this->parsedPatterns[$type]=== false{
  209.             return;
  210.         }
  211.  
  212.         $allErrors = array();
  213.  
  214.         // Loop over each pattern that is listening to the current token type
  215.         // that we are processing.
  216.         foreach ($this->parsedPatterns[$typeas $patternInfo{
  217.             // If processPattern returns false, then the pattern that we are
  218.             // checking the code with must not be designed to check that code.
  219.             $errors $this->processPattern($patternInfo$phpcsFile$stackPtr);
  220.             if ($errors === false{
  221.                 // The pattern didn't match.
  222.                 continue;
  223.             else if (empty($errors=== true{
  224.                 // The pattern matched, but there were no errors.
  225.                 break;
  226.             }
  227.  
  228.             foreach ($errors as $stackPtr => $error{
  229.                 if (isset($this->errorPos[$stackPtr]=== false{
  230.                     $this->errorPos[$stackPtr= true;
  231.                     $allErrors[$stackPtr]      $error;
  232.                 }
  233.             }
  234.         }
  235.  
  236.         foreach ($allErrors as $stackPtr => $error{
  237.             $phpcsFile->addError($error$stackPtr'Found');
  238.         }
  239.  
  240.     }//end process()
  241.  
  242.  
  243.     /**
  244.      * Processes the pattern and verifies the code at $stackPtr.
  245.      *
  246.      * @param array                $patternInfo Information about the pattern used
  247.      *                                           for checking, which includes are
  248.      *                                           parsed token representation of the
  249.      *                                           pattern.
  250.      * @param PHP_CodeSniffer_File $phpcsFile   The PHP_CodeSniffer file where the
  251.      *                                           token occurred.
  252.      * @param int                  $stackPtr    The position in the tokens stack where
  253.      *                                           the listening token type was found.
  254.      *
  255.      * @return array 
  256.      */
  257.     protected function processPattern($patternInfoFile $phpcsFile$stackPtr)
  258.     {
  259.         $tokens      $phpcsFile->getTokens();
  260.         $pattern     $patternInfo['pattern'];
  261.         $patternCode $patternInfo['pattern_code'];
  262.         $errors      = array();
  263.         $found       '';
  264.  
  265.         $ignoreTokens = array(T_WHITESPACE);
  266.         if ($this->ignoreComments === true{
  267.             $ignoreTokens
  268.                 = array_merge($ignoreTokensTokens::$commentTokens);
  269.         }
  270.  
  271.         $origStackPtr $stackPtr;
  272.         $hasError     = false;
  273.  
  274.         if ($patternInfo['listen_pos'> 0{
  275.             $stackPtr--;
  276.  
  277.             for ($i ($patternInfo['listen_pos'- 1)$i >= 0; $i--{
  278.                 if ($pattern[$i]['type'=== 'token'{
  279.                     if ($pattern[$i]['token'=== T_WHITESPACE{
  280.                         if ($tokens[$stackPtr]['code'=== T_WHITESPACE{
  281.                             $found $tokens[$stackPtr]['content'].$found;
  282.                         }
  283.  
  284.                         // Only check the size of the whitespace if this is not
  285.                         // the first token. We don't care about the size of
  286.                         // leading whitespace, just that there is some.
  287.                         if ($i !== 0{
  288.                             if ($tokens[$stackPtr]['content'!== $pattern[$i]['value']{
  289.                                 $hasError = true;
  290.                             }
  291.                         }
  292.                     else {
  293.                         // Check to see if this important token is the same as the
  294.                         // previous important token in the pattern. If it is not,
  295.                         // then the pattern cannot be for this piece of code.
  296.                         $prev $phpcsFile->findPrevious(
  297.                             $ignoreTokens,
  298.                             $stackPtr,
  299.                             null,
  300.                             true
  301.                         );
  302.  
  303.                         if ($prev === false
  304.                             || $tokens[$prev]['code'!== $pattern[$i]['token']
  305.                         {
  306.                             return false;
  307.                         }
  308.  
  309.                         // If we skipped past some whitespace tokens, then add them
  310.                         // to the found string.
  311.                         $tokenContent $phpcsFile->getTokensAsString(
  312.                             ($prev + 1),
  313.                             ($stackPtr $prev - 1)
  314.                         );
  315.  
  316.                         $found $tokens[$prev]['content'].$tokenContent.$found;
  317.  
  318.                         if (isset($pattern[($i - 1)]=== true
  319.                             && $pattern[($i - 1)]['type'=== 'skip'
  320.                         {
  321.                             $stackPtr $prev;
  322.                         else {
  323.                             $stackPtr ($prev - 1);
  324.                         }
  325.                     }//end if
  326.                 else if ($pattern[$i]['type'=== 'skip'{
  327.                     // Skip to next piece of relevant code.
  328.                     if ($pattern[$i]['to'=== 'parenthesis_closer'{
  329.                         $to 'parenthesis_opener';
  330.                     else {
  331.                         $to 'scope_opener';
  332.                     }
  333.  
  334.                     // Find the previous opener.
  335.                     $next $phpcsFile->findPrevious(
  336.                         $ignoreTokens,
  337.                         $stackPtr,
  338.                         null,
  339.                         true
  340.                     );
  341.  
  342.                     if ($next === false || isset($tokens[$next][$to]=== false{
  343.                         // If there was not opener, then we must be
  344.                         // using the wrong pattern.
  345.                         return false;
  346.                     }
  347.  
  348.                     if ($to === 'parenthesis_opener'{
  349.                         $found '{'.$found;
  350.                     else {
  351.                         $found '('.$found;
  352.                     }
  353.  
  354.                     $found '...'.$found;
  355.  
  356.                     // Skip to the opening token.
  357.                     $stackPtr ($tokens[$next][$to- 1);
  358.                 else if ($pattern[$i]['type'=== 'string'{
  359.                     $found 'abc';
  360.                 else if ($pattern[$i]['type'=== 'newline'{
  361.                     if ($this->ignoreComments === true
  362.                         && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]=== true
  363.                     {
  364.                         $startComment $phpcsFile->findPrevious(
  365.                             Tokens::$commentTokens,
  366.                             ($stackPtr - 1),
  367.                             null,
  368.                             true
  369.                         );
  370.  
  371.                         if ($tokens[$startComment]['line'!== $tokens[($startComment + 1)]['line']{
  372.                             $startComment++;
  373.                         }
  374.  
  375.                         $tokenContent $phpcsFile->getTokensAsString(
  376.                             $startComment,
  377.                             ($stackPtr $startComment + 1)
  378.                         );
  379.  
  380.                         $found    $tokenContent.$found;
  381.                         $stackPtr ($startComment - 1);
  382.                     }
  383.  
  384.                     if ($tokens[$stackPtr]['code'=== T_WHITESPACE{
  385.                         if ($tokens[$stackPtr]['content'!== $phpcsFile->eolChar{
  386.                             $found $tokens[$stackPtr]['content'].$found;
  387.  
  388.                             // This may just be an indent that comes after a newline
  389.                             // so check the token before to make sure. If it is a newline, we
  390.                             // can ignore the error here.
  391.                             if (($tokens[($stackPtr - 1)]['content'!== $phpcsFile->eolChar)
  392.                                 && ($this->ignoreComments === true
  393.                                 && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]=== false)
  394.                             {
  395.                                 $hasError = true;
  396.                             else {
  397.                                 $stackPtr--;
  398.                             }
  399.                         else {
  400.                             $found 'EOL'.$found;
  401.                         }
  402.                     else {
  403.                         $found    $tokens[$stackPtr]['content'].$found;
  404.                         $hasError = true;
  405.                     }//end if
  406.  
  407.                     if ($hasError === false && $pattern[($i - 1)]['type'!== 'newline'{
  408.                         // Make sure they only have 1 newline.
  409.                         $prev $phpcsFile->findPrevious($ignoreTokens($stackPtr - 1)nulltrue);
  410.                         if ($prev !== false && $tokens[$prev]['line'!== $tokens[$stackPtr]['line']{
  411.                             $hasError = true;
  412.                         }
  413.                     }
  414.                 }//end if
  415.             }//end for
  416.         }//end if
  417.  
  418.         $stackPtr          $origStackPtr;
  419.         $lastAddedStackPtr = null;
  420.         $patternLen        count($pattern);
  421.  
  422.         for ($i $patternInfo['listen_pos']$i $patternLen$i++{
  423.             if (isset($tokens[$stackPtr]=== false{
  424.                 break;
  425.             }
  426.  
  427.             if ($pattern[$i]['type'=== 'token'{
  428.                 if ($pattern[$i]['token'=== T_WHITESPACE{
  429.                     if ($this->ignoreComments === true{
  430.                         // If we are ignoring comments, check to see if this current
  431.                         // token is a comment. If so skip it.
  432.                         if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]=== true{
  433.                             continue;
  434.                         }
  435.  
  436.                         // If the next token is a comment, the we need to skip the
  437.                         // current token as we should allow a space before a
  438.                         // comment for readability.
  439.                         if (isset($tokens[($stackPtr + 1)]=== true
  440.                             && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]=== true
  441.                         {
  442.                             continue;
  443.                         }
  444.                     }
  445.  
  446.                     $tokenContent '';
  447.                     if ($tokens[$stackPtr]['code'=== T_WHITESPACE{
  448.                         if (isset($pattern[($i + 1)]=== false{
  449.                             // This is the last token in the pattern, so just compare
  450.                             // the next token of content.
  451.                             $tokenContent $tokens[$stackPtr]['content'];
  452.                         else {
  453.                             // Get all the whitespace to the next token.
  454.                             $next $phpcsFile->findNext(
  455.                                 Tokens::$emptyTokens,
  456.                                 $stackPtr,
  457.                                 null,
  458.                                 true
  459.                             );
  460.  
  461.                             $tokenContent $phpcsFile->getTokensAsString(
  462.                                 $stackPtr,
  463.                                 ($next $stackPtr)
  464.                             );
  465.  
  466.                             $lastAddedStackPtr $stackPtr;
  467.                             $stackPtr          $next;
  468.                         }//end if
  469.  
  470.                         if ($stackPtr !== $lastAddedStackPtr{
  471.                             $found .= $tokenContent;
  472.                         }
  473.                     else {
  474.                         if ($stackPtr !== $lastAddedStackPtr{
  475.                             $found            .= $tokens[$stackPtr]['content'];
  476.                             $lastAddedStackPtr $stackPtr;
  477.                         }
  478.                     }//end if
  479.  
  480.                     if (isset($pattern[($i + 1)]=== true
  481.                         && $pattern[($i + 1)]['type'=== 'skip'
  482.                     {
  483.                         // The next token is a skip token, so we just need to make
  484.                         // sure the whitespace we found has *at least* the
  485.                         // whitespace required.
  486.                         if (strpos($tokenContent$pattern[$i]['value']!== 0{
  487.                             $hasError = true;
  488.                         }
  489.                     else {
  490.                         if ($tokenContent !== $pattern[$i]['value']{
  491.                             $hasError = true;
  492.                         }
  493.                     }
  494.                 else {
  495.                     // Check to see if this important token is the same as the
  496.                     // next important token in the pattern. If it is not, then
  497.                     // the pattern cannot be for this piece of code.
  498.                     $next $phpcsFile->findNext(
  499.                         $ignoreTokens,
  500.                         $stackPtr,
  501.                         null,
  502.                         true
  503.                     );
  504.  
  505.                     if ($next === false
  506.                         || $tokens[$next]['code'!== $pattern[$i]['token']
  507.                     {
  508.                         // The next important token did not match the pattern.
  509.                         return false;
  510.                     }
  511.  
  512.                     if ($lastAddedStackPtr !== null{
  513.                         if (($tokens[$next]['code'=== T_OPEN_CURLY_BRACKET
  514.                             || $tokens[$next]['code'=== T_CLOSE_CURLY_BRACKET)
  515.                             && isset($tokens[$next]['scope_condition']=== true
  516.                             && $tokens[$next]['scope_condition'$lastAddedStackPtr
  517.                         {
  518.                             // This is a brace, but the owner of it is after the current
  519.                             // token, which means it does not belong to any token in
  520.                             // our pattern. This means the pattern is not for us.
  521.                             return false;
  522.                         }
  523.  
  524.                         if (($tokens[$next]['code'=== T_OPEN_PARENTHESIS
  525.                             || $tokens[$next]['code'=== T_CLOSE_PARENTHESIS)
  526.                             && isset($tokens[$next]['parenthesis_owner']=== true
  527.                             && $tokens[$next]['parenthesis_owner'$lastAddedStackPtr
  528.                         {
  529.                             // This is a bracket, but the owner of it is after the current
  530.                             // token, which means it does not belong to any token in
  531.                             // our pattern. This means the pattern is not for us.
  532.                             return false;
  533.                         }
  534.                     }//end if
  535.  
  536.                     // If we skipped past some whitespace tokens, then add them
  537.                     // to the found string.
  538.                     if (($next $stackPtr> 0{
  539.                         $hasComment = false;
  540.                         for ($j $stackPtr$j $next$j++{
  541.                             $found .= $tokens[$j]['content'];
  542.                             if (isset(Tokens::$commentTokens[$tokens[$j]['code']]=== true{
  543.                                 $hasComment = true;
  544.                             }
  545.                         }
  546.  
  547.                         // If we are not ignoring comments, this additional
  548.                         // whitespace or comment is not allowed. If we are
  549.                         // ignoring comments, there needs to be at least one
  550.                         // comment for this to be allowed.
  551.                         if ($this->ignoreComments === false
  552.                             || ($this->ignoreComments === true
  553.                             && $hasComment === false)
  554.                         {
  555.                             $hasError = true;
  556.                         }
  557.  
  558.                         // Even when ignoring comments, we are not allowed to include
  559.                         // newlines without the pattern specifying them, so
  560.                         // everything should be on the same line.
  561.                         if ($tokens[$next]['line'!== $tokens[$stackPtr]['line']{
  562.                             $hasError = true;
  563.                         }
  564.                     }//end if
  565.  
  566.                     if ($next !== $lastAddedStackPtr{
  567.                         $found            .= $tokens[$next]['content'];
  568.                         $lastAddedStackPtr $next;
  569.                     }
  570.  
  571.                     if (isset($pattern[($i + 1)]=== true
  572.                         && $pattern[($i + 1)]['type'=== 'skip'
  573.                     {
  574.                         $stackPtr $next;
  575.                     else {
  576.                         $stackPtr ($next + 1);
  577.                     }
  578.                 }//end if
  579.             else if ($pattern[$i]['type'=== 'skip'{
  580.                 if ($pattern[$i]['to'=== 'unknown'{
  581.                     $next $phpcsFile->findNext(
  582.                         $pattern[($i + 1)]['token'],
  583.                         $stackPtr
  584.                     );
  585.  
  586.                     if ($next === false{
  587.                         // Couldn't find the next token, so we must
  588.                         // be using the wrong pattern.
  589.                         return false;
  590.                     }
  591.  
  592.                     $found   .= '...';
  593.                     $stackPtr $next;
  594.                 else {
  595.                     // Find the previous opener.
  596.                     $next $phpcsFile->findPrevious(
  597.                         Tokens::$blockOpeners,
  598.                         $stackPtr
  599.                     );
  600.  
  601.                     if ($next === false
  602.                         || isset($tokens[$next][$pattern[$i]['to']]=== false
  603.                     {
  604.                         // If there was not opener, then we must
  605.                         // be using the wrong pattern.
  606.                         return false;
  607.                     }
  608.  
  609.                     $found .= '...';
  610.                     if ($pattern[$i]['to'=== 'parenthesis_closer'{
  611.                         $found .= ')';
  612.                     else {
  613.                         $found .= '}';
  614.                     }
  615.  
  616.                     // Skip to the closing token.
  617.                     $stackPtr ($tokens[$next][$pattern[$i]['to']] + 1);
  618.                 }//end if
  619.             else if ($pattern[$i]['type'=== 'string'{
  620.                 if ($tokens[$stackPtr]['code'!== T_STRING{
  621.                     $hasError = true;
  622.                 }
  623.  
  624.                 if ($stackPtr !== $lastAddedStackPtr{
  625.                     $found            .= 'abc';
  626.                     $lastAddedStackPtr $stackPtr;
  627.                 }
  628.  
  629.                 $stackPtr++;
  630.             else if ($pattern[$i]['type'=== 'newline'{
  631.                 // Find the next token that contains a newline character.
  632.                 $newline = 0;
  633.                 for ($j $stackPtr$j $phpcsFile->numTokens; $j++{
  634.                     if (strpos($tokens[$j]['content']$phpcsFile->eolChar!== false{
  635.                         $newline $j;
  636.                         break;
  637.                     }
  638.                 }
  639.  
  640.                 if ($newline === 0{
  641.                     // We didn't find a newline character in the rest of the file.
  642.                     $next     ($phpcsFile->numTokens - 1);
  643.                     $hasError = true;
  644.                 else {
  645.                     if ($this->ignoreComments === false{
  646.                         // The newline character cannot be part of a comment.
  647.                         if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]=== true{
  648.                             $hasError = true;
  649.                         }
  650.                     }
  651.  
  652.                     if ($newline === $stackPtr{
  653.                         $next ($stackPtr + 1);
  654.                     else {
  655.                         // Check that there were no significant tokens that we
  656.                         // skipped over to find our newline character.
  657.                         $next $phpcsFile->findNext(
  658.                             $ignoreTokens,
  659.                             $stackPtr,
  660.                             null,
  661.                             true
  662.                         );
  663.  
  664.                         if ($next $newline{
  665.                             // We skipped a non-ignored token.
  666.                             $hasError = true;
  667.                         else {
  668.                             $next ($newline + 1);
  669.                         }
  670.                     }
  671.                 }//end if
  672.  
  673.                 if ($stackPtr !== $lastAddedStackPtr{
  674.                     $found .= $phpcsFile->getTokensAsString(
  675.                         $stackPtr,
  676.                         ($next $stackPtr)
  677.                     );
  678.  
  679.                     $diff ($next $stackPtr);
  680.                     $lastAddedStackPtr ($next - 1);
  681.                 }
  682.  
  683.                 $stackPtr $next;
  684.             }//end if
  685.         }//end for
  686.  
  687.         if ($hasError === true{
  688.             $error $this->prepareError($found$patternCode);
  689.             $errors[$origStackPtr$error;
  690.         }
  691.  
  692.         return $errors;
  693.  
  694.     }//end processPattern()
  695.  
  696.  
  697.     /**
  698.      * Prepares an error for the specified patternCode.
  699.      *
  700.      * @param string $found       The actual found string in the code.
  701.      * @param string $patternCode The expected pattern code.
  702.      *
  703.      * @return string The error message.
  704.      */
  705.     protected function prepareError($found$patternCode)
  706.     {
  707.         $found    str_replace("\r\n"'\n'$found);
  708.         $found    str_replace("\n"'\n'$found);
  709.         $found    str_replace("\r"'\n'$found);
  710.         $found    str_replace("\t"'\t'$found);
  711.         $found    str_replace('EOL''\n'$found);
  712.         $expected str_replace('EOL''\n'$patternCode);
  713.  
  714.         $error = "Expected \"$expected\"; found \"$found\"";
  715.  
  716.         return $error;
  717.  
  718.     }//end prepareError()
  719.  
  720.  
  721.     /**
  722.      * Returns the patterns that should be checked.
  723.      *
  724.      * @return string[] 
  725.      */
  726.     abstract protected function getPatterns();
  727.  
  728.  
  729.     /**
  730.      * Registers any supplementary tokens that this test might wish to process.
  731.      *
  732.      * A sniff may wish to register supplementary tests when it wishes to group
  733.      * an arbitrary validation that cannot be performed using a pattern, with
  734.      * other pattern tests.
  735.      *
  736.      * @return int[] 
  737.      * @see    processSupplementary()
  738.      */
  739.     protected function registerSupplementary()
  740.     {
  741.         return array();
  742.  
  743.     }//end registerSupplementary()
  744.  
  745.  
  746.      /**
  747.       * Processes any tokens registered with registerSupplementary().
  748.       *
  749.       * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where to
  750.       *                                         process the skip.
  751.       * @param int                  $stackPtr  The position in the tokens stack to
  752.       *                                         process.
  753.       *
  754.       * @return void 
  755.       * @see    registerSupplementary()
  756.       */
  757.     protected function processSupplementary(File $phpcsFile$stackPtr)
  758.     {
  759.  
  760.     }//end processSupplementary()
  761.  
  762.  
  763.     /**
  764.      * Parses a pattern string into an array of pattern steps.
  765.      *
  766.      * @param string $pattern The pattern to parse.
  767.      *
  768.      * @return array The parsed pattern array.
  769.      * @see    createSkipPattern()
  770.      * @see    createTokenPattern()
  771.      */
  772.     private function parse($pattern)
  773.     {
  774.         $patterns   = array();
  775.         $length     strlen($pattern);
  776.         $lastToken  = 0;
  777.         $firstToken = 0;
  778.  
  779.         for ($i = 0; $i $length$i++{
  780.             $specialPattern = false;
  781.             $isLastChar     ($i === ($length - 1));
  782.             $oldFirstToken  $firstToken;
  783.  
  784.             if (substr($pattern$i3=== '...'{
  785.                 // It's a skip pattern. The skip pattern requires the
  786.                 // content of the token in the "from" position and the token
  787.                 // to skip to.
  788.                 $specialPattern $this->createSkipPattern($pattern($i - 1));
  789.                 $lastToken      ($i $firstToken);
  790.                 $firstToken     ($i + 3);
  791.                 $i ($i + 2);
  792.  
  793.                 if ($specialPattern['to'!== 'unknown'{
  794.                     $firstToken++;
  795.                 }
  796.             else if (substr($pattern$i3=== 'abc'{
  797.                 $specialPattern = array('type' => 'string');
  798.                 $lastToken      ($i $firstToken);
  799.                 $firstToken     ($i + 3);
  800.                 $i ($i + 2);
  801.             else if (substr($pattern$i3=== 'EOL'{
  802.                 $specialPattern = array('type' => 'newline');
  803.                 $lastToken      ($i $firstToken);
  804.                 $firstToken     ($i + 3);
  805.                 $i ($i + 2);
  806.             }//end if
  807.  
  808.             if ($specialPattern !== false || $isLastChar === true{
  809.                 // If we are at the end of the string, don't worry about a limit.
  810.                 if ($isLastChar === true{
  811.                     // Get the string from the end of the last skip pattern, if any,
  812.                     // to the end of the pattern string.
  813.                     $str substr($pattern$oldFirstToken);
  814.                 else {
  815.                     // Get the string from the end of the last special pattern,
  816.                     // if any, to the start of this special pattern.
  817.                     if ($lastToken === 0{
  818.                         // Note that if the last special token was zero characters ago,
  819.                         // there will be nothing to process so we can skip this bit.
  820.                         // This happens if you have something like: EOL... in your pattern.
  821.                         $str '';
  822.                     else {
  823.                         $str substr($pattern$oldFirstToken$lastToken);
  824.                     }
  825.                 }
  826.  
  827.                 if ($str !== ''{
  828.                     $tokenPatterns $this->createTokenPattern($str);
  829.                     foreach ($tokenPatterns as $tokenPattern{
  830.                         $patterns[$tokenPattern;
  831.                     }
  832.                 }
  833.  
  834.                 // Make sure we don't skip the last token.
  835.                 if ($isLastChar === false && $i === ($length - 1)) {
  836.                     $i--;
  837.                 }
  838.             }//end if
  839.  
  840.             // Add the skip pattern *after* we have processed
  841.             // all the tokens from the end of the last skip pattern
  842.             // to the start of this skip pattern.
  843.             if ($specialPattern !== false{
  844.                 $patterns[$specialPattern;
  845.             }
  846.         }//end for
  847.  
  848.         return $patterns;
  849.  
  850.     }//end parse()
  851.  
  852.  
  853.     /**
  854.      * Creates a skip pattern.
  855.      *
  856.      * @param string $pattern The pattern being parsed.
  857.      * @param string $from    The token content that the skip pattern starts from.
  858.      *
  859.      * @return array The pattern step.
  860.      * @see    createTokenPattern()
  861.      * @see    parse()
  862.      */
  863.     private function createSkipPattern($pattern$from)
  864.     {
  865.         $skip = array('type' => 'skip');
  866.  
  867.         $nestedParenthesis = 0;
  868.         $nestedBraces      = 0;
  869.         for ($start $from$start >= 0; $start--{
  870.             switch ($pattern[$start]{
  871.             case '(':
  872.                 if ($nestedParenthesis === 0{
  873.                     $skip['to''parenthesis_closer';
  874.                 }
  875.  
  876.                 $nestedParenthesis--;
  877.                 break;
  878.             case '{':
  879.                 if ($nestedBraces === 0{
  880.                     $skip['to''scope_closer';
  881.                 }
  882.  
  883.                 $nestedBraces--;
  884.                 break;
  885.             case '}':
  886.                 $nestedBraces++;
  887.                 break;
  888.             case ')':
  889.                 $nestedParenthesis++;
  890.                 break;
  891.             }//end switch
  892.  
  893.             if (isset($skip['to']=== true{
  894.                 break;
  895.             }
  896.         }//end for
  897.  
  898.         if (isset($skip['to']=== false{
  899.             $skip['to''unknown';
  900.         }
  901.  
  902.         return $skip;
  903.  
  904.     }//end createSkipPattern()
  905.  
  906.  
  907.     /**
  908.      * Creates a token pattern.
  909.      *
  910.      * @param string $str The tokens string that the pattern should match.
  911.      *
  912.      * @return array The pattern step.
  913.      * @see    createSkipPattern()
  914.      * @see    parse()
  915.      */
  916.     private function createTokenPattern($str)
  917.     {
  918.         // Don't add a space after the closing php tag as it will add a new
  919.         // whitespace token.
  920.         $tokenizer = new PHP('<?php '.$str.'?>'null);
  921.  
  922.         // Remove the <?php tag from the front and the end php tag from the back.
  923.         $tokens $tokenizer->getTokens();
  924.         $tokens array_slice($tokens1(count($tokens- 2));
  925.  
  926.         $patterns = array();
  927.         foreach ($tokens as $patternInfo{
  928.             $patterns[= array(
  929.                            'type'  => 'token',
  930.                            'token' => $patternInfo['code'],
  931.                            'value' => $patternInfo['content'],
  932.                           );
  933.         }
  934.  
  935.         return $patterns;
  936.  
  937.     }//end createTokenPattern()
  938.  
  939.  
  940. }//end class

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