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

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