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

Source for file Ruleset.php

Documentation is available at Ruleset.php

  1. <?php
  2. /**
  3.  * Stores the rules used to check and fix files.
  4.  *
  5.  * A ruleset object directly maps to a ruleset XML file.
  6.  *
  7.  * @author    Greg Sherwood <gsherwood@squiz.net>
  8.  * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  9.  * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  10.  */
  11.  
  12. namespace PHP_CodeSniffer;
  13.  
  14. use PHP_CodeSniffer\Util;
  15. use PHP_CodeSniffer\Exceptions\RuntimeException;
  16.  
  17. class Ruleset
  18. {
  19.  
  20.     /**
  21.      * The name of the coding standard being used.
  22.      *
  23.      * If a top-level standard includes other standards, or sniffs
  24.      * from other standards, only the name of the top-level standard
  25.      * will be stored in here.
  26.      *
  27.      * If multiple top-level standards are being loaded into
  28.      * a single ruleset object, this will store a comma separated list
  29.      * of the top-level standard names.
  30.      *
  31.      * @var string 
  32.      */
  33.     public $name '';
  34.  
  35.     /**
  36.      * A list of file paths for the ruleset files being used.
  37.      *
  38.      * @var string[] 
  39.      */
  40.     public $paths = array();
  41.  
  42.     /**
  43.      * A list of regular expressions used to ignore specific sniffs for files and folders.
  44.      *
  45.      * Is also used to set global exclude patterns.
  46.      * The key is the regular expression and the value is the type
  47.      * of ignore pattern (absolute or relative).
  48.      *
  49.      * @var array<string, string>
  50.      */
  51.     public $ignorePatterns = array();
  52.  
  53.     /**
  54.      * A list of regular expressions used to include specific sniffs for files and folders.
  55.      *
  56.      * The key is the sniff code and the value is an array with
  57.      * the key being a regular expression and the value is the type
  58.      * of ignore pattern (absolute or relative).
  59.      *
  60.      * @var array<string, array<string, string>>
  61.      */
  62.     public $includePatterns = array();
  63.  
  64.     /**
  65.      * An array of sniff objects that are being used to check files.
  66.      *
  67.      * The key is the fully qualified name of the sniff class
  68.      * and the value is the sniff object.
  69.      *
  70.      * @var array<string, \PHP_CodeSniffer\Sniff>
  71.      */
  72.     public $sniffs = array();
  73.  
  74.     /**
  75.      * A mapping of sniff codes to fully qualified class names.
  76.      *
  77.      * The key is the sniff code and the value
  78.      * is the fully qualified name of the sniff class.
  79.      *
  80.      * @var array<string, string>
  81.      */
  82.     public $sniffCodes = array();
  83.  
  84.     /**
  85.      * An array of token types and the sniffs that are listening for them.
  86.      *
  87.      * The key is the token name being listened for and the value
  88.      * is the sniff object.
  89.      *
  90.      * @var array<int, \PHP_CodeSniffer\Sniff>
  91.      */
  92.     public $tokenListeners = array();
  93.  
  94.     /**
  95.      * An array of rules from the ruleset.xml file.
  96.      *
  97.      * It may be empty, indicating that the ruleset does not override
  98.      * any of the default sniff settings.
  99.      *
  100.      * @var array<string, mixed>
  101.      */
  102.     public $ruleset = array();
  103.  
  104.     /**
  105.      * The directories that the processed rulesets are in.
  106.      *
  107.      * @var string[] 
  108.      */
  109.     protected $rulesetDirs = array();
  110.  
  111.     /**
  112.      * The config data for the run.
  113.      *
  114.      * @var \PHP_CodeSniffer\Config 
  115.      */
  116.     private $config = null;
  117.  
  118.  
  119.     /**
  120.      * Initialise the ruleset that the run will use.
  121.      *
  122.      * @param \PHP_CodeSniffer\Config $config The config data for the run.
  123.      *
  124.      * @return void 
  125.      */
  126.     public function __construct(Config $config)
  127.     {
  128.         // Ignore sniff restrictions if caching is on.
  129.         $restrictions = array();
  130.         $exclusions   = array();
  131.         if ($config->cache === false{
  132.             $restrictions $config->sniffs;
  133.             $exclusions   $config->exclude;
  134.         }
  135.  
  136.         $this->config $config;
  137.         $sniffs       = array();
  138.  
  139.         $standardPaths = array();
  140.         foreach ($config->standards as $standard{
  141.             $installed = Util\Standards::getInstalledStandardPath($standard);
  142.             if ($installed === null{
  143.                 $standard = Util\Common::realpath($standard);
  144.                 if (is_dir($standard=== true
  145.                     && is_file(Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml')) === true
  146.                 {
  147.                     $standard = Util\Common::realpath($standard.DIRECTORY_SEPARATOR.'ruleset.xml');
  148.                 }
  149.             else {
  150.                 $standard $installed;
  151.             }
  152.  
  153.             $standardPaths[$standard;
  154.         }
  155.  
  156.         if (defined('PHP_CODESNIFFER_IN_TESTS'=== true && empty($restrictions=== false{
  157.             // Should be one standard and one sniff being tested at a time.
  158.             $sniffs $this->expandRulesetReference($restrictions[0]dirname($standardPaths[0]));
  159.         else {
  160.             foreach ($standardPaths as $standard{
  161.                 $ruleset simplexml_load_string(file_get_contents($standard));
  162.                 if ($ruleset !== false{
  163.                     $standardName = (string) $ruleset['name'];
  164.                     if ($this->name !== ''{
  165.                         $this->name .= ', ';
  166.                     }
  167.  
  168.                     $this->name   .= $standardName;
  169.                     $this->paths[$standard;
  170.  
  171.                     // Allow autoloading of custom files inside this standard.
  172.                     Autoload::addSearchPath(dirname(dirname($standard)));
  173.                 }
  174.  
  175.                 if (PHP_CODESNIFFER_VERBOSITY === 1{
  176.                     echo "Registering sniffs in the $standardName standard... ";
  177.                     if (count($config->standards> 1 || PHP_CODESNIFFER_VERBOSITY > 2{
  178.                         echo PHP_EOL;
  179.                     }
  180.                 }
  181.  
  182.                 $sniffs array_merge($sniffs$this->processRuleset($standard));
  183.             }//end foreach
  184.         }//end if
  185.  
  186.         $sniffRestrictions = array();
  187.         foreach ($restrictions as $sniffCode{
  188.             $parts     explode('.'strtolower($sniffCode));
  189.             $sniffName 'php_codesniffer\standards\\'.$parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff';
  190.             $sniffRestrictions[$sniffName= true;
  191.         }
  192.  
  193.         $sniffExclusions = array();
  194.         foreach ($exclusions as $sniffCode{
  195.             $parts     explode('.'strtolower($sniffCode));
  196.             $sniffName 'php_codesniffer\standards\\'.$parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff';
  197.             $sniffExclusions[$sniffName= true;
  198.         }
  199.  
  200.         $this->registerSniffs($sniffs$sniffRestrictions$sniffExclusions);
  201.         $this->populateTokenListeners();
  202.  
  203.         $numSniffs count($this->sniffs);
  204.         if (PHP_CODESNIFFER_VERBOSITY === 1{
  205.             echo "DONE ($numSniffs sniffs registered)".PHP_EOL;
  206.         }
  207.  
  208.         if ($numSniffs === 0{
  209.             throw new RuntimeException('No sniffs were registered');
  210.         }
  211.  
  212.     }//end __construct()
  213.  
  214.  
  215.     /**
  216.      * Prints a report showing the sniffs contained in a standard.
  217.      *
  218.      * @return void 
  219.      */
  220.     public function explain()
  221.     {
  222.         $sniffs array_keys($this->sniffCodes);
  223.         sort($sniffs);
  224.  
  225.         ob_start();
  226.  
  227.         $lastStandard = null;
  228.         $lastCount    '';
  229.         $sniffCount   count($sniffs);
  230.  
  231.         // Add a dummy entry to the end so we loop
  232.         // one last time and clear the output buffer.
  233.         $sniffs['';
  234.  
  235.         echo PHP_EOL."The $this->name standard contains $sniffCount sniffs".PHP_EOL;
  236.  
  237.         ob_start();
  238.  
  239.         foreach ($sniffs as $i => $sniff{
  240.             if ($i === $sniffCount{
  241.                 $currentStandard = null;
  242.             else {
  243.                 $currentStandard substr($sniff0strpos($sniff'.'));
  244.                 if ($lastStandard === null{
  245.                     $lastStandard $currentStandard;
  246.                 }
  247.             }
  248.  
  249.             if ($currentStandard !== $lastStandard{
  250.                 $sniffList ob_get_contents();
  251.                 ob_end_clean();
  252.  
  253.                 echo PHP_EOL.$lastStandard.' ('.$lastCount.' sniff';
  254.                 if ($lastCount > 1{
  255.                     echo 's';
  256.                 }
  257.  
  258.                 echo ')'.PHP_EOL;
  259.                 echo str_repeat('-'(strlen($lastStandard.$lastCount+ 10));
  260.                 echo PHP_EOL;
  261.                 echo $sniffList;
  262.  
  263.                 $lastStandard $currentStandard;
  264.                 $lastCount    = 0;
  265.  
  266.                 if ($currentStandard === null{
  267.                     break;
  268.                 }
  269.  
  270.                 ob_start();
  271.             }//end if
  272.  
  273.             echo '  '.$sniff.PHP_EOL;
  274.             $lastCount++;
  275.         }//end foreach
  276.  
  277.     }//end explain()
  278.  
  279.  
  280.     /**
  281.      * Processes a single ruleset and returns a list of the sniffs it represents.
  282.      *
  283.      * Rules founds within the ruleset are processed immediately, but sniff classes
  284.      * are not registered by this method.
  285.      *
  286.      * @param string $rulesetPath The path to a ruleset XML file.
  287.      * @param int    $depth       How many nested processing steps we are in. This
  288.      *                             is only used for debug output.
  289.      *
  290.      * @return string[] 
  291.      * @throws RuntimeException If the ruleset path is invalid.
  292.      */
  293.     public function processRuleset($rulesetPath$depth=0)
  294.     {
  295.         $rulesetPath = Util\Common::realpath($rulesetPath);
  296.         if (PHP_CODESNIFFER_VERBOSITY > 1{
  297.             echo str_repeat("\t"$depth);
  298.             echo 'Processing ruleset '.Util\Common::stripBasepath($rulesetPath$this->config->basepath).PHP_EOL;
  299.         }
  300.  
  301.         $ruleset simplexml_load_string(file_get_contents($rulesetPath));
  302.         if ($ruleset === false{
  303.             throw new RuntimeException("Ruleset $rulesetPath is not valid");
  304.         }
  305.  
  306.         $ownSniffs      = array();
  307.         $includedSniffs = array();
  308.         $excludedSniffs = array();
  309.  
  310.         $rulesetDir          dirname($rulesetPath);
  311.         $this->rulesetDirs[$rulesetDir;
  312.  
  313.         $sniffDir $rulesetDir.DIRECTORY_SEPARATOR.'Sniffs';
  314.         if (is_dir($sniffDir=== true{
  315.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  316.                 echo str_repeat("\t"$depth);
  317.                 echo "\tAdding sniff files from ".Util\Common::stripBasepath($sniffDir$this->config->basepath).' directory'.PHP_EOL;
  318.             }
  319.  
  320.             $ownSniffs $this->expandSniffDirectory($sniffDir$depth);
  321.         }
  322.  
  323.         // Process custom sniff config settings.
  324.         foreach ($ruleset->{'config'as $config{
  325.             if ($this->shouldProcessElement($config=== false{
  326.                 continue;
  327.             }
  328.  
  329.             Config::setConfigData((string) $config['name'](string) $config['value']true);
  330.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  331.                 echo str_repeat("\t"$depth);
  332.                 echo "\t=> set config value ".(string) $config['name'].': '.(string) $config['value'].PHP_EOL;
  333.             }
  334.         }
  335.  
  336.         foreach ($ruleset->rule as $rule{
  337.             if (isset($rule['ref']=== false
  338.                 || $this->shouldProcessElement($rule=== false
  339.             {
  340.                 continue;
  341.             }
  342.  
  343.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  344.                 echo str_repeat("\t"$depth);
  345.                 echo "\tProcessing rule \"".$rule['ref'].'"'.PHP_EOL;
  346.             }
  347.  
  348.             $expandedSniffs $this->expandRulesetReference($rule['ref']$rulesetDir$depth);
  349.             $newSniffs      array_diff($expandedSniffs$includedSniffs);
  350.             $includedSniffs array_merge($includedSniffs$expandedSniffs);
  351.  
  352.             $parts explode('.'$rule['ref']);
  353.             if (count($parts=== 4{
  354.                 $sniffCode $parts[0].'.'.$parts[1].'.'.$parts[2];
  355.                 if (isset($this->ruleset[$sniffCode]['severity']=== true
  356.                     && $this->ruleset[$sniffCode]['severity'=== 0
  357.                 {
  358.                     // This sniff code has already been turned off, but now
  359.                     // it is being explicitly included again, so turn it back on.
  360.                     $this->ruleset[(string) $rule['ref']]['severity'= 5;
  361.                     if (PHP_CODESNIFFER_VERBOSITY > 1{
  362.                         echo str_repeat("\t"$depth);
  363.                         echo "\t\t* disabling sniff exclusion for specific message code *".PHP_EOL;
  364.                         echo str_repeat("\t"$depth);
  365.                         echo "\t\t=> severity set to 5".PHP_EOL;
  366.                     }
  367.                 else if (empty($newSniffs=== false{
  368.                     // Including a sniff that hasn't been included higher up, but
  369.                     // only including a single message from it. So turn off all messages in
  370.                     // the sniff, except this one.
  371.                     $this->ruleset[$sniffCode]['severity']            = 0;
  372.                     $this->ruleset[(string) $rule['ref']]['severity'= 5;
  373.                     if (PHP_CODESNIFFER_VERBOSITY > 1{
  374.                         echo str_repeat("\t"$depth);
  375.                         echo "\t\tExcluding sniff \"".$sniffCode.'" except for "'.$parts[3].'"'.PHP_EOL;
  376.                     }
  377.                 }//end if
  378.             }//end if
  379.  
  380.             if (isset($rule->exclude=== true{
  381.                 foreach ($rule->exclude as $exclude{
  382.                     if (isset($exclude['name']=== false{
  383.                         if (PHP_CODESNIFFER_VERBOSITY > 1{
  384.                             echo str_repeat("\t"$depth);
  385.                             echo "\t\t* ignoring empty exclude rule *".PHP_EOL;
  386.                             echo "\t\t\t=> ".$exclude->asXML().PHP_EOL;
  387.                         }
  388.  
  389.                         continue;
  390.                     }
  391.  
  392.                     if ($this->shouldProcessElement($exclude=== false{
  393.                         continue;
  394.                     }
  395.  
  396.                     if (PHP_CODESNIFFER_VERBOSITY > 1{
  397.                         echo str_repeat("\t"$depth);
  398.                         echo "\t\tExcluding rule \"".$exclude['name'].'"'.PHP_EOL;
  399.                     }
  400.  
  401.                     // Check if a single code is being excluded, which is a shortcut
  402.                     // for setting the severity of the message to 0.
  403.                     $parts explode('.'$exclude['name']);
  404.                     if (count($parts=== 4{
  405.                         $this->ruleset[(string) $exclude['name']]['severity'= 0;
  406.                         if (PHP_CODESNIFFER_VERBOSITY > 1{
  407.                             echo str_repeat("\t"$depth);
  408.                             echo "\t\t=> severity set to 0".PHP_EOL;
  409.                         }
  410.                     else {
  411.                         $excludedSniffs array_merge(
  412.                             $excludedSniffs,
  413.                             $this->expandRulesetReference($exclude['name']$rulesetDir($depth + 1))
  414.                         );
  415.                     }
  416.                 }//end foreach
  417.             }//end if
  418.  
  419.             $this->processRule($rule$newSniffs$depth);
  420.         }//end foreach
  421.  
  422.         // Process custom command line arguments.
  423.         $cliArgs = array();
  424.         foreach ($ruleset->{'arg'as $arg{
  425.             if ($this->shouldProcessElement($arg=== false{
  426.                 continue;
  427.             }
  428.  
  429.             if (isset($arg['name']=== true{
  430.                 $argString '--'.(string) $arg['name'];
  431.                 if (isset($arg['value']=== true{
  432.                     $argString .= '='.(string) $arg['value'];
  433.                 }
  434.             else {
  435.                 $argString '-'.(string) $arg['value'];
  436.             }
  437.  
  438.             $cliArgs[$argString;
  439.  
  440.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  441.                 echo str_repeat("\t"$depth);
  442.                 echo "\t=> set command line value $argString".PHP_EOL;
  443.             }
  444.         }//end foreach
  445.  
  446.         // Set custom php ini values as CLI args.
  447.         foreach ($ruleset->{'ini'as $arg{
  448.             if ($this->shouldProcessElement($arg=== false{
  449.                 continue;
  450.             }
  451.  
  452.             if (isset($arg['name']=== false{
  453.                 continue;
  454.             }
  455.  
  456.             $name      = (string) $arg['name'];
  457.             $argString $name;
  458.             if (isset($arg['value']=== true{
  459.                 $value      = (string) $arg['value'];
  460.                 $argString .= "=$value";
  461.             else {
  462.                 $value 'true';
  463.             }
  464.  
  465.             $cliArgs['-d';
  466.             $cliArgs[$argString;
  467.  
  468.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  469.                 echo str_repeat("\t"$depth);
  470.                 echo "\t=> set PHP ini value $name to $value".PHP_EOL;
  471.             }
  472.         }//end foreach
  473.  
  474.         if (empty($this->config->files=== true{
  475.             // Process hard-coded file paths.
  476.             foreach ($ruleset->{'file'as $file{
  477.                 $file      = (string) $file;
  478.                 $cliArgs[$file;
  479.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  480.                     echo str_repeat("\t"$depth);
  481.                     echo "\t=> added \"$file\" to the file list".PHP_EOL;
  482.                 }
  483.             }
  484.         }
  485.  
  486.         if (empty($cliArgs=== false{
  487.             // Change the directory so all relative paths are worked
  488.             // out based on the location of the ruleset instead of
  489.             // the location of the user.
  490.             $inPhar = Util\Common::isPharFile($rulesetDir);
  491.             if ($inPhar === false{
  492.                 $currentDir getcwd();
  493.                 chdir($rulesetDir);
  494.             }
  495.  
  496.             $this->config->setCommandLineValues($cliArgs);
  497.  
  498.             if ($inPhar === false{
  499.                 chdir($currentDir);
  500.             }
  501.         }
  502.  
  503.         // Process custom ignore pattern rules.
  504.         foreach ($ruleset->{'exclude-pattern'as $pattern{
  505.             if ($this->shouldProcessElement($pattern=== false{
  506.                 continue;
  507.             }
  508.  
  509.             if (isset($pattern['type']=== false{
  510.                 $pattern['type''absolute';
  511.             }
  512.  
  513.             $this->ignorePatterns[(string) $pattern= (string) $pattern['type'];
  514.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  515.                 echo str_repeat("\t"$depth);
  516.                 echo "\t=> added global ".(string) $pattern['type'].' ignore pattern: '.(string) $pattern.PHP_EOL;
  517.             }
  518.         }
  519.  
  520.         $includedSniffs array_unique(array_merge($ownSniffs$includedSniffs));
  521.         $excludedSniffs array_unique($excludedSniffs);
  522.  
  523.         if (PHP_CODESNIFFER_VERBOSITY > 1{
  524.             $included count($includedSniffs);
  525.             $excluded count($excludedSniffs);
  526.             echo str_repeat("\t"$depth);
  527.             echo "=> Ruleset processing complete; included $included sniffs and excluded $excluded".PHP_EOL;
  528.         }
  529.  
  530.         // Merge our own sniff list with our externally included
  531.         // sniff list, but filter out any excluded sniffs.
  532.         $files = array();
  533.         foreach ($includedSniffs as $sniff{
  534.             if (in_array($sniff$excludedSniffs=== true{
  535.                 continue;
  536.             else {
  537.                 $files[= Util\Common::realpath($sniff);
  538.             }
  539.         }
  540.  
  541.         return $files;
  542.  
  543.     }//end processRuleset()
  544.  
  545.  
  546.     /**
  547.      * Expands a directory into a list of sniff files within.
  548.      *
  549.      * @param string $directory The path to a directory.
  550.      * @param int    $depth     How many nested processing steps we are in. This
  551.      *                           is only used for debug output.
  552.      *
  553.      * @return array 
  554.      */
  555.     private function expandSniffDirectory($directory$depth=0)
  556.     {
  557.         $sniffs = array();
  558.  
  559.         $rdi = new \RecursiveDirectoryIterator($directory\RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
  560.         $di  = new \RecursiveIteratorIterator($rdi0\RecursiveIteratorIterator::CATCH_GET_CHILD);
  561.  
  562.         $dirLen strlen($directory);
  563.  
  564.         foreach ($di as $file{
  565.             $filename $file->getFilename();
  566.  
  567.             // Skip hidden files.
  568.             if (substr($filename01=== '.'{
  569.                 continue;
  570.             }
  571.  
  572.             // We are only interested in PHP and sniff files.
  573.             $fileParts explode('.'$filename);
  574.             if (array_pop($fileParts!== 'php'{
  575.                 continue;
  576.             }
  577.  
  578.             $basename basename($filename'.php');
  579.             if (substr($basename-5!== 'Sniff'{
  580.                 continue;
  581.             }
  582.  
  583.             $path $file->getPathname();
  584.  
  585.             // Skip files in hidden directories within the Sniffs directory of this
  586.             // standard. We use the offset with strpos() to allow hidden directories
  587.             // before, valid example:
  588.             // /home/foo/.composer/vendor/squiz/custom_tool/MyStandard/Sniffs/...
  589.             if (strpos($pathDIRECTORY_SEPARATOR.'.'$dirLen!== false{
  590.                 continue;
  591.             }
  592.  
  593.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  594.                 echo str_repeat("\t"$depth);
  595.                 echo "\t\t=> ".Util\Common::stripBasepath($path$this->config->basepath).PHP_EOL;
  596.             }
  597.  
  598.             $sniffs[$path;
  599.         }//end foreach
  600.  
  601.         return $sniffs;
  602.  
  603.     }//end expandSniffDirectory()
  604.  
  605.  
  606.     /**
  607.      * Expands a ruleset reference into a list of sniff files.
  608.      *
  609.      * @param string $ref        The reference from the ruleset XML file.
  610.      * @param string $rulesetDir The directory of the ruleset XML file, used to
  611.      *                            evaluate relative paths.
  612.      * @param int    $depth      How many nested processing steps we are in. This
  613.      *                            is only used for debug output.
  614.      *
  615.      * @return array 
  616.      * @throws RuntimeException If the reference is invalid.
  617.      */
  618.     private function expandRulesetReference($ref$rulesetDir$depth=0)
  619.     {
  620.         // Ignore internal sniffs codes as they are used to only
  621.         // hide and change internal messages.
  622.         if (substr($ref09=== 'Internal.'{
  623.             if (PHP_CODESNIFFER_VERBOSITY > 1{
  624.                 echo str_repeat("\t"$depth);
  625.                 echo "\t\t* ignoring internal sniff code *".PHP_EOL;
  626.             }
  627.  
  628.             return array();
  629.         }
  630.  
  631.         // As sniffs can't begin with a full stop, assume references in
  632.         // this format are relative paths and attempt to convert them
  633.         // to absolute paths. If this fails, let the reference run through
  634.         // the normal checks and have it fail as normal.
  635.         if (substr($ref01=== '.'{
  636.             $realpath = Util\Common::realpath($rulesetDir.'/'.$ref);
  637.             if ($realpath !== false{
  638.                 $ref $realpath;
  639.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  640.                     echo str_repeat("\t"$depth);
  641.                     echo "\t\t=> ".Util\Common::stripBasepath($ref$this->config->basepath).PHP_EOL;
  642.                 }
  643.             }
  644.         }
  645.  
  646.         // As sniffs can't begin with a tilde, assume references in
  647.         // this format are relative to the user's home directory.
  648.         if (substr($ref02=== '~/'{
  649.             $realpath = Util\Common::realpath($ref);
  650.             if ($realpath !== false{
  651.                 $ref $realpath;
  652.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  653.                     echo str_repeat("\t"$depth);
  654.                     echo "\t\t=> ".Util\Common::stripBasepath($ref$this->config->basepath).PHP_EOL;
  655.                 }
  656.             }
  657.         }
  658.  
  659.         if (is_file($ref=== true{
  660.             if (substr($ref-9=== 'Sniff.php'{
  661.                 // A single external sniff.
  662.                 $this->rulesetDirs[dirname(dirname(dirname($ref)));
  663.                 return array($ref);
  664.             }
  665.         else {
  666.             // See if this is a whole standard being referenced.
  667.             $path = Util\Standards::getInstalledStandardPath($ref);
  668.             if (Util\Common::isPharFile($path=== true && strpos($path'ruleset.xml'=== false{
  669.                 // If the ruleset exists inside the phar file, use it.
  670.                 if (file_exists($path.DIRECTORY_SEPARATOR.'ruleset.xml'=== true{
  671.                     $path $path.DIRECTORY_SEPARATOR.'ruleset.xml';
  672.                 else {
  673.                     $path = null;
  674.                 }
  675.             }
  676.  
  677.             if ($path !== null{
  678.                 $ref $path;
  679.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  680.                     echo str_repeat("\t"$depth);
  681.                     echo "\t\t=> ".Util\Common::stripBasepath($ref$this->config->basepath).PHP_EOL;
  682.                 }
  683.             else if (is_dir($ref=== false{
  684.                 // Work out the sniff path.
  685.                 $sepPos strpos($refDIRECTORY_SEPARATOR);
  686.                 if ($sepPos !== false{
  687.                     $stdName substr($ref0$sepPos);
  688.                     $path    substr($ref$sepPos);
  689.                 else {
  690.                     $parts   explode('.'$ref);
  691.                     $stdName $parts[0];
  692.                     if (count($parts=== 1{
  693.                         // A whole standard?
  694.                         $path '';
  695.                     else if (count($parts=== 2{
  696.                         // A directory of sniffs?
  697.                         $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1];
  698.                     else {
  699.                         // A single sniff?
  700.                         $path = DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$parts[1].DIRECTORY_SEPARATOR.$parts[2].'Sniff.php';
  701.                     }
  702.                 }
  703.  
  704.                 $newRef  = false;
  705.                 $stdPath = Util\Standards::getInstalledStandardPath($stdName);
  706.                 if ($stdPath !== null && $path !== ''{
  707.                     if (Util\Common::isPharFile($stdPath=== true
  708.                         && strpos($stdPath'ruleset.xml'=== false
  709.                     {
  710.                         // Phar files can only return the directory,
  711.                         // since ruleset can be omitted if building one standard.
  712.                         $newRef = Util\Common::realpath($stdPath.$path);
  713.                     else {
  714.                         $newRef = Util\Common::realpath(dirname($stdPath).$path);
  715.                     }
  716.                 }
  717.  
  718.                 if ($newRef === false{
  719.                     // The sniff is not locally installed, so check if it is being
  720.                     // referenced as a remote sniff outside the install. We do this
  721.                     // by looking through all directories where we have found ruleset
  722.                     // files before, looking for ones for this particular standard,
  723.                     // and seeing if it is in there.
  724.                     foreach ($this->rulesetDirs as $dir{
  725.                         if (strtolower(basename($dir)) !== strtolower($stdName)) {
  726.                             continue;
  727.                         }
  728.  
  729.                         $newRef = Util\Common::realpath($dir.$path);
  730.  
  731.                         if ($newRef !== false{
  732.                             $ref $newRef;
  733.                         }
  734.                     }
  735.                 else {
  736.                     $ref $newRef;
  737.                 }
  738.  
  739.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  740.                     echo str_repeat("\t"$depth);
  741.                     echo "\t\t=> ".Util\Common::stripBasepath($ref$this->config->basepath).PHP_EOL;
  742.                 }
  743.             }//end if
  744.         }//end if
  745.  
  746.         if (is_dir($ref=== true{
  747.             if (is_file($ref.DIRECTORY_SEPARATOR.'ruleset.xml'=== true{
  748.                 // We are referencing an external coding standard.
  749.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  750.                     echo str_repeat("\t"$depth);
  751.                     echo "\t\t* rule is referencing a standard using directory name; processing *".PHP_EOL;
  752.                 }
  753.  
  754.                 return $this->processRuleset($ref.DIRECTORY_SEPARATOR.'ruleset.xml'($depth + 2));
  755.             else {
  756.                 // We are referencing a whole directory of sniffs.
  757.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  758.                     echo str_repeat("\t"$depth);
  759.                     echo "\t\t* rule is referencing a directory of sniffs *".PHP_EOL;
  760.                     echo str_repeat("\t"$depth);
  761.                     echo "\t\tAdding sniff files from directory".PHP_EOL;
  762.                 }
  763.  
  764.                 return $this->expandSniffDirectory($ref($depth + 1));
  765.             }
  766.         else {
  767.             if (is_file($ref=== false{
  768.                 $error = "Referenced sniff \"$ref\" does not exist";
  769.                 throw new RuntimeException($error);
  770.             }
  771.  
  772.             if (substr($ref-9=== 'Sniff.php'{
  773.                 // A single sniff.
  774.                 return array($ref);
  775.             else {
  776.                 // Assume an external ruleset.xml file.
  777.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  778.                     echo str_repeat("\t"$depth);
  779.                     echo "\t\t* rule is referencing a standard using ruleset path; processing *".PHP_EOL;
  780.                 }
  781.  
  782.                 return $this->processRuleset($ref($depth + 2));
  783.             }
  784.         }//end if
  785.  
  786.     }//end expandRulesetReference()
  787.  
  788.  
  789.     /**
  790.      * Processes a rule from a ruleset XML file, overriding built-in defaults.
  791.      *
  792.      * @param SimpleXMLElement $rule      The rule object from a ruleset XML file.
  793.      * @param string[]         $newSniffs An array of sniffs that got included by this rule.
  794.      * @param int              $depth     How many nested processing steps we are in.
  795.      *                                     This is only used for debug output.
  796.      *
  797.      * @return void 
  798.      * @throws RuntimeException If rule settings are invalid.
  799.      */
  800.     private function processRule($rule$newSniffs$depth=0)
  801.     {
  802.         $ref  = (string) $rule['ref'];
  803.         $todo = array($ref);
  804.  
  805.         $parts explode('.'$ref);
  806.         if (count($parts<= 2{
  807.             // We are processing a standard or a category of sniffs.
  808.             foreach ($newSniffs as $sniffFile{
  809.                 $parts         explode(DIRECTORY_SEPARATOR$sniffFile);
  810.                 $sniffName     array_pop($parts);
  811.                 $sniffCategory array_pop($parts);
  812.                 array_pop($parts);
  813.                 $sniffStandard array_pop($parts);
  814.                 $todo[]        $sniffStandard.'.'.$sniffCategory.'.'.substr($sniffName0-9);
  815.             }
  816.         }
  817.  
  818.         foreach ($todo as $code{
  819.             // Custom severity.
  820.             if (isset($rule->severity=== true
  821.                 && $this->shouldProcessElement($rule->severity=== true
  822.             {
  823.                 if (isset($this->ruleset[$code]=== false{
  824.                     $this->ruleset[$code= array();
  825.                 }
  826.  
  827.                 $this->ruleset[$code]['severity'= (int) $rule->severity;
  828.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  829.                     echo str_repeat("\t"$depth);
  830.                     echo "\t\t=> severity set to ".(int) $rule->severity;
  831.                     if ($code !== $ref{
  832.                         echo " for $code";
  833.                     }
  834.  
  835.                     echo PHP_EOL;
  836.                 }
  837.             }
  838.  
  839.             // Custom message type.
  840.             if (isset($rule->type=== true
  841.                 && $this->shouldProcessElement($rule->type=== true
  842.             {
  843.                 if (isset($this->ruleset[$code]=== false{
  844.                     $this->ruleset[$code= array();
  845.                 }
  846.  
  847.                 $type strtolower((string) $rule->type);
  848.                 if ($type !== 'error' && $type !== 'warning'{
  849.                     throw new RuntimeException("Message type \"$type\" is invalid; must be \"error\" or \"warning\"");
  850.                 }
  851.  
  852.                 $this->ruleset[$code]['type'$type;
  853.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  854.                     echo str_repeat("\t"$depth);
  855.                     echo "\t\t=> message type set to ".(string) $rule->type;
  856.                     if ($code !== $ref{
  857.                         echo " for $code";
  858.                     }
  859.  
  860.                     echo PHP_EOL;
  861.                 }
  862.             }//end if
  863.  
  864.             // Custom message.
  865.             if (isset($rule->message=== true
  866.                 && $this->shouldProcessElement($rule->message=== true
  867.             {
  868.                 if (isset($this->ruleset[$code]=== false{
  869.                     $this->ruleset[$code= array();
  870.                 }
  871.  
  872.                 $this->ruleset[$code]['message'= (string) $rule->message;
  873.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  874.                     echo str_repeat("\t"$depth);
  875.                     echo "\t\t=> message set to ".(string) $rule->message;
  876.                     if ($code !== $ref{
  877.                         echo " for $code";
  878.                     }
  879.  
  880.                     echo PHP_EOL;
  881.                 }
  882.             }
  883.  
  884.             // Custom properties.
  885.             if (isset($rule->properties=== true
  886.                 && $this->shouldProcessElement($rule->properties=== true
  887.             {
  888.                 foreach ($rule->properties->property as $prop{
  889.                     if ($this->shouldProcessElement($prop=== false{
  890.                         continue;
  891.                     }
  892.  
  893.                     if (isset($this->ruleset[$code]=== false{
  894.                         $this->ruleset[$code= array(
  895.                                                  'properties' => array(),
  896.                                                 );
  897.                     else if (isset($this->ruleset[$code]['properties']=== false{
  898.                         $this->ruleset[$code]['properties'= array();
  899.                     }
  900.  
  901.                     $name = (string) $prop['name'];
  902.                     if (isset($prop['type']=== true
  903.                         && (string) $prop['type'=== 'array'
  904.                     {
  905.                         $value  = (string) $prop['value'];
  906.                         $values = array();
  907.                         foreach (explode(','$valueas $val{
  908.                             $v '';
  909.  
  910.                             list($k,$vexplode('=>'$val.'=>');
  911.                             if ($v !== ''{
  912.                                 $values[$k$v;
  913.                             else {
  914.                                 $values[$k;
  915.                             }
  916.                         }
  917.  
  918.                         $this->ruleset[$code]['properties'][$name$values;
  919.                         if (PHP_CODESNIFFER_VERBOSITY > 1{
  920.                             echo str_repeat("\t"$depth);
  921.                             echo "\t\t=> array property \"$name\" set to \"$value\"";
  922.                             if ($code !== $ref{
  923.                                 echo " for $code";
  924.                             }
  925.  
  926.                             echo PHP_EOL;
  927.                         }
  928.                     else {
  929.                         $this->ruleset[$code]['properties'][$name= (string) $prop['value'];
  930.                         if (PHP_CODESNIFFER_VERBOSITY > 1{
  931.                             echo str_repeat("\t"$depth);
  932.                             echo "\t\t=> property \"$name\" set to \"".(string) $prop['value'].'"';
  933.                             if ($code !== $ref{
  934.                                 echo " for $code";
  935.                             }
  936.  
  937.                             echo PHP_EOL;
  938.                         }
  939.                     }//end if
  940.                 }//end foreach
  941.             }//end if
  942.  
  943.             // Ignore patterns.
  944.             foreach ($rule->{'exclude-pattern'as $pattern{
  945.                 if ($this->shouldProcessElement($pattern=== false{
  946.                     continue;
  947.                 }
  948.  
  949.                 if (isset($this->ignorePatterns[$code]=== false{
  950.                     $this->ignorePatterns[$code= array();
  951.                 }
  952.  
  953.                 if (isset($pattern['type']=== false{
  954.                     $pattern['type''absolute';
  955.                 }
  956.  
  957.                 $this->ignorePatterns[$code][(string) $pattern= (string) $pattern['type'];
  958.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  959.                     echo str_repeat("\t"$depth);
  960.                     echo "\t\t=> added rule-specific ".(string) $pattern['type'].' ignore pattern';
  961.                     if ($code !== $ref{
  962.                         echo " for $code";
  963.                     }
  964.  
  965.                     echo ': '.(string) $pattern.PHP_EOL;
  966.                 }
  967.             }//end foreach
  968.  
  969.             // Include patterns.
  970.             foreach ($rule->{'include-pattern'as $pattern{
  971.                 if ($this->shouldProcessElement($pattern=== false{
  972.                     continue;
  973.                 }
  974.  
  975.                 if (isset($this->includePatterns[$code]=== false{
  976.                     $this->includePatterns[$code= array();
  977.                 }
  978.  
  979.                 if (isset($pattern['type']=== false{
  980.                     $pattern['type''absolute';
  981.                 }
  982.  
  983.                 $this->includePatterns[$code][(string) $pattern= (string) $pattern['type'];
  984.                 if (PHP_CODESNIFFER_VERBOSITY > 1{
  985.                     echo str_repeat("\t"$depth);
  986.                     echo "\t\t=> added rule-specific ".(string) $pattern['type'].' include pattern';
  987.                     if ($code !== $ref{
  988.                         echo " for $code";
  989.                     }
  990.  
  991.                     echo ': '.(string) $pattern.PHP_EOL;
  992.                 }
  993.             }//end foreach
  994.         }//end foreach
  995.  
  996.     }//end processRule()
  997.  
  998.  
  999.     /**
  1000.      * Determine if an element should be processed or ignored.
  1001.      *
  1002.      * @param SimpleXMLElement $element An object from a ruleset XML file.
  1003.      *
  1004.      * @return bool 
  1005.      */
  1006.     private function shouldProcessElement($element)
  1007.     {
  1008.         if (isset($element['phpcbf-only']=== false
  1009.             && isset($element['phpcs-only']=== false
  1010.         {
  1011.             // No exceptions are being made.
  1012.             return true;
  1013.         }
  1014.  
  1015.         if (PHP_CODESNIFFER_CBF === true
  1016.             && isset($element['phpcbf-only']=== true
  1017.             && (string) $element['phpcbf-only'=== 'true'
  1018.         {
  1019.             return true;
  1020.         }
  1021.  
  1022.         if (PHP_CODESNIFFER_CBF === false
  1023.             && isset($element['phpcs-only']=== true
  1024.             && (string) $element['phpcs-only'=== 'true'
  1025.         {
  1026.             return true;
  1027.         }
  1028.  
  1029.         return false;
  1030.  
  1031.     }//end shouldProcessElement()
  1032.  
  1033.  
  1034.     /**
  1035.      * Loads and stores sniffs objects used for sniffing files.
  1036.      *
  1037.      * @param array $files        Paths to the sniff files to register.
  1038.      * @param array $restrictions The sniff class names to restrict the allowed
  1039.      *                             listeners to.
  1040.      * @param array $exclusions   The sniff class names to exclude from the
  1041.      *                             listeners list.
  1042.      *
  1043.      * @return void 
  1044.      */
  1045.     public function registerSniffs($files$restrictions$exclusions)
  1046.     {
  1047.         $listeners = array();
  1048.  
  1049.         foreach ($files as $file{
  1050.             // Work out where the position of /StandardName/Sniffs/... is
  1051.             // so we can determine what the class will be called.
  1052.             $sniffPos strrpos($fileDIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR);
  1053.             if ($sniffPos === false{
  1054.                 continue;
  1055.             }
  1056.  
  1057.             $slashPos strrpos(substr($file0$sniffPos)DIRECTORY_SEPARATOR);
  1058.             if ($slashPos === false{
  1059.                 continue;
  1060.             }
  1061.  
  1062.             $className = Autoload::loadFile($file);
  1063.  
  1064.             // If they have specified a list of sniffs to restrict to, check
  1065.             // to see if this sniff is allowed.
  1066.             if (empty($restrictions=== false
  1067.                 && isset($restrictions[strtolower($className)]=== false
  1068.             {
  1069.                 continue;
  1070.             }
  1071.  
  1072.             // If they have specified a list of sniffs to exclude, check
  1073.             // to see if this sniff is allowed.
  1074.             if (empty($exclusions=== false
  1075.                 && isset($exclusions[strtolower($className)]=== true
  1076.             {
  1077.                 continue;
  1078.             }
  1079.  
  1080.             // Skip abstract classes.
  1081.             $reflection = new \ReflectionClass($className);
  1082.             if ($reflection->isAbstract(=== true{
  1083.                 continue;
  1084.             }
  1085.  
  1086.             $listeners[$className$className;
  1087.  
  1088.             if (PHP_CODESNIFFER_VERBOSITY > 2{
  1089.                 echo "Registered $className".PHP_EOL;
  1090.             }
  1091.         }//end foreach
  1092.  
  1093.         $this->sniffs = $listeners;
  1094.  
  1095.     }//end registerSniffs()
  1096.  
  1097.  
  1098.     /**
  1099.      * Populates the array of PHP_CodeSniffer_Sniff's for this file.
  1100.      *
  1101.      * @return void 
  1102.      * @throws RuntimeException If sniff registration fails.
  1103.      */
  1104.     public function populateTokenListeners()
  1105.     {
  1106.         // Construct a list of listeners indexed by token being listened for.
  1107.         $this->tokenListeners = array();
  1108.  
  1109.         foreach ($this->sniffs as $sniffClass => $sniffObject{
  1110.             $this->sniffs[$sniffClass= null;
  1111.             $this->sniffs[$sniffClass= new $sniffClass();
  1112.  
  1113.             $sniffCode = Util\Common::getSniffCode($sniffClass);
  1114.             $this->sniffCodes[$sniffCode$sniffClass;
  1115.  
  1116.             // Set custom properties.
  1117.             if (isset($this->ruleset[$sniffCode]['properties']=== true{
  1118.                 foreach ($this->ruleset[$sniffCode]['properties'as $name => $value{
  1119.                     $this->setSniffProperty($sniffClass$name$value);
  1120.                 }
  1121.             }
  1122.  
  1123.             $tokenizers = array();
  1124.             $vars       get_class_vars($sniffClass);
  1125.             if (isset($vars['supportedTokenizers']=== true{
  1126.                 foreach ($vars['supportedTokenizers'as $tokenizer{
  1127.                     $tokenizers[$tokenizer$tokenizer;
  1128.                 }
  1129.             else {
  1130.                 $tokenizers = array('PHP' => 'PHP');
  1131.             }
  1132.  
  1133.             $tokens $this->sniffs[$sniffClass]->register();
  1134.             if (is_array($tokens=== false{
  1135.                 $msg = "Sniff $sniffClass register() method must return an array";
  1136.                 throw new RuntimeException($msg);
  1137.             }
  1138.  
  1139.             $ignorePatterns = array();
  1140.             $patterns       $this->getIgnorePatterns($sniffCode);
  1141.             foreach ($patterns as $pattern => $type{
  1142.                 $replacements = array(
  1143.                                  '\\,' => ',',
  1144.                                  '*'   => '.*',
  1145.                                 );
  1146.  
  1147.                 $ignorePatterns[strtr($pattern$replacements);
  1148.             }
  1149.  
  1150.             $includePatterns = array();
  1151.             $patterns        $this->getIncludePatterns($sniffCode);
  1152.             foreach ($patterns as $pattern => $type{
  1153.                 $replacements = array(
  1154.                                  '\\,' => ',',
  1155.                                  '*'   => '.*',
  1156.                                 );
  1157.  
  1158.                 $includePatterns[strtr($pattern$replacements);
  1159.             }
  1160.  
  1161.             foreach ($tokens as $token{
  1162.                 if (isset($this->tokenListeners[$token]=== false{
  1163.                     $this->tokenListeners[$token= array();
  1164.                 }
  1165.  
  1166.                 if (isset($this->tokenListeners[$token][$sniffClass]=== false{
  1167.                     $this->tokenListeners[$token][$sniffClass= array(
  1168.                                                                   'class'      => $sniffClass,
  1169.                                                                   'source'     => $sniffCode,
  1170.                                                                   'tokenizers' => $tokenizers,
  1171.                                                                   'ignore'     => $ignorePatterns,
  1172.                                                                   'include'    => $includePatterns,
  1173.                                                                  );
  1174.                 }
  1175.             }
  1176.         }//end foreach
  1177.  
  1178.     }//end populateTokenListeners()
  1179.  
  1180.  
  1181.     /**
  1182.      * Set a single property for a sniff.
  1183.      *
  1184.      * @param string $sniffClass The class name of the sniff.
  1185.      * @param string $name       The name of the property to change.
  1186.      * @param string $value      The new value of the property.
  1187.      *
  1188.      * @return void 
  1189.      */
  1190.     public function setSniffProperty($sniffClass$name$value)
  1191.     {
  1192.         // Setting a property for a sniff we are not using.
  1193.         if (isset($this->sniffs[$sniffClass]=== false{
  1194.             return;
  1195.         }
  1196.  
  1197.         $name trim($name);
  1198.         if (is_string($value=== true{
  1199.             $value trim($value);
  1200.         }
  1201.  
  1202.         // Special case for booleans.
  1203.         if ($value === 'true'{
  1204.             $value = true;
  1205.         else if ($value === 'false'{
  1206.             $value = false;
  1207.         }
  1208.  
  1209.         $this->sniffs[$sniffClass]->$name $value;
  1210.  
  1211.     }//end setSniffProperty()
  1212.  
  1213.  
  1214.     /**
  1215.      * Gets the array of ignore patterns.
  1216.      *
  1217.      * Optionally takes a listener to get ignore patterns specified
  1218.      * for that sniff only.
  1219.      *
  1220.      * @param string $listener The listener to get patterns for. If NULL, all
  1221.      *                          patterns are returned.
  1222.      *
  1223.      * @return array 
  1224.      */
  1225.     public function getIgnorePatterns($listener=null)
  1226.     {
  1227.         if ($listener === null{
  1228.             return $this->ignorePatterns;
  1229.         }
  1230.  
  1231.         if (isset($this->ignorePatterns[$listener]=== true{
  1232.             return $this->ignorePatterns[$listener];
  1233.         }
  1234.  
  1235.         return array();
  1236.  
  1237.     }//end getIgnorePatterns()
  1238.  
  1239.  
  1240.     /**
  1241.      * Gets the array of include patterns.
  1242.      *
  1243.      * Optionally takes a listener to get include patterns specified
  1244.      * for that sniff only.
  1245.      *
  1246.      * @param string $listener The listener to get patterns for. If NULL, all
  1247.      *                          patterns are returned.
  1248.      *
  1249.      * @return array 
  1250.      */
  1251.     public function getIncludePatterns($listener=null)
  1252.     {
  1253.         if ($listener === null{
  1254.             return $this->includePatterns;
  1255.         }
  1256.  
  1257.         if (isset($this->includePatterns[$listener]=== true{
  1258.             return $this->includePatterns[$listener];
  1259.         }
  1260.  
  1261.         return array();
  1262.  
  1263.     }//end getIncludePatterns()
  1264.  
  1265.  
  1266. }//end class

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