@@ -16,116 +16,116 @@ |
||
| 16 | 16 | { |
| 17 | 17 | |
| 18 | 18 | |
| 19 | - /** |
|
| 20 | - * Generate a partial report for a single processed file. |
|
| 21 | - * |
|
| 22 | - * Function should return TRUE if it printed or stored data about the file |
|
| 23 | - * and FALSE if it ignored the file. Returning TRUE indicates that the file and |
|
| 24 | - * its data should be counted in the grand totals. |
|
| 25 | - * |
|
| 26 | - * @param array $report Prepared report data. |
|
| 27 | - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. |
|
| 28 | - * @param bool $showSources Show sources? |
|
| 29 | - * @param int $width Maximum allowed line width. |
|
| 30 | - * |
|
| 31 | - * @return bool |
|
| 32 | - */ |
|
| 33 | - public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) |
|
| 34 | - { |
|
| 35 | - $errors = $phpcsFile->getFixableCount(); |
|
| 36 | - if ($errors === 0) { |
|
| 37 | - return false; |
|
| 38 | - } |
|
| 39 | - |
|
| 40 | - $phpcsFile->disableCaching(); |
|
| 41 | - $tokens = $phpcsFile->getTokens(); |
|
| 42 | - if (empty($tokens) === true) { |
|
| 43 | - if (PHP_CODESNIFFER_VERBOSITY === 1) { |
|
| 44 | - $startTime = microtime(true); |
|
| 45 | - echo 'DIFF report is parsing '.basename($report['filename']).' '; |
|
| 46 | - } else if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 47 | - echo 'DIFF report is forcing parse of '.$report['filename'].PHP_EOL; |
|
| 48 | - } |
|
| 49 | - |
|
| 50 | - $phpcsFile->parse(); |
|
| 51 | - |
|
| 52 | - if (PHP_CODESNIFFER_VERBOSITY === 1) { |
|
| 53 | - $timeTaken = ((microtime(true) - $startTime) * 1000); |
|
| 54 | - if ($timeTaken < 1000) { |
|
| 55 | - $timeTaken = round($timeTaken); |
|
| 56 | - echo "DONE in {$timeTaken}ms"; |
|
| 57 | - } else { |
|
| 58 | - $timeTaken = round(($timeTaken / 1000), 2); |
|
| 59 | - echo "DONE in $timeTaken secs"; |
|
| 60 | - } |
|
| 61 | - |
|
| 62 | - echo PHP_EOL; |
|
| 63 | - } |
|
| 64 | - |
|
| 65 | - $phpcsFile->fixer->startFile($phpcsFile); |
|
| 66 | - }//end if |
|
| 67 | - |
|
| 68 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 69 | - ob_end_clean(); |
|
| 70 | - echo "\t*** START FILE FIXING ***".PHP_EOL; |
|
| 71 | - } |
|
| 72 | - |
|
| 73 | - $fixed = $phpcsFile->fixer->fixFile(); |
|
| 74 | - |
|
| 75 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 76 | - echo "\t*** END FILE FIXING ***".PHP_EOL; |
|
| 77 | - ob_start(); |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - if ($fixed === false) { |
|
| 81 | - return false; |
|
| 82 | - } |
|
| 83 | - |
|
| 84 | - $diff = $phpcsFile->fixer->generateDiff(); |
|
| 85 | - if ($diff === '') { |
|
| 86 | - // Nothing to print. |
|
| 87 | - return false; |
|
| 88 | - } |
|
| 89 | - |
|
| 90 | - echo $diff.PHP_EOL; |
|
| 91 | - return true; |
|
| 92 | - |
|
| 93 | - }//end generateFileReport() |
|
| 94 | - |
|
| 95 | - |
|
| 96 | - /** |
|
| 97 | - * Prints all errors and warnings for each file processed. |
|
| 98 | - * |
|
| 99 | - * @param string $cachedData Any partial report data that was returned from |
|
| 100 | - * generateFileReport during the run. |
|
| 101 | - * @param int $totalFiles Total number of files processed during the run. |
|
| 102 | - * @param int $totalErrors Total number of errors found during the run. |
|
| 103 | - * @param int $totalWarnings Total number of warnings found during the run. |
|
| 104 | - * @param int $totalFixable Total number of problems that can be fixed. |
|
| 105 | - * @param bool $showSources Show sources? |
|
| 106 | - * @param int $width Maximum allowed line width. |
|
| 107 | - * @param bool $interactive Are we running in interactive mode? |
|
| 108 | - * @param bool $toScreen Is the report being printed to screen? |
|
| 109 | - * |
|
| 110 | - * @return void |
|
| 111 | - */ |
|
| 112 | - public function generate( |
|
| 113 | - $cachedData, |
|
| 114 | - $totalFiles, |
|
| 115 | - $totalErrors, |
|
| 116 | - $totalWarnings, |
|
| 117 | - $totalFixable, |
|
| 118 | - $showSources=false, |
|
| 119 | - $width=80, |
|
| 120 | - $interactive=false, |
|
| 121 | - $toScreen=true |
|
| 122 | - ) { |
|
| 123 | - echo $cachedData; |
|
| 124 | - if ($toScreen === true && $cachedData !== '') { |
|
| 125 | - echo PHP_EOL; |
|
| 126 | - } |
|
| 127 | - |
|
| 128 | - }//end generate() |
|
| 19 | + /** |
|
| 20 | + * Generate a partial report for a single processed file. |
|
| 21 | + * |
|
| 22 | + * Function should return TRUE if it printed or stored data about the file |
|
| 23 | + * and FALSE if it ignored the file. Returning TRUE indicates that the file and |
|
| 24 | + * its data should be counted in the grand totals. |
|
| 25 | + * |
|
| 26 | + * @param array $report Prepared report data. |
|
| 27 | + * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. |
|
| 28 | + * @param bool $showSources Show sources? |
|
| 29 | + * @param int $width Maximum allowed line width. |
|
| 30 | + * |
|
| 31 | + * @return bool |
|
| 32 | + */ |
|
| 33 | + public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) |
|
| 34 | + { |
|
| 35 | + $errors = $phpcsFile->getFixableCount(); |
|
| 36 | + if ($errors === 0) { |
|
| 37 | + return false; |
|
| 38 | + } |
|
| 39 | + |
|
| 40 | + $phpcsFile->disableCaching(); |
|
| 41 | + $tokens = $phpcsFile->getTokens(); |
|
| 42 | + if (empty($tokens) === true) { |
|
| 43 | + if (PHP_CODESNIFFER_VERBOSITY === 1) { |
|
| 44 | + $startTime = microtime(true); |
|
| 45 | + echo 'DIFF report is parsing '.basename($report['filename']).' '; |
|
| 46 | + } else if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 47 | + echo 'DIFF report is forcing parse of '.$report['filename'].PHP_EOL; |
|
| 48 | + } |
|
| 49 | + |
|
| 50 | + $phpcsFile->parse(); |
|
| 51 | + |
|
| 52 | + if (PHP_CODESNIFFER_VERBOSITY === 1) { |
|
| 53 | + $timeTaken = ((microtime(true) - $startTime) * 1000); |
|
| 54 | + if ($timeTaken < 1000) { |
|
| 55 | + $timeTaken = round($timeTaken); |
|
| 56 | + echo "DONE in {$timeTaken}ms"; |
|
| 57 | + } else { |
|
| 58 | + $timeTaken = round(($timeTaken / 1000), 2); |
|
| 59 | + echo "DONE in $timeTaken secs"; |
|
| 60 | + } |
|
| 61 | + |
|
| 62 | + echo PHP_EOL; |
|
| 63 | + } |
|
| 64 | + |
|
| 65 | + $phpcsFile->fixer->startFile($phpcsFile); |
|
| 66 | + }//end if |
|
| 67 | + |
|
| 68 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 69 | + ob_end_clean(); |
|
| 70 | + echo "\t*** START FILE FIXING ***".PHP_EOL; |
|
| 71 | + } |
|
| 72 | + |
|
| 73 | + $fixed = $phpcsFile->fixer->fixFile(); |
|
| 74 | + |
|
| 75 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 76 | + echo "\t*** END FILE FIXING ***".PHP_EOL; |
|
| 77 | + ob_start(); |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + if ($fixed === false) { |
|
| 81 | + return false; |
|
| 82 | + } |
|
| 83 | + |
|
| 84 | + $diff = $phpcsFile->fixer->generateDiff(); |
|
| 85 | + if ($diff === '') { |
|
| 86 | + // Nothing to print. |
|
| 87 | + return false; |
|
| 88 | + } |
|
| 89 | + |
|
| 90 | + echo $diff.PHP_EOL; |
|
| 91 | + return true; |
|
| 92 | + |
|
| 93 | + }//end generateFileReport() |
|
| 94 | + |
|
| 95 | + |
|
| 96 | + /** |
|
| 97 | + * Prints all errors and warnings for each file processed. |
|
| 98 | + * |
|
| 99 | + * @param string $cachedData Any partial report data that was returned from |
|
| 100 | + * generateFileReport during the run. |
|
| 101 | + * @param int $totalFiles Total number of files processed during the run. |
|
| 102 | + * @param int $totalErrors Total number of errors found during the run. |
|
| 103 | + * @param int $totalWarnings Total number of warnings found during the run. |
|
| 104 | + * @param int $totalFixable Total number of problems that can be fixed. |
|
| 105 | + * @param bool $showSources Show sources? |
|
| 106 | + * @param int $width Maximum allowed line width. |
|
| 107 | + * @param bool $interactive Are we running in interactive mode? |
|
| 108 | + * @param bool $toScreen Is the report being printed to screen? |
|
| 109 | + * |
|
| 110 | + * @return void |
|
| 111 | + */ |
|
| 112 | + public function generate( |
|
| 113 | + $cachedData, |
|
| 114 | + $totalFiles, |
|
| 115 | + $totalErrors, |
|
| 116 | + $totalWarnings, |
|
| 117 | + $totalFixable, |
|
| 118 | + $showSources=false, |
|
| 119 | + $width=80, |
|
| 120 | + $interactive=false, |
|
| 121 | + $toScreen=true |
|
| 122 | + ) { |
|
| 123 | + echo $cachedData; |
|
| 124 | + if ($toScreen === true && $cachedData !== '') { |
|
| 125 | + echo PHP_EOL; |
|
| 126 | + } |
|
| 127 | + |
|
| 128 | + }//end generate() |
|
| 129 | 129 | |
| 130 | 130 | |
| 131 | 131 | }//end class |
@@ -30,7 +30,7 @@ discard block |
||
| 30 | 30 | * |
| 31 | 31 | * @return bool |
| 32 | 32 | */ |
| 33 | - public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) |
|
| 33 | + public function generateFileReport($report, File $phpcsFile, $showSources = false, $width = 80) |
|
| 34 | 34 | { |
| 35 | 35 | $errors = $phpcsFile->getFixableCount(); |
| 36 | 36 | if ($errors === 0) { |
@@ -115,10 +115,10 @@ discard block |
||
| 115 | 115 | $totalErrors, |
| 116 | 116 | $totalWarnings, |
| 117 | 117 | $totalFixable, |
| 118 | - $showSources=false, |
|
| 119 | - $width=80, |
|
| 120 | - $interactive=false, |
|
| 121 | - $toScreen=true |
|
| 118 | + $showSources = false, |
|
| 119 | + $width = 80, |
|
| 120 | + $interactive = false, |
|
| 121 | + $toScreen = true |
|
| 122 | 122 | ) { |
| 123 | 123 | echo $cachedData; |
| 124 | 124 | if ($toScreen === true && $cachedData !== '') { |
@@ -16,361 +16,361 @@ |
||
| 16 | 16 | abstract class VersionControl implements Report |
| 17 | 17 | { |
| 18 | 18 | |
| 19 | - /** |
|
| 20 | - * The name of the report we want in the output. |
|
| 21 | - * |
|
| 22 | - * @var string |
|
| 23 | - */ |
|
| 24 | - protected $reportName = 'VERSION CONTROL'; |
|
| 25 | - |
|
| 26 | - |
|
| 27 | - /** |
|
| 28 | - * Generate a partial report for a single processed file. |
|
| 29 | - * |
|
| 30 | - * Function should return TRUE if it printed or stored data about the file |
|
| 31 | - * and FALSE if it ignored the file. Returning TRUE indicates that the file and |
|
| 32 | - * its data should be counted in the grand totals. |
|
| 33 | - * |
|
| 34 | - * @param array $report Prepared report data. |
|
| 35 | - * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. |
|
| 36 | - * @param bool $showSources Show sources? |
|
| 37 | - * @param int $width Maximum allowed line width. |
|
| 38 | - * |
|
| 39 | - * @return bool |
|
| 40 | - */ |
|
| 41 | - public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) |
|
| 42 | - { |
|
| 43 | - $blames = $this->getBlameContent($report['filename']); |
|
| 44 | - |
|
| 45 | - $authorCache = []; |
|
| 46 | - $praiseCache = []; |
|
| 47 | - $sourceCache = []; |
|
| 48 | - |
|
| 49 | - foreach ($report['messages'] as $line => $lineErrors) { |
|
| 50 | - $author = 'Unknown'; |
|
| 51 | - if (isset($blames[($line - 1)]) === true) { |
|
| 52 | - $blameAuthor = $this->getAuthor($blames[($line - 1)]); |
|
| 53 | - if ($blameAuthor !== false) { |
|
| 54 | - $author = $blameAuthor; |
|
| 55 | - } |
|
| 56 | - } |
|
| 57 | - |
|
| 58 | - if (isset($authorCache[$author]) === false) { |
|
| 59 | - $authorCache[$author] = 0; |
|
| 60 | - $praiseCache[$author] = [ |
|
| 61 | - 'good' => 0, |
|
| 62 | - 'bad' => 0, |
|
| 63 | - ]; |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - $praiseCache[$author]['bad']++; |
|
| 67 | - |
|
| 68 | - foreach ($lineErrors as $column => $colErrors) { |
|
| 69 | - foreach ($colErrors as $error) { |
|
| 70 | - $authorCache[$author]++; |
|
| 71 | - |
|
| 72 | - if ($showSources === true) { |
|
| 73 | - $source = $error['source']; |
|
| 74 | - if (isset($sourceCache[$author][$source]) === false) { |
|
| 75 | - $sourceCache[$author][$source] = [ |
|
| 76 | - 'count' => 1, |
|
| 77 | - 'fixable' => $error['fixable'], |
|
| 78 | - ]; |
|
| 79 | - } else { |
|
| 80 | - $sourceCache[$author][$source]['count']++; |
|
| 81 | - } |
|
| 82 | - } |
|
| 83 | - } |
|
| 84 | - } |
|
| 85 | - |
|
| 86 | - unset($blames[($line - 1)]); |
|
| 87 | - }//end foreach |
|
| 88 | - |
|
| 89 | - // Now go through and give the authors some credit for |
|
| 90 | - // all the lines that do not have errors. |
|
| 91 | - foreach ($blames as $line) { |
|
| 92 | - $author = $this->getAuthor($line); |
|
| 93 | - if ($author === false) { |
|
| 94 | - $author = 'Unknown'; |
|
| 95 | - } |
|
| 96 | - |
|
| 97 | - if (isset($authorCache[$author]) === false) { |
|
| 98 | - // This author doesn't have any errors. |
|
| 99 | - if (PHP_CODESNIFFER_VERBOSITY === 0) { |
|
| 100 | - continue; |
|
| 101 | - } |
|
| 102 | - |
|
| 103 | - $authorCache[$author] = 0; |
|
| 104 | - $praiseCache[$author] = [ |
|
| 105 | - 'good' => 0, |
|
| 106 | - 'bad' => 0, |
|
| 107 | - ]; |
|
| 108 | - } |
|
| 109 | - |
|
| 110 | - $praiseCache[$author]['good']++; |
|
| 111 | - }//end foreach |
|
| 112 | - |
|
| 113 | - foreach ($authorCache as $author => $errors) { |
|
| 114 | - echo "AUTHOR>>$author>>$errors".PHP_EOL; |
|
| 115 | - } |
|
| 116 | - |
|
| 117 | - foreach ($praiseCache as $author => $praise) { |
|
| 118 | - echo "PRAISE>>$author>>".$praise['good'].'>>'.$praise['bad'].PHP_EOL; |
|
| 119 | - } |
|
| 120 | - |
|
| 121 | - foreach ($sourceCache as $author => $sources) { |
|
| 122 | - foreach ($sources as $source => $sourceData) { |
|
| 123 | - $count = $sourceData['count']; |
|
| 124 | - $fixable = (int) $sourceData['fixable']; |
|
| 125 | - echo "SOURCE>>$author>>$source>>$count>>$fixable".PHP_EOL; |
|
| 126 | - } |
|
| 127 | - } |
|
| 128 | - |
|
| 129 | - return true; |
|
| 130 | - |
|
| 131 | - }//end generateFileReport() |
|
| 132 | - |
|
| 133 | - |
|
| 134 | - /** |
|
| 135 | - * Prints the author of all errors and warnings, as given by "version control blame". |
|
| 136 | - * |
|
| 137 | - * @param string $cachedData Any partial report data that was returned from |
|
| 138 | - * generateFileReport during the run. |
|
| 139 | - * @param int $totalFiles Total number of files processed during the run. |
|
| 140 | - * @param int $totalErrors Total number of errors found during the run. |
|
| 141 | - * @param int $totalWarnings Total number of warnings found during the run. |
|
| 142 | - * @param int $totalFixable Total number of problems that can be fixed. |
|
| 143 | - * @param bool $showSources Show sources? |
|
| 144 | - * @param int $width Maximum allowed line width. |
|
| 145 | - * @param bool $interactive Are we running in interactive mode? |
|
| 146 | - * @param bool $toScreen Is the report being printed to screen? |
|
| 147 | - * |
|
| 148 | - * @return void |
|
| 149 | - */ |
|
| 150 | - public function generate( |
|
| 151 | - $cachedData, |
|
| 152 | - $totalFiles, |
|
| 153 | - $totalErrors, |
|
| 154 | - $totalWarnings, |
|
| 155 | - $totalFixable, |
|
| 156 | - $showSources=false, |
|
| 157 | - $width=80, |
|
| 158 | - $interactive=false, |
|
| 159 | - $toScreen=true |
|
| 160 | - ) { |
|
| 161 | - $errorsShown = ($totalErrors + $totalWarnings); |
|
| 162 | - if ($errorsShown === 0) { |
|
| 163 | - // Nothing to show. |
|
| 164 | - return; |
|
| 165 | - } |
|
| 166 | - |
|
| 167 | - $lines = explode(PHP_EOL, $cachedData); |
|
| 168 | - array_pop($lines); |
|
| 169 | - |
|
| 170 | - if (empty($lines) === true) { |
|
| 171 | - return; |
|
| 172 | - } |
|
| 173 | - |
|
| 174 | - $authorCache = []; |
|
| 175 | - $praiseCache = []; |
|
| 176 | - $sourceCache = []; |
|
| 177 | - |
|
| 178 | - foreach ($lines as $line) { |
|
| 179 | - $parts = explode('>>', $line); |
|
| 180 | - switch ($parts[0]) { |
|
| 181 | - case 'AUTHOR': |
|
| 182 | - if (isset($authorCache[$parts[1]]) === false) { |
|
| 183 | - $authorCache[$parts[1]] = $parts[2]; |
|
| 184 | - } else { |
|
| 185 | - $authorCache[$parts[1]] += $parts[2]; |
|
| 186 | - } |
|
| 187 | - break; |
|
| 188 | - case 'PRAISE': |
|
| 189 | - if (isset($praiseCache[$parts[1]]) === false) { |
|
| 190 | - $praiseCache[$parts[1]] = [ |
|
| 191 | - 'good' => $parts[2], |
|
| 192 | - 'bad' => $parts[3], |
|
| 193 | - ]; |
|
| 194 | - } else { |
|
| 195 | - $praiseCache[$parts[1]]['good'] += $parts[2]; |
|
| 196 | - $praiseCache[$parts[1]]['bad'] += $parts[3]; |
|
| 197 | - } |
|
| 198 | - break; |
|
| 199 | - case 'SOURCE': |
|
| 200 | - if (isset($praiseCache[$parts[1]]) === false) { |
|
| 201 | - $praiseCache[$parts[1]] = []; |
|
| 202 | - } |
|
| 203 | - |
|
| 204 | - if (isset($sourceCache[$parts[1]][$parts[2]]) === false) { |
|
| 205 | - $sourceCache[$parts[1]][$parts[2]] = [ |
|
| 206 | - 'count' => $parts[3], |
|
| 207 | - 'fixable' => (bool) $parts[4], |
|
| 208 | - ]; |
|
| 209 | - } else { |
|
| 210 | - $sourceCache[$parts[1]][$parts[2]]['count'] += $parts[3]; |
|
| 211 | - } |
|
| 212 | - break; |
|
| 213 | - default: |
|
| 214 | - break; |
|
| 215 | - }//end switch |
|
| 216 | - }//end foreach |
|
| 217 | - |
|
| 218 | - // Make sure the report width isn't too big. |
|
| 219 | - $maxLength = 0; |
|
| 220 | - foreach ($authorCache as $author => $count) { |
|
| 221 | - $maxLength = max($maxLength, strlen($author)); |
|
| 222 | - if ($showSources === true && isset($sourceCache[$author]) === true) { |
|
| 223 | - foreach ($sourceCache[$author] as $source => $sourceData) { |
|
| 224 | - if ($source === 'count') { |
|
| 225 | - continue; |
|
| 226 | - } |
|
| 227 | - |
|
| 228 | - $maxLength = max($maxLength, (strlen($source) + 9)); |
|
| 229 | - } |
|
| 230 | - } |
|
| 231 | - } |
|
| 232 | - |
|
| 233 | - $width = min($width, ($maxLength + 30)); |
|
| 234 | - $width = max($width, 70); |
|
| 235 | - arsort($authorCache); |
|
| 236 | - |
|
| 237 | - echo PHP_EOL."\033[1m".'PHP CODE SNIFFER '.$this->reportName.' BLAME SUMMARY'."\033[0m".PHP_EOL; |
|
| 238 | - echo str_repeat('-', $width).PHP_EOL."\033[1m"; |
|
| 239 | - if ($showSources === true) { |
|
| 240 | - echo 'AUTHOR SOURCE'.str_repeat(' ', ($width - 43)).'(Author %) (Overall %) COUNT'.PHP_EOL; |
|
| 241 | - echo str_repeat('-', $width).PHP_EOL; |
|
| 242 | - } else { |
|
| 243 | - echo 'AUTHOR'.str_repeat(' ', ($width - 34)).'(Author %) (Overall %) COUNT'.PHP_EOL; |
|
| 244 | - echo str_repeat('-', $width).PHP_EOL; |
|
| 245 | - } |
|
| 246 | - |
|
| 247 | - echo "\033[0m"; |
|
| 248 | - |
|
| 249 | - if ($showSources === true) { |
|
| 250 | - $maxSniffWidth = ($width - 15); |
|
| 251 | - |
|
| 252 | - if ($totalFixable > 0) { |
|
| 253 | - $maxSniffWidth -= 4; |
|
| 254 | - } |
|
| 255 | - } |
|
| 256 | - |
|
| 257 | - $fixableSources = 0; |
|
| 258 | - |
|
| 259 | - foreach ($authorCache as $author => $count) { |
|
| 260 | - if ($praiseCache[$author]['good'] === 0) { |
|
| 261 | - $percent = 0; |
|
| 262 | - } else { |
|
| 263 | - $total = ($praiseCache[$author]['bad'] + $praiseCache[$author]['good']); |
|
| 264 | - $percent = round(($praiseCache[$author]['bad'] / $total * 100), 2); |
|
| 265 | - } |
|
| 266 | - |
|
| 267 | - $overallPercent = '('.round((($count / $errorsShown) * 100), 2).')'; |
|
| 268 | - $authorPercent = '('.$percent.')'; |
|
| 269 | - $line = str_repeat(' ', (6 - strlen($count))).$count; |
|
| 270 | - $line = str_repeat(' ', (12 - strlen($overallPercent))).$overallPercent.$line; |
|
| 271 | - $line = str_repeat(' ', (11 - strlen($authorPercent))).$authorPercent.$line; |
|
| 272 | - $line = $author.str_repeat(' ', ($width - strlen($author) - strlen($line))).$line; |
|
| 273 | - |
|
| 274 | - if ($showSources === true) { |
|
| 275 | - $line = "\033[1m$line\033[0m"; |
|
| 276 | - } |
|
| 277 | - |
|
| 278 | - echo $line.PHP_EOL; |
|
| 279 | - |
|
| 280 | - if ($showSources === true && isset($sourceCache[$author]) === true) { |
|
| 281 | - $errors = $sourceCache[$author]; |
|
| 282 | - asort($errors); |
|
| 283 | - $errors = array_reverse($errors); |
|
| 284 | - |
|
| 285 | - foreach ($errors as $source => $sourceData) { |
|
| 286 | - if ($source === 'count') { |
|
| 287 | - continue; |
|
| 288 | - } |
|
| 289 | - |
|
| 290 | - $count = $sourceData['count']; |
|
| 291 | - |
|
| 292 | - $srcLength = strlen($source); |
|
| 293 | - if ($srcLength > $maxSniffWidth) { |
|
| 294 | - $source = substr($source, 0, $maxSniffWidth); |
|
| 295 | - } |
|
| 296 | - |
|
| 297 | - $line = str_repeat(' ', (5 - strlen($count))).$count; |
|
| 298 | - |
|
| 299 | - echo ' '; |
|
| 300 | - if ($totalFixable > 0) { |
|
| 301 | - echo '['; |
|
| 302 | - if ($sourceData['fixable'] === true) { |
|
| 303 | - echo 'x'; |
|
| 304 | - $fixableSources++; |
|
| 305 | - } else { |
|
| 306 | - echo ' '; |
|
| 307 | - } |
|
| 308 | - |
|
| 309 | - echo '] '; |
|
| 310 | - } |
|
| 311 | - |
|
| 312 | - echo $source; |
|
| 313 | - if ($totalFixable > 0) { |
|
| 314 | - echo str_repeat(' ', ($width - 18 - strlen($source))); |
|
| 315 | - } else { |
|
| 316 | - echo str_repeat(' ', ($width - 14 - strlen($source))); |
|
| 317 | - } |
|
| 318 | - |
|
| 319 | - echo $line.PHP_EOL; |
|
| 320 | - }//end foreach |
|
| 321 | - }//end if |
|
| 322 | - }//end foreach |
|
| 323 | - |
|
| 324 | - echo str_repeat('-', $width).PHP_EOL; |
|
| 325 | - echo "\033[1m".'A TOTAL OF '.$errorsShown.' SNIFF VIOLATION'; |
|
| 326 | - if ($errorsShown !== 1) { |
|
| 327 | - echo 'S'; |
|
| 328 | - } |
|
| 329 | - |
|
| 330 | - echo ' WERE COMMITTED BY '.count($authorCache).' AUTHOR'; |
|
| 331 | - if (count($authorCache) !== 1) { |
|
| 332 | - echo 'S'; |
|
| 333 | - } |
|
| 334 | - |
|
| 335 | - echo "\033[0m"; |
|
| 336 | - |
|
| 337 | - if ($totalFixable > 0) { |
|
| 338 | - if ($showSources === true) { |
|
| 339 | - echo PHP_EOL.str_repeat('-', $width).PHP_EOL; |
|
| 340 | - echo "\033[1mPHPCBF CAN FIX THE $fixableSources MARKED SOURCES AUTOMATICALLY ($totalFixable VIOLATIONS IN TOTAL)\033[0m"; |
|
| 341 | - } else { |
|
| 342 | - echo PHP_EOL.str_repeat('-', $width).PHP_EOL; |
|
| 343 | - echo "\033[1mPHPCBF CAN FIX $totalFixable OF THESE SNIFF VIOLATIONS AUTOMATICALLY\033[0m"; |
|
| 344 | - } |
|
| 345 | - } |
|
| 346 | - |
|
| 347 | - echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL; |
|
| 348 | - |
|
| 349 | - if ($toScreen === true && $interactive === false) { |
|
| 350 | - Timing::printRunTime(); |
|
| 351 | - } |
|
| 352 | - |
|
| 353 | - }//end generate() |
|
| 354 | - |
|
| 355 | - |
|
| 356 | - /** |
|
| 357 | - * Extract the author from a blame line. |
|
| 358 | - * |
|
| 359 | - * @param string $line Line to parse. |
|
| 360 | - * |
|
| 361 | - * @return mixed string or false if impossible to recover. |
|
| 362 | - */ |
|
| 363 | - abstract protected function getAuthor($line); |
|
| 364 | - |
|
| 365 | - |
|
| 366 | - /** |
|
| 367 | - * Gets the blame output. |
|
| 368 | - * |
|
| 369 | - * @param string $filename File to blame. |
|
| 370 | - * |
|
| 371 | - * @return array |
|
| 372 | - */ |
|
| 373 | - abstract protected function getBlameContent($filename); |
|
| 19 | + /** |
|
| 20 | + * The name of the report we want in the output. |
|
| 21 | + * |
|
| 22 | + * @var string |
|
| 23 | + */ |
|
| 24 | + protected $reportName = 'VERSION CONTROL'; |
|
| 25 | + |
|
| 26 | + |
|
| 27 | + /** |
|
| 28 | + * Generate a partial report for a single processed file. |
|
| 29 | + * |
|
| 30 | + * Function should return TRUE if it printed or stored data about the file |
|
| 31 | + * and FALSE if it ignored the file. Returning TRUE indicates that the file and |
|
| 32 | + * its data should be counted in the grand totals. |
|
| 33 | + * |
|
| 34 | + * @param array $report Prepared report data. |
|
| 35 | + * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on. |
|
| 36 | + * @param bool $showSources Show sources? |
|
| 37 | + * @param int $width Maximum allowed line width. |
|
| 38 | + * |
|
| 39 | + * @return bool |
|
| 40 | + */ |
|
| 41 | + public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) |
|
| 42 | + { |
|
| 43 | + $blames = $this->getBlameContent($report['filename']); |
|
| 44 | + |
|
| 45 | + $authorCache = []; |
|
| 46 | + $praiseCache = []; |
|
| 47 | + $sourceCache = []; |
|
| 48 | + |
|
| 49 | + foreach ($report['messages'] as $line => $lineErrors) { |
|
| 50 | + $author = 'Unknown'; |
|
| 51 | + if (isset($blames[($line - 1)]) === true) { |
|
| 52 | + $blameAuthor = $this->getAuthor($blames[($line - 1)]); |
|
| 53 | + if ($blameAuthor !== false) { |
|
| 54 | + $author = $blameAuthor; |
|
| 55 | + } |
|
| 56 | + } |
|
| 57 | + |
|
| 58 | + if (isset($authorCache[$author]) === false) { |
|
| 59 | + $authorCache[$author] = 0; |
|
| 60 | + $praiseCache[$author] = [ |
|
| 61 | + 'good' => 0, |
|
| 62 | + 'bad' => 0, |
|
| 63 | + ]; |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + $praiseCache[$author]['bad']++; |
|
| 67 | + |
|
| 68 | + foreach ($lineErrors as $column => $colErrors) { |
|
| 69 | + foreach ($colErrors as $error) { |
|
| 70 | + $authorCache[$author]++; |
|
| 71 | + |
|
| 72 | + if ($showSources === true) { |
|
| 73 | + $source = $error['source']; |
|
| 74 | + if (isset($sourceCache[$author][$source]) === false) { |
|
| 75 | + $sourceCache[$author][$source] = [ |
|
| 76 | + 'count' => 1, |
|
| 77 | + 'fixable' => $error['fixable'], |
|
| 78 | + ]; |
|
| 79 | + } else { |
|
| 80 | + $sourceCache[$author][$source]['count']++; |
|
| 81 | + } |
|
| 82 | + } |
|
| 83 | + } |
|
| 84 | + } |
|
| 85 | + |
|
| 86 | + unset($blames[($line - 1)]); |
|
| 87 | + }//end foreach |
|
| 88 | + |
|
| 89 | + // Now go through and give the authors some credit for |
|
| 90 | + // all the lines that do not have errors. |
|
| 91 | + foreach ($blames as $line) { |
|
| 92 | + $author = $this->getAuthor($line); |
|
| 93 | + if ($author === false) { |
|
| 94 | + $author = 'Unknown'; |
|
| 95 | + } |
|
| 96 | + |
|
| 97 | + if (isset($authorCache[$author]) === false) { |
|
| 98 | + // This author doesn't have any errors. |
|
| 99 | + if (PHP_CODESNIFFER_VERBOSITY === 0) { |
|
| 100 | + continue; |
|
| 101 | + } |
|
| 102 | + |
|
| 103 | + $authorCache[$author] = 0; |
|
| 104 | + $praiseCache[$author] = [ |
|
| 105 | + 'good' => 0, |
|
| 106 | + 'bad' => 0, |
|
| 107 | + ]; |
|
| 108 | + } |
|
| 109 | + |
|
| 110 | + $praiseCache[$author]['good']++; |
|
| 111 | + }//end foreach |
|
| 112 | + |
|
| 113 | + foreach ($authorCache as $author => $errors) { |
|
| 114 | + echo "AUTHOR>>$author>>$errors".PHP_EOL; |
|
| 115 | + } |
|
| 116 | + |
|
| 117 | + foreach ($praiseCache as $author => $praise) { |
|
| 118 | + echo "PRAISE>>$author>>".$praise['good'].'>>'.$praise['bad'].PHP_EOL; |
|
| 119 | + } |
|
| 120 | + |
|
| 121 | + foreach ($sourceCache as $author => $sources) { |
|
| 122 | + foreach ($sources as $source => $sourceData) { |
|
| 123 | + $count = $sourceData['count']; |
|
| 124 | + $fixable = (int) $sourceData['fixable']; |
|
| 125 | + echo "SOURCE>>$author>>$source>>$count>>$fixable".PHP_EOL; |
|
| 126 | + } |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + return true; |
|
| 130 | + |
|
| 131 | + }//end generateFileReport() |
|
| 132 | + |
|
| 133 | + |
|
| 134 | + /** |
|
| 135 | + * Prints the author of all errors and warnings, as given by "version control blame". |
|
| 136 | + * |
|
| 137 | + * @param string $cachedData Any partial report data that was returned from |
|
| 138 | + * generateFileReport during the run. |
|
| 139 | + * @param int $totalFiles Total number of files processed during the run. |
|
| 140 | + * @param int $totalErrors Total number of errors found during the run. |
|
| 141 | + * @param int $totalWarnings Total number of warnings found during the run. |
|
| 142 | + * @param int $totalFixable Total number of problems that can be fixed. |
|
| 143 | + * @param bool $showSources Show sources? |
|
| 144 | + * @param int $width Maximum allowed line width. |
|
| 145 | + * @param bool $interactive Are we running in interactive mode? |
|
| 146 | + * @param bool $toScreen Is the report being printed to screen? |
|
| 147 | + * |
|
| 148 | + * @return void |
|
| 149 | + */ |
|
| 150 | + public function generate( |
|
| 151 | + $cachedData, |
|
| 152 | + $totalFiles, |
|
| 153 | + $totalErrors, |
|
| 154 | + $totalWarnings, |
|
| 155 | + $totalFixable, |
|
| 156 | + $showSources=false, |
|
| 157 | + $width=80, |
|
| 158 | + $interactive=false, |
|
| 159 | + $toScreen=true |
|
| 160 | + ) { |
|
| 161 | + $errorsShown = ($totalErrors + $totalWarnings); |
|
| 162 | + if ($errorsShown === 0) { |
|
| 163 | + // Nothing to show. |
|
| 164 | + return; |
|
| 165 | + } |
|
| 166 | + |
|
| 167 | + $lines = explode(PHP_EOL, $cachedData); |
|
| 168 | + array_pop($lines); |
|
| 169 | + |
|
| 170 | + if (empty($lines) === true) { |
|
| 171 | + return; |
|
| 172 | + } |
|
| 173 | + |
|
| 174 | + $authorCache = []; |
|
| 175 | + $praiseCache = []; |
|
| 176 | + $sourceCache = []; |
|
| 177 | + |
|
| 178 | + foreach ($lines as $line) { |
|
| 179 | + $parts = explode('>>', $line); |
|
| 180 | + switch ($parts[0]) { |
|
| 181 | + case 'AUTHOR': |
|
| 182 | + if (isset($authorCache[$parts[1]]) === false) { |
|
| 183 | + $authorCache[$parts[1]] = $parts[2]; |
|
| 184 | + } else { |
|
| 185 | + $authorCache[$parts[1]] += $parts[2]; |
|
| 186 | + } |
|
| 187 | + break; |
|
| 188 | + case 'PRAISE': |
|
| 189 | + if (isset($praiseCache[$parts[1]]) === false) { |
|
| 190 | + $praiseCache[$parts[1]] = [ |
|
| 191 | + 'good' => $parts[2], |
|
| 192 | + 'bad' => $parts[3], |
|
| 193 | + ]; |
|
| 194 | + } else { |
|
| 195 | + $praiseCache[$parts[1]]['good'] += $parts[2]; |
|
| 196 | + $praiseCache[$parts[1]]['bad'] += $parts[3]; |
|
| 197 | + } |
|
| 198 | + break; |
|
| 199 | + case 'SOURCE': |
|
| 200 | + if (isset($praiseCache[$parts[1]]) === false) { |
|
| 201 | + $praiseCache[$parts[1]] = []; |
|
| 202 | + } |
|
| 203 | + |
|
| 204 | + if (isset($sourceCache[$parts[1]][$parts[2]]) === false) { |
|
| 205 | + $sourceCache[$parts[1]][$parts[2]] = [ |
|
| 206 | + 'count' => $parts[3], |
|
| 207 | + 'fixable' => (bool) $parts[4], |
|
| 208 | + ]; |
|
| 209 | + } else { |
|
| 210 | + $sourceCache[$parts[1]][$parts[2]]['count'] += $parts[3]; |
|
| 211 | + } |
|
| 212 | + break; |
|
| 213 | + default: |
|
| 214 | + break; |
|
| 215 | + }//end switch |
|
| 216 | + }//end foreach |
|
| 217 | + |
|
| 218 | + // Make sure the report width isn't too big. |
|
| 219 | + $maxLength = 0; |
|
| 220 | + foreach ($authorCache as $author => $count) { |
|
| 221 | + $maxLength = max($maxLength, strlen($author)); |
|
| 222 | + if ($showSources === true && isset($sourceCache[$author]) === true) { |
|
| 223 | + foreach ($sourceCache[$author] as $source => $sourceData) { |
|
| 224 | + if ($source === 'count') { |
|
| 225 | + continue; |
|
| 226 | + } |
|
| 227 | + |
|
| 228 | + $maxLength = max($maxLength, (strlen($source) + 9)); |
|
| 229 | + } |
|
| 230 | + } |
|
| 231 | + } |
|
| 232 | + |
|
| 233 | + $width = min($width, ($maxLength + 30)); |
|
| 234 | + $width = max($width, 70); |
|
| 235 | + arsort($authorCache); |
|
| 236 | + |
|
| 237 | + echo PHP_EOL."\033[1m".'PHP CODE SNIFFER '.$this->reportName.' BLAME SUMMARY'."\033[0m".PHP_EOL; |
|
| 238 | + echo str_repeat('-', $width).PHP_EOL."\033[1m"; |
|
| 239 | + if ($showSources === true) { |
|
| 240 | + echo 'AUTHOR SOURCE'.str_repeat(' ', ($width - 43)).'(Author %) (Overall %) COUNT'.PHP_EOL; |
|
| 241 | + echo str_repeat('-', $width).PHP_EOL; |
|
| 242 | + } else { |
|
| 243 | + echo 'AUTHOR'.str_repeat(' ', ($width - 34)).'(Author %) (Overall %) COUNT'.PHP_EOL; |
|
| 244 | + echo str_repeat('-', $width).PHP_EOL; |
|
| 245 | + } |
|
| 246 | + |
|
| 247 | + echo "\033[0m"; |
|
| 248 | + |
|
| 249 | + if ($showSources === true) { |
|
| 250 | + $maxSniffWidth = ($width - 15); |
|
| 251 | + |
|
| 252 | + if ($totalFixable > 0) { |
|
| 253 | + $maxSniffWidth -= 4; |
|
| 254 | + } |
|
| 255 | + } |
|
| 256 | + |
|
| 257 | + $fixableSources = 0; |
|
| 258 | + |
|
| 259 | + foreach ($authorCache as $author => $count) { |
|
| 260 | + if ($praiseCache[$author]['good'] === 0) { |
|
| 261 | + $percent = 0; |
|
| 262 | + } else { |
|
| 263 | + $total = ($praiseCache[$author]['bad'] + $praiseCache[$author]['good']); |
|
| 264 | + $percent = round(($praiseCache[$author]['bad'] / $total * 100), 2); |
|
| 265 | + } |
|
| 266 | + |
|
| 267 | + $overallPercent = '('.round((($count / $errorsShown) * 100), 2).')'; |
|
| 268 | + $authorPercent = '('.$percent.')'; |
|
| 269 | + $line = str_repeat(' ', (6 - strlen($count))).$count; |
|
| 270 | + $line = str_repeat(' ', (12 - strlen($overallPercent))).$overallPercent.$line; |
|
| 271 | + $line = str_repeat(' ', (11 - strlen($authorPercent))).$authorPercent.$line; |
|
| 272 | + $line = $author.str_repeat(' ', ($width - strlen($author) - strlen($line))).$line; |
|
| 273 | + |
|
| 274 | + if ($showSources === true) { |
|
| 275 | + $line = "\033[1m$line\033[0m"; |
|
| 276 | + } |
|
| 277 | + |
|
| 278 | + echo $line.PHP_EOL; |
|
| 279 | + |
|
| 280 | + if ($showSources === true && isset($sourceCache[$author]) === true) { |
|
| 281 | + $errors = $sourceCache[$author]; |
|
| 282 | + asort($errors); |
|
| 283 | + $errors = array_reverse($errors); |
|
| 284 | + |
|
| 285 | + foreach ($errors as $source => $sourceData) { |
|
| 286 | + if ($source === 'count') { |
|
| 287 | + continue; |
|
| 288 | + } |
|
| 289 | + |
|
| 290 | + $count = $sourceData['count']; |
|
| 291 | + |
|
| 292 | + $srcLength = strlen($source); |
|
| 293 | + if ($srcLength > $maxSniffWidth) { |
|
| 294 | + $source = substr($source, 0, $maxSniffWidth); |
|
| 295 | + } |
|
| 296 | + |
|
| 297 | + $line = str_repeat(' ', (5 - strlen($count))).$count; |
|
| 298 | + |
|
| 299 | + echo ' '; |
|
| 300 | + if ($totalFixable > 0) { |
|
| 301 | + echo '['; |
|
| 302 | + if ($sourceData['fixable'] === true) { |
|
| 303 | + echo 'x'; |
|
| 304 | + $fixableSources++; |
|
| 305 | + } else { |
|
| 306 | + echo ' '; |
|
| 307 | + } |
|
| 308 | + |
|
| 309 | + echo '] '; |
|
| 310 | + } |
|
| 311 | + |
|
| 312 | + echo $source; |
|
| 313 | + if ($totalFixable > 0) { |
|
| 314 | + echo str_repeat(' ', ($width - 18 - strlen($source))); |
|
| 315 | + } else { |
|
| 316 | + echo str_repeat(' ', ($width - 14 - strlen($source))); |
|
| 317 | + } |
|
| 318 | + |
|
| 319 | + echo $line.PHP_EOL; |
|
| 320 | + }//end foreach |
|
| 321 | + }//end if |
|
| 322 | + }//end foreach |
|
| 323 | + |
|
| 324 | + echo str_repeat('-', $width).PHP_EOL; |
|
| 325 | + echo "\033[1m".'A TOTAL OF '.$errorsShown.' SNIFF VIOLATION'; |
|
| 326 | + if ($errorsShown !== 1) { |
|
| 327 | + echo 'S'; |
|
| 328 | + } |
|
| 329 | + |
|
| 330 | + echo ' WERE COMMITTED BY '.count($authorCache).' AUTHOR'; |
|
| 331 | + if (count($authorCache) !== 1) { |
|
| 332 | + echo 'S'; |
|
| 333 | + } |
|
| 334 | + |
|
| 335 | + echo "\033[0m"; |
|
| 336 | + |
|
| 337 | + if ($totalFixable > 0) { |
|
| 338 | + if ($showSources === true) { |
|
| 339 | + echo PHP_EOL.str_repeat('-', $width).PHP_EOL; |
|
| 340 | + echo "\033[1mPHPCBF CAN FIX THE $fixableSources MARKED SOURCES AUTOMATICALLY ($totalFixable VIOLATIONS IN TOTAL)\033[0m"; |
|
| 341 | + } else { |
|
| 342 | + echo PHP_EOL.str_repeat('-', $width).PHP_EOL; |
|
| 343 | + echo "\033[1mPHPCBF CAN FIX $totalFixable OF THESE SNIFF VIOLATIONS AUTOMATICALLY\033[0m"; |
|
| 344 | + } |
|
| 345 | + } |
|
| 346 | + |
|
| 347 | + echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL; |
|
| 348 | + |
|
| 349 | + if ($toScreen === true && $interactive === false) { |
|
| 350 | + Timing::printRunTime(); |
|
| 351 | + } |
|
| 352 | + |
|
| 353 | + }//end generate() |
|
| 354 | + |
|
| 355 | + |
|
| 356 | + /** |
|
| 357 | + * Extract the author from a blame line. |
|
| 358 | + * |
|
| 359 | + * @param string $line Line to parse. |
|
| 360 | + * |
|
| 361 | + * @return mixed string or false if impossible to recover. |
|
| 362 | + */ |
|
| 363 | + abstract protected function getAuthor($line); |
|
| 364 | + |
|
| 365 | + |
|
| 366 | + /** |
|
| 367 | + * Gets the blame output. |
|
| 368 | + * |
|
| 369 | + * @param string $filename File to blame. |
|
| 370 | + * |
|
| 371 | + * @return array |
|
| 372 | + */ |
|
| 373 | + abstract protected function getBlameContent($filename); |
|
| 374 | 374 | |
| 375 | 375 | |
| 376 | 376 | }//end class |
@@ -178,40 +178,40 @@ |
||
| 178 | 178 | foreach ($lines as $line) { |
| 179 | 179 | $parts = explode('>>', $line); |
| 180 | 180 | switch ($parts[0]) { |
| 181 | - case 'AUTHOR': |
|
| 182 | - if (isset($authorCache[$parts[1]]) === false) { |
|
| 183 | - $authorCache[$parts[1]] = $parts[2]; |
|
| 184 | - } else { |
|
| 185 | - $authorCache[$parts[1]] += $parts[2]; |
|
| 186 | - } |
|
| 187 | - break; |
|
| 188 | - case 'PRAISE': |
|
| 189 | - if (isset($praiseCache[$parts[1]]) === false) { |
|
| 190 | - $praiseCache[$parts[1]] = [ |
|
| 191 | - 'good' => $parts[2], |
|
| 192 | - 'bad' => $parts[3], |
|
| 193 | - ]; |
|
| 194 | - } else { |
|
| 195 | - $praiseCache[$parts[1]]['good'] += $parts[2]; |
|
| 196 | - $praiseCache[$parts[1]]['bad'] += $parts[3]; |
|
| 197 | - } |
|
| 198 | - break; |
|
| 199 | - case 'SOURCE': |
|
| 200 | - if (isset($praiseCache[$parts[1]]) === false) { |
|
| 201 | - $praiseCache[$parts[1]] = []; |
|
| 202 | - } |
|
| 203 | - |
|
| 204 | - if (isset($sourceCache[$parts[1]][$parts[2]]) === false) { |
|
| 205 | - $sourceCache[$parts[1]][$parts[2]] = [ |
|
| 206 | - 'count' => $parts[3], |
|
| 207 | - 'fixable' => (bool) $parts[4], |
|
| 208 | - ]; |
|
| 209 | - } else { |
|
| 210 | - $sourceCache[$parts[1]][$parts[2]]['count'] += $parts[3]; |
|
| 211 | - } |
|
| 212 | - break; |
|
| 213 | - default: |
|
| 214 | - break; |
|
| 181 | + case 'AUTHOR': |
|
| 182 | + if (isset($authorCache[$parts[1]]) === false) { |
|
| 183 | + $authorCache[$parts[1]] = $parts[2]; |
|
| 184 | + } else { |
|
| 185 | + $authorCache[$parts[1]] += $parts[2]; |
|
| 186 | + } |
|
| 187 | + break; |
|
| 188 | + case 'PRAISE': |
|
| 189 | + if (isset($praiseCache[$parts[1]]) === false) { |
|
| 190 | + $praiseCache[$parts[1]] = [ |
|
| 191 | + 'good' => $parts[2], |
|
| 192 | + 'bad' => $parts[3], |
|
| 193 | + ]; |
|
| 194 | + } else { |
|
| 195 | + $praiseCache[$parts[1]]['good'] += $parts[2]; |
|
| 196 | + $praiseCache[$parts[1]]['bad'] += $parts[3]; |
|
| 197 | + } |
|
| 198 | + break; |
|
| 199 | + case 'SOURCE': |
|
| 200 | + if (isset($praiseCache[$parts[1]]) === false) { |
|
| 201 | + $praiseCache[$parts[1]] = []; |
|
| 202 | + } |
|
| 203 | + |
|
| 204 | + if (isset($sourceCache[$parts[1]][$parts[2]]) === false) { |
|
| 205 | + $sourceCache[$parts[1]][$parts[2]] = [ |
|
| 206 | + 'count' => $parts[3], |
|
| 207 | + 'fixable' => (bool) $parts[4], |
|
| 208 | + ]; |
|
| 209 | + } else { |
|
| 210 | + $sourceCache[$parts[1]][$parts[2]]['count'] += $parts[3]; |
|
| 211 | + } |
|
| 212 | + break; |
|
| 213 | + default: |
|
| 214 | + break; |
|
| 215 | 215 | }//end switch |
| 216 | 216 | }//end foreach |
| 217 | 217 | |
@@ -38,7 +38,7 @@ discard block |
||
| 38 | 38 | * |
| 39 | 39 | * @return bool |
| 40 | 40 | */ |
| 41 | - public function generateFileReport($report, File $phpcsFile, $showSources=false, $width=80) |
|
| 41 | + public function generateFileReport($report, File $phpcsFile, $showSources = false, $width = 80) |
|
| 42 | 42 | { |
| 43 | 43 | $blames = $this->getBlameContent($report['filename']); |
| 44 | 44 | |
@@ -153,10 +153,10 @@ discard block |
||
| 153 | 153 | $totalErrors, |
| 154 | 154 | $totalWarnings, |
| 155 | 155 | $totalFixable, |
| 156 | - $showSources=false, |
|
| 157 | - $width=80, |
|
| 158 | - $interactive=false, |
|
| 159 | - $toScreen=true |
|
| 156 | + $showSources = false, |
|
| 157 | + $width = 80, |
|
| 158 | + $interactive = false, |
|
| 159 | + $toScreen = true |
|
| 160 | 160 | ) { |
| 161 | 161 | $errorsShown = ($totalErrors + $totalWarnings); |
| 162 | 162 | if ($errorsShown === 0) { |
@@ -269,14 +269,14 @@ |
||
| 269 | 269 | foreach ($diffLines as $line) { |
| 270 | 270 | if (isset($line[0]) === true) { |
| 271 | 271 | switch ($line[0]) { |
| 272 | - case '-': |
|
| 273 | - $diff[] = "\033[31m$line\033[0m"; |
|
| 274 | - break; |
|
| 275 | - case '+': |
|
| 276 | - $diff[] = "\033[32m$line\033[0m"; |
|
| 277 | - break; |
|
| 278 | - default: |
|
| 279 | - $diff[] = $line; |
|
| 272 | + case '-': |
|
| 273 | + $diff[] = "\033[31m$line\033[0m"; |
|
| 274 | + break; |
|
| 275 | + case '+': |
|
| 276 | + $diff[] = "\033[32m$line\033[0m"; |
|
| 277 | + break; |
|
| 278 | + default: |
|
| 279 | + $diff[] = $line; |
|
| 280 | 280 | } |
| 281 | 281 | } |
| 282 | 282 | } |
@@ -224,7 +224,7 @@ discard block |
||
| 224 | 224 | * |
| 225 | 225 | * @return string |
| 226 | 226 | */ |
| 227 | - public function generateDiff($filePath=null, $colors=true) |
|
| 227 | + public function generateDiff($filePath = null, $colors = true) |
|
| 228 | 228 | { |
| 229 | 229 | if ($filePath === null) { |
| 230 | 230 | $filePath = $this->currentFile->getFilename(); |
@@ -643,7 +643,7 @@ discard block |
||
| 643 | 643 | * |
| 644 | 644 | * @return bool If the change was accepted. |
| 645 | 645 | */ |
| 646 | - public function substrToken($stackPtr, $start, $length=null) |
|
| 646 | + public function substrToken($stackPtr, $start, $length = null) |
|
| 647 | 647 | { |
| 648 | 648 | $current = $this->getTokenContent($stackPtr); |
| 649 | 649 | |
@@ -18,789 +18,789 @@ |
||
| 18 | 18 | class Fixer |
| 19 | 19 | { |
| 20 | 20 | |
| 21 | - /** |
|
| 22 | - * Is the fixer enabled and fixing a file? |
|
| 23 | - * |
|
| 24 | - * Sniffs should check this value to ensure they are not |
|
| 25 | - * doing extra processing to prepare for a fix when fixing is |
|
| 26 | - * not required. |
|
| 27 | - * |
|
| 28 | - * @var boolean |
|
| 29 | - */ |
|
| 30 | - public $enabled = false; |
|
| 31 | - |
|
| 32 | - /** |
|
| 33 | - * The number of times we have looped over a file. |
|
| 34 | - * |
|
| 35 | - * @var integer |
|
| 36 | - */ |
|
| 37 | - public $loops = 0; |
|
| 38 | - |
|
| 39 | - /** |
|
| 40 | - * The file being fixed. |
|
| 41 | - * |
|
| 42 | - * @var \PHP_CodeSniffer\Files\File |
|
| 43 | - */ |
|
| 44 | - private $currentFile = null; |
|
| 45 | - |
|
| 46 | - /** |
|
| 47 | - * The list of tokens that make up the file contents. |
|
| 48 | - * |
|
| 49 | - * This is a simplified list which just contains the token content and nothing |
|
| 50 | - * else. This is the array that is updated as fixes are made, not the file's |
|
| 51 | - * token array. Imploding this array will give you the file content back. |
|
| 52 | - * |
|
| 53 | - * @var array<int, string> |
|
| 54 | - */ |
|
| 55 | - private $tokens = []; |
|
| 56 | - |
|
| 57 | - /** |
|
| 58 | - * A list of tokens that have already been fixed. |
|
| 59 | - * |
|
| 60 | - * We don't allow the same token to be fixed more than once each time |
|
| 61 | - * through a file as this can easily cause conflicts between sniffs. |
|
| 62 | - * |
|
| 63 | - * @var int[] |
|
| 64 | - */ |
|
| 65 | - private $fixedTokens = []; |
|
| 66 | - |
|
| 67 | - /** |
|
| 68 | - * The last value of each fixed token. |
|
| 69 | - * |
|
| 70 | - * If a token is being "fixed" back to its last value, the fix is |
|
| 71 | - * probably conflicting with another. |
|
| 72 | - * |
|
| 73 | - * @var array<int, string> |
|
| 74 | - */ |
|
| 75 | - private $oldTokenValues = []; |
|
| 76 | - |
|
| 77 | - /** |
|
| 78 | - * A list of tokens that have been fixed during a changeset. |
|
| 79 | - * |
|
| 80 | - * All changes in changeset must be able to be applied, or else |
|
| 81 | - * the entire changeset is rejected. |
|
| 82 | - * |
|
| 83 | - * @var array |
|
| 84 | - */ |
|
| 85 | - private $changeset = []; |
|
| 86 | - |
|
| 87 | - /** |
|
| 88 | - * Is there an open changeset. |
|
| 89 | - * |
|
| 90 | - * @var boolean |
|
| 91 | - */ |
|
| 92 | - private $inChangeset = false; |
|
| 93 | - |
|
| 94 | - /** |
|
| 95 | - * Is the current fixing loop in conflict? |
|
| 96 | - * |
|
| 97 | - * @var boolean |
|
| 98 | - */ |
|
| 99 | - private $inConflict = false; |
|
| 100 | - |
|
| 101 | - /** |
|
| 102 | - * The number of fixes that have been performed. |
|
| 103 | - * |
|
| 104 | - * @var integer |
|
| 105 | - */ |
|
| 106 | - private $numFixes = 0; |
|
| 107 | - |
|
| 108 | - |
|
| 109 | - /** |
|
| 110 | - * Starts fixing a new file. |
|
| 111 | - * |
|
| 112 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being fixed. |
|
| 113 | - * |
|
| 114 | - * @return void |
|
| 115 | - */ |
|
| 116 | - public function startFile(File $phpcsFile) |
|
| 117 | - { |
|
| 118 | - $this->currentFile = $phpcsFile; |
|
| 119 | - $this->numFixes = 0; |
|
| 120 | - $this->fixedTokens = []; |
|
| 121 | - |
|
| 122 | - $tokens = $phpcsFile->getTokens(); |
|
| 123 | - $this->tokens = []; |
|
| 124 | - foreach ($tokens as $index => $token) { |
|
| 125 | - if (isset($token['orig_content']) === true) { |
|
| 126 | - $this->tokens[$index] = $token['orig_content']; |
|
| 127 | - } else { |
|
| 128 | - $this->tokens[$index] = $token['content']; |
|
| 129 | - } |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - }//end startFile() |
|
| 133 | - |
|
| 134 | - |
|
| 135 | - /** |
|
| 136 | - * Attempt to fix the file by processing it until no fixes are made. |
|
| 137 | - * |
|
| 138 | - * @return boolean |
|
| 139 | - */ |
|
| 140 | - public function fixFile() |
|
| 141 | - { |
|
| 142 | - $fixable = $this->currentFile->getFixableCount(); |
|
| 143 | - if ($fixable === 0) { |
|
| 144 | - // Nothing to fix. |
|
| 145 | - return false; |
|
| 146 | - } |
|
| 147 | - |
|
| 148 | - $this->enabled = true; |
|
| 149 | - |
|
| 150 | - $this->loops = 0; |
|
| 151 | - while ($this->loops < 50) { |
|
| 152 | - ob_start(); |
|
| 153 | - |
|
| 154 | - // Only needed once file content has changed. |
|
| 155 | - $contents = $this->getContents(); |
|
| 156 | - |
|
| 157 | - if (PHP_CODESNIFFER_VERBOSITY > 2) { |
|
| 158 | - @ob_end_clean(); |
|
| 159 | - echo '---START FILE CONTENT---'.PHP_EOL; |
|
| 160 | - $lines = explode($this->currentFile->eolChar, $contents); |
|
| 161 | - $max = strlen(count($lines)); |
|
| 162 | - foreach ($lines as $lineNum => $line) { |
|
| 163 | - $lineNum++; |
|
| 164 | - echo str_pad($lineNum, $max, ' ', STR_PAD_LEFT).'|'.$line.PHP_EOL; |
|
| 165 | - } |
|
| 166 | - |
|
| 167 | - echo '--- END FILE CONTENT ---'.PHP_EOL; |
|
| 168 | - ob_start(); |
|
| 169 | - } |
|
| 170 | - |
|
| 171 | - $this->inConflict = false; |
|
| 172 | - $this->currentFile->ruleset->populateTokenListeners(); |
|
| 173 | - $this->currentFile->setContent($contents); |
|
| 174 | - $this->currentFile->process(); |
|
| 175 | - ob_end_clean(); |
|
| 176 | - |
|
| 177 | - $this->loops++; |
|
| 178 | - |
|
| 179 | - if (PHP_CODESNIFFER_CBF === true && PHP_CODESNIFFER_VERBOSITY > 0) { |
|
| 180 | - echo "\r".str_repeat(' ', 80)."\r"; |
|
| 181 | - echo "\t=> Fixing file: $this->numFixes/$fixable violations remaining [made $this->loops pass"; |
|
| 182 | - if ($this->loops > 1) { |
|
| 183 | - echo 'es'; |
|
| 184 | - } |
|
| 185 | - |
|
| 186 | - echo ']... '; |
|
| 187 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 188 | - echo PHP_EOL; |
|
| 189 | - } |
|
| 190 | - } |
|
| 191 | - |
|
| 192 | - if ($this->numFixes === 0 && $this->inConflict === false) { |
|
| 193 | - // Nothing left to do. |
|
| 194 | - break; |
|
| 195 | - } else if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 196 | - echo "\t* fixed $this->numFixes violations, starting loop ".($this->loops + 1).' *'.PHP_EOL; |
|
| 197 | - } |
|
| 198 | - }//end while |
|
| 199 | - |
|
| 200 | - $this->enabled = false; |
|
| 201 | - |
|
| 202 | - if ($this->numFixes > 0) { |
|
| 203 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 204 | - if (ob_get_level() > 0) { |
|
| 205 | - ob_end_clean(); |
|
| 206 | - } |
|
| 207 | - |
|
| 208 | - echo "\t*** Reached maximum number of loops with $this->numFixes violations left unfixed ***".PHP_EOL; |
|
| 209 | - ob_start(); |
|
| 210 | - } |
|
| 211 | - |
|
| 212 | - return false; |
|
| 213 | - } |
|
| 214 | - |
|
| 215 | - return true; |
|
| 216 | - |
|
| 217 | - }//end fixFile() |
|
| 218 | - |
|
| 219 | - |
|
| 220 | - /** |
|
| 221 | - * Generates a text diff of the original file and the new content. |
|
| 222 | - * |
|
| 223 | - * @param string $filePath Optional file path to diff the file against. |
|
| 224 | - * If not specified, the original version of the |
|
| 225 | - * file will be used. |
|
| 226 | - * @param boolean $colors Print coloured output or not. |
|
| 227 | - * |
|
| 228 | - * @return string |
|
| 229 | - */ |
|
| 230 | - public function generateDiff($filePath=null, $colors=true) |
|
| 231 | - { |
|
| 232 | - if ($filePath === null) { |
|
| 233 | - $filePath = $this->currentFile->getFilename(); |
|
| 234 | - } |
|
| 235 | - |
|
| 236 | - $cwd = getcwd().DIRECTORY_SEPARATOR; |
|
| 237 | - if (strpos($filePath, $cwd) === 0) { |
|
| 238 | - $filename = substr($filePath, strlen($cwd)); |
|
| 239 | - } else { |
|
| 240 | - $filename = $filePath; |
|
| 241 | - } |
|
| 242 | - |
|
| 243 | - $contents = $this->getContents(); |
|
| 244 | - |
|
| 245 | - $tempName = tempnam(sys_get_temp_dir(), 'phpcs-fixer'); |
|
| 246 | - $fixedFile = fopen($tempName, 'w'); |
|
| 247 | - fwrite($fixedFile, $contents); |
|
| 248 | - |
|
| 249 | - // We must use something like shell_exec() because whitespace at the end |
|
| 250 | - // of lines is critical to diff files. |
|
| 251 | - $filename = escapeshellarg($filename); |
|
| 252 | - $cmd = "diff -u -L$filename -LPHP_CodeSniffer $filename \"$tempName\""; |
|
| 253 | - |
|
| 254 | - $diff = shell_exec($cmd); |
|
| 255 | - |
|
| 256 | - fclose($fixedFile); |
|
| 257 | - if (is_file($tempName) === true) { |
|
| 258 | - unlink($tempName); |
|
| 259 | - } |
|
| 260 | - |
|
| 261 | - if ($diff === null) { |
|
| 262 | - return ''; |
|
| 263 | - } |
|
| 264 | - |
|
| 265 | - if ($colors === false) { |
|
| 266 | - return $diff; |
|
| 267 | - } |
|
| 268 | - |
|
| 269 | - $diffLines = explode(PHP_EOL, $diff); |
|
| 270 | - if (count($diffLines) === 1) { |
|
| 271 | - // Seems to be required for cygwin. |
|
| 272 | - $diffLines = explode("\n", $diff); |
|
| 273 | - } |
|
| 274 | - |
|
| 275 | - $diff = []; |
|
| 276 | - foreach ($diffLines as $line) { |
|
| 277 | - if (isset($line[0]) === true) { |
|
| 278 | - switch ($line[0]) { |
|
| 279 | - case '-': |
|
| 280 | - $diff[] = "\033[31m$line\033[0m"; |
|
| 281 | - break; |
|
| 282 | - case '+': |
|
| 283 | - $diff[] = "\033[32m$line\033[0m"; |
|
| 284 | - break; |
|
| 285 | - default: |
|
| 286 | - $diff[] = $line; |
|
| 287 | - } |
|
| 288 | - } |
|
| 289 | - } |
|
| 290 | - |
|
| 291 | - $diff = implode(PHP_EOL, $diff); |
|
| 292 | - |
|
| 293 | - return $diff; |
|
| 294 | - |
|
| 295 | - }//end generateDiff() |
|
| 296 | - |
|
| 297 | - |
|
| 298 | - /** |
|
| 299 | - * Get a count of fixes that have been performed on the file. |
|
| 300 | - * |
|
| 301 | - * This value is reset every time a new file is started, or an existing |
|
| 302 | - * file is restarted. |
|
| 303 | - * |
|
| 304 | - * @return int |
|
| 305 | - */ |
|
| 306 | - public function getFixCount() |
|
| 307 | - { |
|
| 308 | - return $this->numFixes; |
|
| 309 | - |
|
| 310 | - }//end getFixCount() |
|
| 311 | - |
|
| 312 | - |
|
| 313 | - /** |
|
| 314 | - * Get the current content of the file, as a string. |
|
| 315 | - * |
|
| 316 | - * @return string |
|
| 317 | - */ |
|
| 318 | - public function getContents() |
|
| 319 | - { |
|
| 320 | - $contents = implode($this->tokens); |
|
| 321 | - return $contents; |
|
| 322 | - |
|
| 323 | - }//end getContents() |
|
| 324 | - |
|
| 325 | - |
|
| 326 | - /** |
|
| 327 | - * Get the current fixed content of a token. |
|
| 328 | - * |
|
| 329 | - * This function takes changesets into account so should be used |
|
| 330 | - * instead of directly accessing the token array. |
|
| 331 | - * |
|
| 332 | - * @param int $stackPtr The position of the token in the token stack. |
|
| 333 | - * |
|
| 334 | - * @return string |
|
| 335 | - */ |
|
| 336 | - public function getTokenContent($stackPtr) |
|
| 337 | - { |
|
| 338 | - if ($this->inChangeset === true |
|
| 339 | - && isset($this->changeset[$stackPtr]) === true |
|
| 340 | - ) { |
|
| 341 | - return $this->changeset[$stackPtr]; |
|
| 342 | - } else { |
|
| 343 | - return $this->tokens[$stackPtr]; |
|
| 344 | - } |
|
| 345 | - |
|
| 346 | - }//end getTokenContent() |
|
| 347 | - |
|
| 348 | - |
|
| 349 | - /** |
|
| 350 | - * Start recording actions for a changeset. |
|
| 351 | - * |
|
| 352 | - * @return void |
|
| 353 | - */ |
|
| 354 | - public function beginChangeset() |
|
| 355 | - { |
|
| 356 | - if ($this->inConflict === true) { |
|
| 357 | - return false; |
|
| 358 | - } |
|
| 359 | - |
|
| 360 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 361 | - $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
|
| 362 | - if ($bt[1]['class'] === __CLASS__) { |
|
| 363 | - $sniff = 'Fixer'; |
|
| 364 | - } else { |
|
| 365 | - $sniff = Util\Common::getSniffCode($bt[1]['class']); |
|
| 366 | - } |
|
| 367 | - |
|
| 368 | - $line = $bt[0]['line']; |
|
| 369 | - |
|
| 370 | - @ob_end_clean(); |
|
| 371 | - echo "\t=> Changeset started by $sniff:$line".PHP_EOL; |
|
| 372 | - ob_start(); |
|
| 373 | - } |
|
| 374 | - |
|
| 375 | - $this->changeset = []; |
|
| 376 | - $this->inChangeset = true; |
|
| 377 | - |
|
| 378 | - }//end beginChangeset() |
|
| 379 | - |
|
| 380 | - |
|
| 381 | - /** |
|
| 382 | - * Stop recording actions for a changeset, and apply logged changes. |
|
| 383 | - * |
|
| 384 | - * @return boolean |
|
| 385 | - */ |
|
| 386 | - public function endChangeset() |
|
| 387 | - { |
|
| 388 | - if ($this->inConflict === true) { |
|
| 389 | - return false; |
|
| 390 | - } |
|
| 391 | - |
|
| 392 | - $this->inChangeset = false; |
|
| 393 | - |
|
| 394 | - $success = true; |
|
| 395 | - $applied = []; |
|
| 396 | - foreach ($this->changeset as $stackPtr => $content) { |
|
| 397 | - $success = $this->replaceToken($stackPtr, $content); |
|
| 398 | - if ($success === false) { |
|
| 399 | - break; |
|
| 400 | - } else { |
|
| 401 | - $applied[] = $stackPtr; |
|
| 402 | - } |
|
| 403 | - } |
|
| 404 | - |
|
| 405 | - if ($success === false) { |
|
| 406 | - // Rolling back all changes. |
|
| 407 | - foreach ($applied as $stackPtr) { |
|
| 408 | - $this->revertToken($stackPtr); |
|
| 409 | - } |
|
| 410 | - |
|
| 411 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 412 | - @ob_end_clean(); |
|
| 413 | - echo "\t=> Changeset failed to apply".PHP_EOL; |
|
| 414 | - ob_start(); |
|
| 415 | - } |
|
| 416 | - } else if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 417 | - $fixes = count($this->changeset); |
|
| 418 | - @ob_end_clean(); |
|
| 419 | - echo "\t=> Changeset ended: $fixes changes applied".PHP_EOL; |
|
| 420 | - ob_start(); |
|
| 421 | - } |
|
| 422 | - |
|
| 423 | - $this->changeset = []; |
|
| 424 | - return true; |
|
| 425 | - |
|
| 426 | - }//end endChangeset() |
|
| 427 | - |
|
| 428 | - |
|
| 429 | - /** |
|
| 430 | - * Stop recording actions for a changeset, and discard logged changes. |
|
| 431 | - * |
|
| 432 | - * @return void |
|
| 433 | - */ |
|
| 434 | - public function rollbackChangeset() |
|
| 435 | - { |
|
| 436 | - $this->inChangeset = false; |
|
| 437 | - $this->inConflict = false; |
|
| 438 | - |
|
| 439 | - if (empty($this->changeset) === false) { |
|
| 440 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 441 | - $bt = debug_backtrace(); |
|
| 442 | - if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') { |
|
| 443 | - $sniff = $bt[2]['class']; |
|
| 444 | - $line = $bt[1]['line']; |
|
| 445 | - } else { |
|
| 446 | - $sniff = $bt[1]['class']; |
|
| 447 | - $line = $bt[0]['line']; |
|
| 448 | - } |
|
| 449 | - |
|
| 450 | - $sniff = Util\Common::getSniffCode($sniff); |
|
| 451 | - |
|
| 452 | - $numChanges = count($this->changeset); |
|
| 453 | - |
|
| 454 | - @ob_end_clean(); |
|
| 455 | - echo "\t\tR: $sniff:$line rolled back the changeset ($numChanges changes)".PHP_EOL; |
|
| 456 | - echo "\t=> Changeset rolled back".PHP_EOL; |
|
| 457 | - ob_start(); |
|
| 458 | - } |
|
| 459 | - |
|
| 460 | - $this->changeset = []; |
|
| 461 | - }//end if |
|
| 462 | - |
|
| 463 | - }//end rollbackChangeset() |
|
| 464 | - |
|
| 465 | - |
|
| 466 | - /** |
|
| 467 | - * Replace the entire contents of a token. |
|
| 468 | - * |
|
| 469 | - * @param int $stackPtr The position of the token in the token stack. |
|
| 470 | - * @param string $content The new content of the token. |
|
| 471 | - * |
|
| 472 | - * @return bool If the change was accepted. |
|
| 473 | - */ |
|
| 474 | - public function replaceToken($stackPtr, $content) |
|
| 475 | - { |
|
| 476 | - if ($this->inConflict === true) { |
|
| 477 | - return false; |
|
| 478 | - } |
|
| 479 | - |
|
| 480 | - if ($this->inChangeset === false |
|
| 481 | - && isset($this->fixedTokens[$stackPtr]) === true |
|
| 482 | - ) { |
|
| 483 | - $indent = "\t"; |
|
| 484 | - if (empty($this->changeset) === false) { |
|
| 485 | - $indent .= "\t"; |
|
| 486 | - } |
|
| 487 | - |
|
| 488 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 489 | - @ob_end_clean(); |
|
| 490 | - echo "$indent* token $stackPtr has already been modified, skipping *".PHP_EOL; |
|
| 491 | - ob_start(); |
|
| 492 | - } |
|
| 493 | - |
|
| 494 | - return false; |
|
| 495 | - } |
|
| 496 | - |
|
| 497 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 498 | - $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
|
| 499 | - if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') { |
|
| 500 | - $sniff = $bt[2]['class']; |
|
| 501 | - $line = $bt[1]['line']; |
|
| 502 | - } else { |
|
| 503 | - $sniff = $bt[1]['class']; |
|
| 504 | - $line = $bt[0]['line']; |
|
| 505 | - } |
|
| 506 | - |
|
| 507 | - $sniff = Util\Common::getSniffCode($sniff); |
|
| 508 | - |
|
| 509 | - $tokens = $this->currentFile->getTokens(); |
|
| 510 | - $type = $tokens[$stackPtr]['type']; |
|
| 511 | - $tokenLine = $tokens[$stackPtr]['line']; |
|
| 512 | - $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]); |
|
| 513 | - $newContent = Common::prepareForOutput($content); |
|
| 514 | - if (trim($this->tokens[$stackPtr]) === '' && isset($this->tokens[($stackPtr + 1)]) === true) { |
|
| 515 | - // Add some context for whitespace only changes. |
|
| 516 | - $append = Common::prepareForOutput($this->tokens[($stackPtr + 1)]); |
|
| 517 | - $oldContent .= $append; |
|
| 518 | - $newContent .= $append; |
|
| 519 | - } |
|
| 520 | - }//end if |
|
| 521 | - |
|
| 522 | - if ($this->inChangeset === true) { |
|
| 523 | - $this->changeset[$stackPtr] = $content; |
|
| 524 | - |
|
| 525 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 526 | - @ob_end_clean(); |
|
| 527 | - echo "\t\tQ: $sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL; |
|
| 528 | - ob_start(); |
|
| 529 | - } |
|
| 530 | - |
|
| 531 | - return true; |
|
| 532 | - } |
|
| 533 | - |
|
| 534 | - if (isset($this->oldTokenValues[$stackPtr]) === false) { |
|
| 535 | - $this->oldTokenValues[$stackPtr] = [ |
|
| 536 | - 'curr' => $content, |
|
| 537 | - 'prev' => $this->tokens[$stackPtr], |
|
| 538 | - 'loop' => $this->loops, |
|
| 539 | - ]; |
|
| 540 | - } else { |
|
| 541 | - if ($this->oldTokenValues[$stackPtr]['prev'] === $content |
|
| 542 | - && $this->oldTokenValues[$stackPtr]['loop'] === ($this->loops - 1) |
|
| 543 | - ) { |
|
| 544 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 545 | - $indent = "\t"; |
|
| 546 | - if (empty($this->changeset) === false) { |
|
| 547 | - $indent .= "\t"; |
|
| 548 | - } |
|
| 549 | - |
|
| 550 | - $loop = $this->oldTokenValues[$stackPtr]['loop']; |
|
| 551 | - |
|
| 552 | - @ob_end_clean(); |
|
| 553 | - echo "$indent**** $sniff:$line has possible conflict with another sniff on loop $loop; caused by the following change ****".PHP_EOL; |
|
| 554 | - echo "$indent**** replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\" ****".PHP_EOL; |
|
| 555 | - } |
|
| 556 | - |
|
| 557 | - if ($this->oldTokenValues[$stackPtr]['loop'] >= ($this->loops - 1)) { |
|
| 558 | - $this->inConflict = true; |
|
| 559 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 560 | - echo "$indent**** ignoring all changes until next loop ****".PHP_EOL; |
|
| 561 | - } |
|
| 562 | - } |
|
| 563 | - |
|
| 564 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 565 | - ob_start(); |
|
| 566 | - } |
|
| 567 | - |
|
| 568 | - return false; |
|
| 569 | - }//end if |
|
| 570 | - |
|
| 571 | - $this->oldTokenValues[$stackPtr]['prev'] = $this->oldTokenValues[$stackPtr]['curr']; |
|
| 572 | - $this->oldTokenValues[$stackPtr]['curr'] = $content; |
|
| 573 | - $this->oldTokenValues[$stackPtr]['loop'] = $this->loops; |
|
| 574 | - }//end if |
|
| 575 | - |
|
| 576 | - $this->fixedTokens[$stackPtr] = $this->tokens[$stackPtr]; |
|
| 577 | - $this->tokens[$stackPtr] = $content; |
|
| 578 | - $this->numFixes++; |
|
| 579 | - |
|
| 580 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 581 | - $indent = "\t"; |
|
| 582 | - if (empty($this->changeset) === false) { |
|
| 583 | - $indent .= "\tA: "; |
|
| 584 | - } |
|
| 585 | - |
|
| 586 | - if (ob_get_level() > 0) { |
|
| 587 | - ob_end_clean(); |
|
| 588 | - } |
|
| 589 | - |
|
| 590 | - echo "$indent$sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL; |
|
| 591 | - ob_start(); |
|
| 592 | - } |
|
| 593 | - |
|
| 594 | - return true; |
|
| 595 | - |
|
| 596 | - }//end replaceToken() |
|
| 597 | - |
|
| 598 | - |
|
| 599 | - /** |
|
| 600 | - * Reverts the previous fix made to a token. |
|
| 601 | - * |
|
| 602 | - * @param int $stackPtr The position of the token in the token stack. |
|
| 603 | - * |
|
| 604 | - * @return bool If a change was reverted. |
|
| 605 | - */ |
|
| 606 | - public function revertToken($stackPtr) |
|
| 607 | - { |
|
| 608 | - if (isset($this->fixedTokens[$stackPtr]) === false) { |
|
| 609 | - return false; |
|
| 610 | - } |
|
| 611 | - |
|
| 612 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 613 | - $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
|
| 614 | - if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') { |
|
| 615 | - $sniff = $bt[2]['class']; |
|
| 616 | - $line = $bt[1]['line']; |
|
| 617 | - } else { |
|
| 618 | - $sniff = $bt[1]['class']; |
|
| 619 | - $line = $bt[0]['line']; |
|
| 620 | - } |
|
| 621 | - |
|
| 622 | - $sniff = Util\Common::getSniffCode($sniff); |
|
| 623 | - |
|
| 624 | - $tokens = $this->currentFile->getTokens(); |
|
| 625 | - $type = $tokens[$stackPtr]['type']; |
|
| 626 | - $tokenLine = $tokens[$stackPtr]['line']; |
|
| 627 | - $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]); |
|
| 628 | - $newContent = Common::prepareForOutput($this->fixedTokens[$stackPtr]); |
|
| 629 | - if (trim($this->tokens[$stackPtr]) === '' && isset($tokens[($stackPtr + 1)]) === true) { |
|
| 630 | - // Add some context for whitespace only changes. |
|
| 631 | - $append = Common::prepareForOutput($this->tokens[($stackPtr + 1)]); |
|
| 632 | - $oldContent .= $append; |
|
| 633 | - $newContent .= $append; |
|
| 634 | - } |
|
| 635 | - }//end if |
|
| 636 | - |
|
| 637 | - $this->tokens[$stackPtr] = $this->fixedTokens[$stackPtr]; |
|
| 638 | - unset($this->fixedTokens[$stackPtr]); |
|
| 639 | - $this->numFixes--; |
|
| 640 | - |
|
| 641 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 642 | - $indent = "\t"; |
|
| 643 | - if (empty($this->changeset) === false) { |
|
| 644 | - $indent .= "\tR: "; |
|
| 645 | - } |
|
| 646 | - |
|
| 647 | - @ob_end_clean(); |
|
| 648 | - echo "$indent$sniff:$line reverted token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL; |
|
| 649 | - ob_start(); |
|
| 650 | - } |
|
| 651 | - |
|
| 652 | - return true; |
|
| 653 | - |
|
| 654 | - }//end revertToken() |
|
| 655 | - |
|
| 656 | - |
|
| 657 | - /** |
|
| 658 | - * Replace the content of a token with a part of its current content. |
|
| 659 | - * |
|
| 660 | - * @param int $stackPtr The position of the token in the token stack. |
|
| 661 | - * @param int $start The first character to keep. |
|
| 662 | - * @param int $length The number of characters to keep. If NULL, the content of |
|
| 663 | - * the token from $start to the end of the content is kept. |
|
| 664 | - * |
|
| 665 | - * @return bool If the change was accepted. |
|
| 666 | - */ |
|
| 667 | - public function substrToken($stackPtr, $start, $length=null) |
|
| 668 | - { |
|
| 669 | - $current = $this->getTokenContent($stackPtr); |
|
| 670 | - |
|
| 671 | - if ($length === null) { |
|
| 672 | - $newContent = substr($current, $start); |
|
| 673 | - } else { |
|
| 674 | - $newContent = substr($current, $start, $length); |
|
| 675 | - } |
|
| 676 | - |
|
| 677 | - return $this->replaceToken($stackPtr, $newContent); |
|
| 678 | - |
|
| 679 | - }//end substrToken() |
|
| 680 | - |
|
| 681 | - |
|
| 682 | - /** |
|
| 683 | - * Adds a newline to end of a token's content. |
|
| 684 | - * |
|
| 685 | - * @param int $stackPtr The position of the token in the token stack. |
|
| 686 | - * |
|
| 687 | - * @return bool If the change was accepted. |
|
| 688 | - */ |
|
| 689 | - public function addNewline($stackPtr) |
|
| 690 | - { |
|
| 691 | - $current = $this->getTokenContent($stackPtr); |
|
| 692 | - return $this->replaceToken($stackPtr, $current.$this->currentFile->eolChar); |
|
| 693 | - |
|
| 694 | - }//end addNewline() |
|
| 695 | - |
|
| 696 | - |
|
| 697 | - /** |
|
| 698 | - * Adds a newline to the start of a token's content. |
|
| 699 | - * |
|
| 700 | - * @param int $stackPtr The position of the token in the token stack. |
|
| 701 | - * |
|
| 702 | - * @return bool If the change was accepted. |
|
| 703 | - */ |
|
| 704 | - public function addNewlineBefore($stackPtr) |
|
| 705 | - { |
|
| 706 | - $current = $this->getTokenContent($stackPtr); |
|
| 707 | - return $this->replaceToken($stackPtr, $this->currentFile->eolChar.$current); |
|
| 708 | - |
|
| 709 | - }//end addNewlineBefore() |
|
| 710 | - |
|
| 711 | - |
|
| 712 | - /** |
|
| 713 | - * Adds content to the end of a token's current content. |
|
| 714 | - * |
|
| 715 | - * @param int $stackPtr The position of the token in the token stack. |
|
| 716 | - * @param string $content The content to add. |
|
| 717 | - * |
|
| 718 | - * @return bool If the change was accepted. |
|
| 719 | - */ |
|
| 720 | - public function addContent($stackPtr, $content) |
|
| 721 | - { |
|
| 722 | - $current = $this->getTokenContent($stackPtr); |
|
| 723 | - return $this->replaceToken($stackPtr, $current.$content); |
|
| 724 | - |
|
| 725 | - }//end addContent() |
|
| 726 | - |
|
| 727 | - |
|
| 728 | - /** |
|
| 729 | - * Adds content to the start of a token's current content. |
|
| 730 | - * |
|
| 731 | - * @param int $stackPtr The position of the token in the token stack. |
|
| 732 | - * @param string $content The content to add. |
|
| 733 | - * |
|
| 734 | - * @return bool If the change was accepted. |
|
| 735 | - */ |
|
| 736 | - public function addContentBefore($stackPtr, $content) |
|
| 737 | - { |
|
| 738 | - $current = $this->getTokenContent($stackPtr); |
|
| 739 | - return $this->replaceToken($stackPtr, $content.$current); |
|
| 740 | - |
|
| 741 | - }//end addContentBefore() |
|
| 742 | - |
|
| 743 | - |
|
| 744 | - /** |
|
| 745 | - * Adjust the indent of a code block. |
|
| 746 | - * |
|
| 747 | - * @param int $start The position of the token in the token stack |
|
| 748 | - * to start adjusting the indent from. |
|
| 749 | - * @param int $end The position of the token in the token stack |
|
| 750 | - * to end adjusting the indent. |
|
| 751 | - * @param int $change The number of spaces to adjust the indent by |
|
| 752 | - * (positive or negative). |
|
| 753 | - * |
|
| 754 | - * @return void |
|
| 755 | - */ |
|
| 756 | - public function changeCodeBlockIndent($start, $end, $change) |
|
| 757 | - { |
|
| 758 | - $tokens = $this->currentFile->getTokens(); |
|
| 759 | - |
|
| 760 | - $baseIndent = ''; |
|
| 761 | - if ($change > 0) { |
|
| 762 | - $baseIndent = str_repeat(' ', $change); |
|
| 763 | - } |
|
| 764 | - |
|
| 765 | - $useChangeset = false; |
|
| 766 | - if ($this->inChangeset === false) { |
|
| 767 | - $this->beginChangeset(); |
|
| 768 | - $useChangeset = true; |
|
| 769 | - } |
|
| 770 | - |
|
| 771 | - for ($i = $start; $i <= $end; $i++) { |
|
| 772 | - if ($tokens[$i]['column'] !== 1 |
|
| 773 | - || $tokens[($i + 1)]['line'] !== $tokens[$i]['line'] |
|
| 774 | - ) { |
|
| 775 | - continue; |
|
| 776 | - } |
|
| 777 | - |
|
| 778 | - $length = 0; |
|
| 779 | - if ($tokens[$i]['code'] === T_WHITESPACE |
|
| 780 | - || $tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE |
|
| 781 | - ) { |
|
| 782 | - $length = $tokens[$i]['length']; |
|
| 783 | - |
|
| 784 | - $padding = ($length + $change); |
|
| 785 | - if ($padding > 0) { |
|
| 786 | - $padding = str_repeat(' ', $padding); |
|
| 787 | - } else { |
|
| 788 | - $padding = ''; |
|
| 789 | - } |
|
| 790 | - |
|
| 791 | - $newContent = $padding.ltrim($tokens[$i]['content']); |
|
| 792 | - } else { |
|
| 793 | - $newContent = $baseIndent.$tokens[$i]['content']; |
|
| 794 | - } |
|
| 795 | - |
|
| 796 | - $this->replaceToken($i, $newContent); |
|
| 797 | - }//end for |
|
| 798 | - |
|
| 799 | - if ($useChangeset === true) { |
|
| 800 | - $this->endChangeset(); |
|
| 801 | - } |
|
| 802 | - |
|
| 803 | - }//end changeCodeBlockIndent() |
|
| 21 | + /** |
|
| 22 | + * Is the fixer enabled and fixing a file? |
|
| 23 | + * |
|
| 24 | + * Sniffs should check this value to ensure they are not |
|
| 25 | + * doing extra processing to prepare for a fix when fixing is |
|
| 26 | + * not required. |
|
| 27 | + * |
|
| 28 | + * @var boolean |
|
| 29 | + */ |
|
| 30 | + public $enabled = false; |
|
| 31 | + |
|
| 32 | + /** |
|
| 33 | + * The number of times we have looped over a file. |
|
| 34 | + * |
|
| 35 | + * @var integer |
|
| 36 | + */ |
|
| 37 | + public $loops = 0; |
|
| 38 | + |
|
| 39 | + /** |
|
| 40 | + * The file being fixed. |
|
| 41 | + * |
|
| 42 | + * @var \PHP_CodeSniffer\Files\File |
|
| 43 | + */ |
|
| 44 | + private $currentFile = null; |
|
| 45 | + |
|
| 46 | + /** |
|
| 47 | + * The list of tokens that make up the file contents. |
|
| 48 | + * |
|
| 49 | + * This is a simplified list which just contains the token content and nothing |
|
| 50 | + * else. This is the array that is updated as fixes are made, not the file's |
|
| 51 | + * token array. Imploding this array will give you the file content back. |
|
| 52 | + * |
|
| 53 | + * @var array<int, string> |
|
| 54 | + */ |
|
| 55 | + private $tokens = []; |
|
| 56 | + |
|
| 57 | + /** |
|
| 58 | + * A list of tokens that have already been fixed. |
|
| 59 | + * |
|
| 60 | + * We don't allow the same token to be fixed more than once each time |
|
| 61 | + * through a file as this can easily cause conflicts between sniffs. |
|
| 62 | + * |
|
| 63 | + * @var int[] |
|
| 64 | + */ |
|
| 65 | + private $fixedTokens = []; |
|
| 66 | + |
|
| 67 | + /** |
|
| 68 | + * The last value of each fixed token. |
|
| 69 | + * |
|
| 70 | + * If a token is being "fixed" back to its last value, the fix is |
|
| 71 | + * probably conflicting with another. |
|
| 72 | + * |
|
| 73 | + * @var array<int, string> |
|
| 74 | + */ |
|
| 75 | + private $oldTokenValues = []; |
|
| 76 | + |
|
| 77 | + /** |
|
| 78 | + * A list of tokens that have been fixed during a changeset. |
|
| 79 | + * |
|
| 80 | + * All changes in changeset must be able to be applied, or else |
|
| 81 | + * the entire changeset is rejected. |
|
| 82 | + * |
|
| 83 | + * @var array |
|
| 84 | + */ |
|
| 85 | + private $changeset = []; |
|
| 86 | + |
|
| 87 | + /** |
|
| 88 | + * Is there an open changeset. |
|
| 89 | + * |
|
| 90 | + * @var boolean |
|
| 91 | + */ |
|
| 92 | + private $inChangeset = false; |
|
| 93 | + |
|
| 94 | + /** |
|
| 95 | + * Is the current fixing loop in conflict? |
|
| 96 | + * |
|
| 97 | + * @var boolean |
|
| 98 | + */ |
|
| 99 | + private $inConflict = false; |
|
| 100 | + |
|
| 101 | + /** |
|
| 102 | + * The number of fixes that have been performed. |
|
| 103 | + * |
|
| 104 | + * @var integer |
|
| 105 | + */ |
|
| 106 | + private $numFixes = 0; |
|
| 107 | + |
|
| 108 | + |
|
| 109 | + /** |
|
| 110 | + * Starts fixing a new file. |
|
| 111 | + * |
|
| 112 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being fixed. |
|
| 113 | + * |
|
| 114 | + * @return void |
|
| 115 | + */ |
|
| 116 | + public function startFile(File $phpcsFile) |
|
| 117 | + { |
|
| 118 | + $this->currentFile = $phpcsFile; |
|
| 119 | + $this->numFixes = 0; |
|
| 120 | + $this->fixedTokens = []; |
|
| 121 | + |
|
| 122 | + $tokens = $phpcsFile->getTokens(); |
|
| 123 | + $this->tokens = []; |
|
| 124 | + foreach ($tokens as $index => $token) { |
|
| 125 | + if (isset($token['orig_content']) === true) { |
|
| 126 | + $this->tokens[$index] = $token['orig_content']; |
|
| 127 | + } else { |
|
| 128 | + $this->tokens[$index] = $token['content']; |
|
| 129 | + } |
|
| 130 | + } |
|
| 131 | + |
|
| 132 | + }//end startFile() |
|
| 133 | + |
|
| 134 | + |
|
| 135 | + /** |
|
| 136 | + * Attempt to fix the file by processing it until no fixes are made. |
|
| 137 | + * |
|
| 138 | + * @return boolean |
|
| 139 | + */ |
|
| 140 | + public function fixFile() |
|
| 141 | + { |
|
| 142 | + $fixable = $this->currentFile->getFixableCount(); |
|
| 143 | + if ($fixable === 0) { |
|
| 144 | + // Nothing to fix. |
|
| 145 | + return false; |
|
| 146 | + } |
|
| 147 | + |
|
| 148 | + $this->enabled = true; |
|
| 149 | + |
|
| 150 | + $this->loops = 0; |
|
| 151 | + while ($this->loops < 50) { |
|
| 152 | + ob_start(); |
|
| 153 | + |
|
| 154 | + // Only needed once file content has changed. |
|
| 155 | + $contents = $this->getContents(); |
|
| 156 | + |
|
| 157 | + if (PHP_CODESNIFFER_VERBOSITY > 2) { |
|
| 158 | + @ob_end_clean(); |
|
| 159 | + echo '---START FILE CONTENT---'.PHP_EOL; |
|
| 160 | + $lines = explode($this->currentFile->eolChar, $contents); |
|
| 161 | + $max = strlen(count($lines)); |
|
| 162 | + foreach ($lines as $lineNum => $line) { |
|
| 163 | + $lineNum++; |
|
| 164 | + echo str_pad($lineNum, $max, ' ', STR_PAD_LEFT).'|'.$line.PHP_EOL; |
|
| 165 | + } |
|
| 166 | + |
|
| 167 | + echo '--- END FILE CONTENT ---'.PHP_EOL; |
|
| 168 | + ob_start(); |
|
| 169 | + } |
|
| 170 | + |
|
| 171 | + $this->inConflict = false; |
|
| 172 | + $this->currentFile->ruleset->populateTokenListeners(); |
|
| 173 | + $this->currentFile->setContent($contents); |
|
| 174 | + $this->currentFile->process(); |
|
| 175 | + ob_end_clean(); |
|
| 176 | + |
|
| 177 | + $this->loops++; |
|
| 178 | + |
|
| 179 | + if (PHP_CODESNIFFER_CBF === true && PHP_CODESNIFFER_VERBOSITY > 0) { |
|
| 180 | + echo "\r".str_repeat(' ', 80)."\r"; |
|
| 181 | + echo "\t=> Fixing file: $this->numFixes/$fixable violations remaining [made $this->loops pass"; |
|
| 182 | + if ($this->loops > 1) { |
|
| 183 | + echo 'es'; |
|
| 184 | + } |
|
| 185 | + |
|
| 186 | + echo ']... '; |
|
| 187 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 188 | + echo PHP_EOL; |
|
| 189 | + } |
|
| 190 | + } |
|
| 191 | + |
|
| 192 | + if ($this->numFixes === 0 && $this->inConflict === false) { |
|
| 193 | + // Nothing left to do. |
|
| 194 | + break; |
|
| 195 | + } else if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 196 | + echo "\t* fixed $this->numFixes violations, starting loop ".($this->loops + 1).' *'.PHP_EOL; |
|
| 197 | + } |
|
| 198 | + }//end while |
|
| 199 | + |
|
| 200 | + $this->enabled = false; |
|
| 201 | + |
|
| 202 | + if ($this->numFixes > 0) { |
|
| 203 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 204 | + if (ob_get_level() > 0) { |
|
| 205 | + ob_end_clean(); |
|
| 206 | + } |
|
| 207 | + |
|
| 208 | + echo "\t*** Reached maximum number of loops with $this->numFixes violations left unfixed ***".PHP_EOL; |
|
| 209 | + ob_start(); |
|
| 210 | + } |
|
| 211 | + |
|
| 212 | + return false; |
|
| 213 | + } |
|
| 214 | + |
|
| 215 | + return true; |
|
| 216 | + |
|
| 217 | + }//end fixFile() |
|
| 218 | + |
|
| 219 | + |
|
| 220 | + /** |
|
| 221 | + * Generates a text diff of the original file and the new content. |
|
| 222 | + * |
|
| 223 | + * @param string $filePath Optional file path to diff the file against. |
|
| 224 | + * If not specified, the original version of the |
|
| 225 | + * file will be used. |
|
| 226 | + * @param boolean $colors Print coloured output or not. |
|
| 227 | + * |
|
| 228 | + * @return string |
|
| 229 | + */ |
|
| 230 | + public function generateDiff($filePath=null, $colors=true) |
|
| 231 | + { |
|
| 232 | + if ($filePath === null) { |
|
| 233 | + $filePath = $this->currentFile->getFilename(); |
|
| 234 | + } |
|
| 235 | + |
|
| 236 | + $cwd = getcwd().DIRECTORY_SEPARATOR; |
|
| 237 | + if (strpos($filePath, $cwd) === 0) { |
|
| 238 | + $filename = substr($filePath, strlen($cwd)); |
|
| 239 | + } else { |
|
| 240 | + $filename = $filePath; |
|
| 241 | + } |
|
| 242 | + |
|
| 243 | + $contents = $this->getContents(); |
|
| 244 | + |
|
| 245 | + $tempName = tempnam(sys_get_temp_dir(), 'phpcs-fixer'); |
|
| 246 | + $fixedFile = fopen($tempName, 'w'); |
|
| 247 | + fwrite($fixedFile, $contents); |
|
| 248 | + |
|
| 249 | + // We must use something like shell_exec() because whitespace at the end |
|
| 250 | + // of lines is critical to diff files. |
|
| 251 | + $filename = escapeshellarg($filename); |
|
| 252 | + $cmd = "diff -u -L$filename -LPHP_CodeSniffer $filename \"$tempName\""; |
|
| 253 | + |
|
| 254 | + $diff = shell_exec($cmd); |
|
| 255 | + |
|
| 256 | + fclose($fixedFile); |
|
| 257 | + if (is_file($tempName) === true) { |
|
| 258 | + unlink($tempName); |
|
| 259 | + } |
|
| 260 | + |
|
| 261 | + if ($diff === null) { |
|
| 262 | + return ''; |
|
| 263 | + } |
|
| 264 | + |
|
| 265 | + if ($colors === false) { |
|
| 266 | + return $diff; |
|
| 267 | + } |
|
| 268 | + |
|
| 269 | + $diffLines = explode(PHP_EOL, $diff); |
|
| 270 | + if (count($diffLines) === 1) { |
|
| 271 | + // Seems to be required for cygwin. |
|
| 272 | + $diffLines = explode("\n", $diff); |
|
| 273 | + } |
|
| 274 | + |
|
| 275 | + $diff = []; |
|
| 276 | + foreach ($diffLines as $line) { |
|
| 277 | + if (isset($line[0]) === true) { |
|
| 278 | + switch ($line[0]) { |
|
| 279 | + case '-': |
|
| 280 | + $diff[] = "\033[31m$line\033[0m"; |
|
| 281 | + break; |
|
| 282 | + case '+': |
|
| 283 | + $diff[] = "\033[32m$line\033[0m"; |
|
| 284 | + break; |
|
| 285 | + default: |
|
| 286 | + $diff[] = $line; |
|
| 287 | + } |
|
| 288 | + } |
|
| 289 | + } |
|
| 290 | + |
|
| 291 | + $diff = implode(PHP_EOL, $diff); |
|
| 292 | + |
|
| 293 | + return $diff; |
|
| 294 | + |
|
| 295 | + }//end generateDiff() |
|
| 296 | + |
|
| 297 | + |
|
| 298 | + /** |
|
| 299 | + * Get a count of fixes that have been performed on the file. |
|
| 300 | + * |
|
| 301 | + * This value is reset every time a new file is started, or an existing |
|
| 302 | + * file is restarted. |
|
| 303 | + * |
|
| 304 | + * @return int |
|
| 305 | + */ |
|
| 306 | + public function getFixCount() |
|
| 307 | + { |
|
| 308 | + return $this->numFixes; |
|
| 309 | + |
|
| 310 | + }//end getFixCount() |
|
| 311 | + |
|
| 312 | + |
|
| 313 | + /** |
|
| 314 | + * Get the current content of the file, as a string. |
|
| 315 | + * |
|
| 316 | + * @return string |
|
| 317 | + */ |
|
| 318 | + public function getContents() |
|
| 319 | + { |
|
| 320 | + $contents = implode($this->tokens); |
|
| 321 | + return $contents; |
|
| 322 | + |
|
| 323 | + }//end getContents() |
|
| 324 | + |
|
| 325 | + |
|
| 326 | + /** |
|
| 327 | + * Get the current fixed content of a token. |
|
| 328 | + * |
|
| 329 | + * This function takes changesets into account so should be used |
|
| 330 | + * instead of directly accessing the token array. |
|
| 331 | + * |
|
| 332 | + * @param int $stackPtr The position of the token in the token stack. |
|
| 333 | + * |
|
| 334 | + * @return string |
|
| 335 | + */ |
|
| 336 | + public function getTokenContent($stackPtr) |
|
| 337 | + { |
|
| 338 | + if ($this->inChangeset === true |
|
| 339 | + && isset($this->changeset[$stackPtr]) === true |
|
| 340 | + ) { |
|
| 341 | + return $this->changeset[$stackPtr]; |
|
| 342 | + } else { |
|
| 343 | + return $this->tokens[$stackPtr]; |
|
| 344 | + } |
|
| 345 | + |
|
| 346 | + }//end getTokenContent() |
|
| 347 | + |
|
| 348 | + |
|
| 349 | + /** |
|
| 350 | + * Start recording actions for a changeset. |
|
| 351 | + * |
|
| 352 | + * @return void |
|
| 353 | + */ |
|
| 354 | + public function beginChangeset() |
|
| 355 | + { |
|
| 356 | + if ($this->inConflict === true) { |
|
| 357 | + return false; |
|
| 358 | + } |
|
| 359 | + |
|
| 360 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 361 | + $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
|
| 362 | + if ($bt[1]['class'] === __CLASS__) { |
|
| 363 | + $sniff = 'Fixer'; |
|
| 364 | + } else { |
|
| 365 | + $sniff = Util\Common::getSniffCode($bt[1]['class']); |
|
| 366 | + } |
|
| 367 | + |
|
| 368 | + $line = $bt[0]['line']; |
|
| 369 | + |
|
| 370 | + @ob_end_clean(); |
|
| 371 | + echo "\t=> Changeset started by $sniff:$line".PHP_EOL; |
|
| 372 | + ob_start(); |
|
| 373 | + } |
|
| 374 | + |
|
| 375 | + $this->changeset = []; |
|
| 376 | + $this->inChangeset = true; |
|
| 377 | + |
|
| 378 | + }//end beginChangeset() |
|
| 379 | + |
|
| 380 | + |
|
| 381 | + /** |
|
| 382 | + * Stop recording actions for a changeset, and apply logged changes. |
|
| 383 | + * |
|
| 384 | + * @return boolean |
|
| 385 | + */ |
|
| 386 | + public function endChangeset() |
|
| 387 | + { |
|
| 388 | + if ($this->inConflict === true) { |
|
| 389 | + return false; |
|
| 390 | + } |
|
| 391 | + |
|
| 392 | + $this->inChangeset = false; |
|
| 393 | + |
|
| 394 | + $success = true; |
|
| 395 | + $applied = []; |
|
| 396 | + foreach ($this->changeset as $stackPtr => $content) { |
|
| 397 | + $success = $this->replaceToken($stackPtr, $content); |
|
| 398 | + if ($success === false) { |
|
| 399 | + break; |
|
| 400 | + } else { |
|
| 401 | + $applied[] = $stackPtr; |
|
| 402 | + } |
|
| 403 | + } |
|
| 404 | + |
|
| 405 | + if ($success === false) { |
|
| 406 | + // Rolling back all changes. |
|
| 407 | + foreach ($applied as $stackPtr) { |
|
| 408 | + $this->revertToken($stackPtr); |
|
| 409 | + } |
|
| 410 | + |
|
| 411 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 412 | + @ob_end_clean(); |
|
| 413 | + echo "\t=> Changeset failed to apply".PHP_EOL; |
|
| 414 | + ob_start(); |
|
| 415 | + } |
|
| 416 | + } else if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 417 | + $fixes = count($this->changeset); |
|
| 418 | + @ob_end_clean(); |
|
| 419 | + echo "\t=> Changeset ended: $fixes changes applied".PHP_EOL; |
|
| 420 | + ob_start(); |
|
| 421 | + } |
|
| 422 | + |
|
| 423 | + $this->changeset = []; |
|
| 424 | + return true; |
|
| 425 | + |
|
| 426 | + }//end endChangeset() |
|
| 427 | + |
|
| 428 | + |
|
| 429 | + /** |
|
| 430 | + * Stop recording actions for a changeset, and discard logged changes. |
|
| 431 | + * |
|
| 432 | + * @return void |
|
| 433 | + */ |
|
| 434 | + public function rollbackChangeset() |
|
| 435 | + { |
|
| 436 | + $this->inChangeset = false; |
|
| 437 | + $this->inConflict = false; |
|
| 438 | + |
|
| 439 | + if (empty($this->changeset) === false) { |
|
| 440 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 441 | + $bt = debug_backtrace(); |
|
| 442 | + if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') { |
|
| 443 | + $sniff = $bt[2]['class']; |
|
| 444 | + $line = $bt[1]['line']; |
|
| 445 | + } else { |
|
| 446 | + $sniff = $bt[1]['class']; |
|
| 447 | + $line = $bt[0]['line']; |
|
| 448 | + } |
|
| 449 | + |
|
| 450 | + $sniff = Util\Common::getSniffCode($sniff); |
|
| 451 | + |
|
| 452 | + $numChanges = count($this->changeset); |
|
| 453 | + |
|
| 454 | + @ob_end_clean(); |
|
| 455 | + echo "\t\tR: $sniff:$line rolled back the changeset ($numChanges changes)".PHP_EOL; |
|
| 456 | + echo "\t=> Changeset rolled back".PHP_EOL; |
|
| 457 | + ob_start(); |
|
| 458 | + } |
|
| 459 | + |
|
| 460 | + $this->changeset = []; |
|
| 461 | + }//end if |
|
| 462 | + |
|
| 463 | + }//end rollbackChangeset() |
|
| 464 | + |
|
| 465 | + |
|
| 466 | + /** |
|
| 467 | + * Replace the entire contents of a token. |
|
| 468 | + * |
|
| 469 | + * @param int $stackPtr The position of the token in the token stack. |
|
| 470 | + * @param string $content The new content of the token. |
|
| 471 | + * |
|
| 472 | + * @return bool If the change was accepted. |
|
| 473 | + */ |
|
| 474 | + public function replaceToken($stackPtr, $content) |
|
| 475 | + { |
|
| 476 | + if ($this->inConflict === true) { |
|
| 477 | + return false; |
|
| 478 | + } |
|
| 479 | + |
|
| 480 | + if ($this->inChangeset === false |
|
| 481 | + && isset($this->fixedTokens[$stackPtr]) === true |
|
| 482 | + ) { |
|
| 483 | + $indent = "\t"; |
|
| 484 | + if (empty($this->changeset) === false) { |
|
| 485 | + $indent .= "\t"; |
|
| 486 | + } |
|
| 487 | + |
|
| 488 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 489 | + @ob_end_clean(); |
|
| 490 | + echo "$indent* token $stackPtr has already been modified, skipping *".PHP_EOL; |
|
| 491 | + ob_start(); |
|
| 492 | + } |
|
| 493 | + |
|
| 494 | + return false; |
|
| 495 | + } |
|
| 496 | + |
|
| 497 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 498 | + $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
|
| 499 | + if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') { |
|
| 500 | + $sniff = $bt[2]['class']; |
|
| 501 | + $line = $bt[1]['line']; |
|
| 502 | + } else { |
|
| 503 | + $sniff = $bt[1]['class']; |
|
| 504 | + $line = $bt[0]['line']; |
|
| 505 | + } |
|
| 506 | + |
|
| 507 | + $sniff = Util\Common::getSniffCode($sniff); |
|
| 508 | + |
|
| 509 | + $tokens = $this->currentFile->getTokens(); |
|
| 510 | + $type = $tokens[$stackPtr]['type']; |
|
| 511 | + $tokenLine = $tokens[$stackPtr]['line']; |
|
| 512 | + $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]); |
|
| 513 | + $newContent = Common::prepareForOutput($content); |
|
| 514 | + if (trim($this->tokens[$stackPtr]) === '' && isset($this->tokens[($stackPtr + 1)]) === true) { |
|
| 515 | + // Add some context for whitespace only changes. |
|
| 516 | + $append = Common::prepareForOutput($this->tokens[($stackPtr + 1)]); |
|
| 517 | + $oldContent .= $append; |
|
| 518 | + $newContent .= $append; |
|
| 519 | + } |
|
| 520 | + }//end if |
|
| 521 | + |
|
| 522 | + if ($this->inChangeset === true) { |
|
| 523 | + $this->changeset[$stackPtr] = $content; |
|
| 524 | + |
|
| 525 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 526 | + @ob_end_clean(); |
|
| 527 | + echo "\t\tQ: $sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL; |
|
| 528 | + ob_start(); |
|
| 529 | + } |
|
| 530 | + |
|
| 531 | + return true; |
|
| 532 | + } |
|
| 533 | + |
|
| 534 | + if (isset($this->oldTokenValues[$stackPtr]) === false) { |
|
| 535 | + $this->oldTokenValues[$stackPtr] = [ |
|
| 536 | + 'curr' => $content, |
|
| 537 | + 'prev' => $this->tokens[$stackPtr], |
|
| 538 | + 'loop' => $this->loops, |
|
| 539 | + ]; |
|
| 540 | + } else { |
|
| 541 | + if ($this->oldTokenValues[$stackPtr]['prev'] === $content |
|
| 542 | + && $this->oldTokenValues[$stackPtr]['loop'] === ($this->loops - 1) |
|
| 543 | + ) { |
|
| 544 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 545 | + $indent = "\t"; |
|
| 546 | + if (empty($this->changeset) === false) { |
|
| 547 | + $indent .= "\t"; |
|
| 548 | + } |
|
| 549 | + |
|
| 550 | + $loop = $this->oldTokenValues[$stackPtr]['loop']; |
|
| 551 | + |
|
| 552 | + @ob_end_clean(); |
|
| 553 | + echo "$indent**** $sniff:$line has possible conflict with another sniff on loop $loop; caused by the following change ****".PHP_EOL; |
|
| 554 | + echo "$indent**** replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\" ****".PHP_EOL; |
|
| 555 | + } |
|
| 556 | + |
|
| 557 | + if ($this->oldTokenValues[$stackPtr]['loop'] >= ($this->loops - 1)) { |
|
| 558 | + $this->inConflict = true; |
|
| 559 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 560 | + echo "$indent**** ignoring all changes until next loop ****".PHP_EOL; |
|
| 561 | + } |
|
| 562 | + } |
|
| 563 | + |
|
| 564 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 565 | + ob_start(); |
|
| 566 | + } |
|
| 567 | + |
|
| 568 | + return false; |
|
| 569 | + }//end if |
|
| 570 | + |
|
| 571 | + $this->oldTokenValues[$stackPtr]['prev'] = $this->oldTokenValues[$stackPtr]['curr']; |
|
| 572 | + $this->oldTokenValues[$stackPtr]['curr'] = $content; |
|
| 573 | + $this->oldTokenValues[$stackPtr]['loop'] = $this->loops; |
|
| 574 | + }//end if |
|
| 575 | + |
|
| 576 | + $this->fixedTokens[$stackPtr] = $this->tokens[$stackPtr]; |
|
| 577 | + $this->tokens[$stackPtr] = $content; |
|
| 578 | + $this->numFixes++; |
|
| 579 | + |
|
| 580 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 581 | + $indent = "\t"; |
|
| 582 | + if (empty($this->changeset) === false) { |
|
| 583 | + $indent .= "\tA: "; |
|
| 584 | + } |
|
| 585 | + |
|
| 586 | + if (ob_get_level() > 0) { |
|
| 587 | + ob_end_clean(); |
|
| 588 | + } |
|
| 589 | + |
|
| 590 | + echo "$indent$sniff:$line replaced token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL; |
|
| 591 | + ob_start(); |
|
| 592 | + } |
|
| 593 | + |
|
| 594 | + return true; |
|
| 595 | + |
|
| 596 | + }//end replaceToken() |
|
| 597 | + |
|
| 598 | + |
|
| 599 | + /** |
|
| 600 | + * Reverts the previous fix made to a token. |
|
| 601 | + * |
|
| 602 | + * @param int $stackPtr The position of the token in the token stack. |
|
| 603 | + * |
|
| 604 | + * @return bool If a change was reverted. |
|
| 605 | + */ |
|
| 606 | + public function revertToken($stackPtr) |
|
| 607 | + { |
|
| 608 | + if (isset($this->fixedTokens[$stackPtr]) === false) { |
|
| 609 | + return false; |
|
| 610 | + } |
|
| 611 | + |
|
| 612 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 613 | + $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
|
| 614 | + if ($bt[1]['class'] === 'PHP_CodeSniffer\Fixer') { |
|
| 615 | + $sniff = $bt[2]['class']; |
|
| 616 | + $line = $bt[1]['line']; |
|
| 617 | + } else { |
|
| 618 | + $sniff = $bt[1]['class']; |
|
| 619 | + $line = $bt[0]['line']; |
|
| 620 | + } |
|
| 621 | + |
|
| 622 | + $sniff = Util\Common::getSniffCode($sniff); |
|
| 623 | + |
|
| 624 | + $tokens = $this->currentFile->getTokens(); |
|
| 625 | + $type = $tokens[$stackPtr]['type']; |
|
| 626 | + $tokenLine = $tokens[$stackPtr]['line']; |
|
| 627 | + $oldContent = Common::prepareForOutput($this->tokens[$stackPtr]); |
|
| 628 | + $newContent = Common::prepareForOutput($this->fixedTokens[$stackPtr]); |
|
| 629 | + if (trim($this->tokens[$stackPtr]) === '' && isset($tokens[($stackPtr + 1)]) === true) { |
|
| 630 | + // Add some context for whitespace only changes. |
|
| 631 | + $append = Common::prepareForOutput($this->tokens[($stackPtr + 1)]); |
|
| 632 | + $oldContent .= $append; |
|
| 633 | + $newContent .= $append; |
|
| 634 | + } |
|
| 635 | + }//end if |
|
| 636 | + |
|
| 637 | + $this->tokens[$stackPtr] = $this->fixedTokens[$stackPtr]; |
|
| 638 | + unset($this->fixedTokens[$stackPtr]); |
|
| 639 | + $this->numFixes--; |
|
| 640 | + |
|
| 641 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 642 | + $indent = "\t"; |
|
| 643 | + if (empty($this->changeset) === false) { |
|
| 644 | + $indent .= "\tR: "; |
|
| 645 | + } |
|
| 646 | + |
|
| 647 | + @ob_end_clean(); |
|
| 648 | + echo "$indent$sniff:$line reverted token $stackPtr ($type on line $tokenLine) \"$oldContent\" => \"$newContent\"".PHP_EOL; |
|
| 649 | + ob_start(); |
|
| 650 | + } |
|
| 651 | + |
|
| 652 | + return true; |
|
| 653 | + |
|
| 654 | + }//end revertToken() |
|
| 655 | + |
|
| 656 | + |
|
| 657 | + /** |
|
| 658 | + * Replace the content of a token with a part of its current content. |
|
| 659 | + * |
|
| 660 | + * @param int $stackPtr The position of the token in the token stack. |
|
| 661 | + * @param int $start The first character to keep. |
|
| 662 | + * @param int $length The number of characters to keep. If NULL, the content of |
|
| 663 | + * the token from $start to the end of the content is kept. |
|
| 664 | + * |
|
| 665 | + * @return bool If the change was accepted. |
|
| 666 | + */ |
|
| 667 | + public function substrToken($stackPtr, $start, $length=null) |
|
| 668 | + { |
|
| 669 | + $current = $this->getTokenContent($stackPtr); |
|
| 670 | + |
|
| 671 | + if ($length === null) { |
|
| 672 | + $newContent = substr($current, $start); |
|
| 673 | + } else { |
|
| 674 | + $newContent = substr($current, $start, $length); |
|
| 675 | + } |
|
| 676 | + |
|
| 677 | + return $this->replaceToken($stackPtr, $newContent); |
|
| 678 | + |
|
| 679 | + }//end substrToken() |
|
| 680 | + |
|
| 681 | + |
|
| 682 | + /** |
|
| 683 | + * Adds a newline to end of a token's content. |
|
| 684 | + * |
|
| 685 | + * @param int $stackPtr The position of the token in the token stack. |
|
| 686 | + * |
|
| 687 | + * @return bool If the change was accepted. |
|
| 688 | + */ |
|
| 689 | + public function addNewline($stackPtr) |
|
| 690 | + { |
|
| 691 | + $current = $this->getTokenContent($stackPtr); |
|
| 692 | + return $this->replaceToken($stackPtr, $current.$this->currentFile->eolChar); |
|
| 693 | + |
|
| 694 | + }//end addNewline() |
|
| 695 | + |
|
| 696 | + |
|
| 697 | + /** |
|
| 698 | + * Adds a newline to the start of a token's content. |
|
| 699 | + * |
|
| 700 | + * @param int $stackPtr The position of the token in the token stack. |
|
| 701 | + * |
|
| 702 | + * @return bool If the change was accepted. |
|
| 703 | + */ |
|
| 704 | + public function addNewlineBefore($stackPtr) |
|
| 705 | + { |
|
| 706 | + $current = $this->getTokenContent($stackPtr); |
|
| 707 | + return $this->replaceToken($stackPtr, $this->currentFile->eolChar.$current); |
|
| 708 | + |
|
| 709 | + }//end addNewlineBefore() |
|
| 710 | + |
|
| 711 | + |
|
| 712 | + /** |
|
| 713 | + * Adds content to the end of a token's current content. |
|
| 714 | + * |
|
| 715 | + * @param int $stackPtr The position of the token in the token stack. |
|
| 716 | + * @param string $content The content to add. |
|
| 717 | + * |
|
| 718 | + * @return bool If the change was accepted. |
|
| 719 | + */ |
|
| 720 | + public function addContent($stackPtr, $content) |
|
| 721 | + { |
|
| 722 | + $current = $this->getTokenContent($stackPtr); |
|
| 723 | + return $this->replaceToken($stackPtr, $current.$content); |
|
| 724 | + |
|
| 725 | + }//end addContent() |
|
| 726 | + |
|
| 727 | + |
|
| 728 | + /** |
|
| 729 | + * Adds content to the start of a token's current content. |
|
| 730 | + * |
|
| 731 | + * @param int $stackPtr The position of the token in the token stack. |
|
| 732 | + * @param string $content The content to add. |
|
| 733 | + * |
|
| 734 | + * @return bool If the change was accepted. |
|
| 735 | + */ |
|
| 736 | + public function addContentBefore($stackPtr, $content) |
|
| 737 | + { |
|
| 738 | + $current = $this->getTokenContent($stackPtr); |
|
| 739 | + return $this->replaceToken($stackPtr, $content.$current); |
|
| 740 | + |
|
| 741 | + }//end addContentBefore() |
|
| 742 | + |
|
| 743 | + |
|
| 744 | + /** |
|
| 745 | + * Adjust the indent of a code block. |
|
| 746 | + * |
|
| 747 | + * @param int $start The position of the token in the token stack |
|
| 748 | + * to start adjusting the indent from. |
|
| 749 | + * @param int $end The position of the token in the token stack |
|
| 750 | + * to end adjusting the indent. |
|
| 751 | + * @param int $change The number of spaces to adjust the indent by |
|
| 752 | + * (positive or negative). |
|
| 753 | + * |
|
| 754 | + * @return void |
|
| 755 | + */ |
|
| 756 | + public function changeCodeBlockIndent($start, $end, $change) |
|
| 757 | + { |
|
| 758 | + $tokens = $this->currentFile->getTokens(); |
|
| 759 | + |
|
| 760 | + $baseIndent = ''; |
|
| 761 | + if ($change > 0) { |
|
| 762 | + $baseIndent = str_repeat(' ', $change); |
|
| 763 | + } |
|
| 764 | + |
|
| 765 | + $useChangeset = false; |
|
| 766 | + if ($this->inChangeset === false) { |
|
| 767 | + $this->beginChangeset(); |
|
| 768 | + $useChangeset = true; |
|
| 769 | + } |
|
| 770 | + |
|
| 771 | + for ($i = $start; $i <= $end; $i++) { |
|
| 772 | + if ($tokens[$i]['column'] !== 1 |
|
| 773 | + || $tokens[($i + 1)]['line'] !== $tokens[$i]['line'] |
|
| 774 | + ) { |
|
| 775 | + continue; |
|
| 776 | + } |
|
| 777 | + |
|
| 778 | + $length = 0; |
|
| 779 | + if ($tokens[$i]['code'] === T_WHITESPACE |
|
| 780 | + || $tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE |
|
| 781 | + ) { |
|
| 782 | + $length = $tokens[$i]['length']; |
|
| 783 | + |
|
| 784 | + $padding = ($length + $change); |
|
| 785 | + if ($padding > 0) { |
|
| 786 | + $padding = str_repeat(' ', $padding); |
|
| 787 | + } else { |
|
| 788 | + $padding = ''; |
|
| 789 | + } |
|
| 790 | + |
|
| 791 | + $newContent = $padding.ltrim($tokens[$i]['content']); |
|
| 792 | + } else { |
|
| 793 | + $newContent = $baseIndent.$tokens[$i]['content']; |
|
| 794 | + } |
|
| 795 | + |
|
| 796 | + $this->replaceToken($i, $newContent); |
|
| 797 | + }//end for |
|
| 798 | + |
|
| 799 | + if ($useChangeset === true) { |
|
| 800 | + $this->endChangeset(); |
|
| 801 | + } |
|
| 802 | + |
|
| 803 | + }//end changeCodeBlockIndent() |
|
| 804 | 804 | |
| 805 | 805 | |
| 806 | 806 | }//end class |
@@ -15,263 +15,263 @@ |
||
| 15 | 15 | { |
| 16 | 16 | |
| 17 | 17 | |
| 18 | - /** |
|
| 19 | - * Creates an array of tokens when given some PHP code. |
|
| 20 | - * |
|
| 21 | - * Starts by using token_get_all() but does a lot of extra processing |
|
| 22 | - * to insert information about the context of the token. |
|
| 23 | - * |
|
| 24 | - * @param string $string The string to tokenize. |
|
| 25 | - * @param string $eolChar The EOL character to use for splitting strings. |
|
| 26 | - * @param int $stackPtr The position of the first token in the file. |
|
| 27 | - * |
|
| 28 | - * @return array |
|
| 29 | - */ |
|
| 30 | - public function tokenizeString($string, $eolChar, $stackPtr) |
|
| 31 | - { |
|
| 32 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 33 | - echo "\t\t*** START COMMENT TOKENIZING ***".PHP_EOL; |
|
| 34 | - } |
|
| 35 | - |
|
| 36 | - $tokens = []; |
|
| 37 | - $numChars = strlen($string); |
|
| 38 | - |
|
| 39 | - /* |
|
| 18 | + /** |
|
| 19 | + * Creates an array of tokens when given some PHP code. |
|
| 20 | + * |
|
| 21 | + * Starts by using token_get_all() but does a lot of extra processing |
|
| 22 | + * to insert information about the context of the token. |
|
| 23 | + * |
|
| 24 | + * @param string $string The string to tokenize. |
|
| 25 | + * @param string $eolChar The EOL character to use for splitting strings. |
|
| 26 | + * @param int $stackPtr The position of the first token in the file. |
|
| 27 | + * |
|
| 28 | + * @return array |
|
| 29 | + */ |
|
| 30 | + public function tokenizeString($string, $eolChar, $stackPtr) |
|
| 31 | + { |
|
| 32 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 33 | + echo "\t\t*** START COMMENT TOKENIZING ***".PHP_EOL; |
|
| 34 | + } |
|
| 35 | + |
|
| 36 | + $tokens = []; |
|
| 37 | + $numChars = strlen($string); |
|
| 38 | + |
|
| 39 | + /* |
|
| 40 | 40 | Doc block comments start with /*, but typically contain an |
| 41 | 41 | extra star when they are used for function and class comments. |
| 42 | 42 | */ |
| 43 | 43 | |
| 44 | - $char = ($numChars - strlen(ltrim($string, '/*'))); |
|
| 45 | - $openTag = substr($string, 0, $char); |
|
| 46 | - $string = ltrim($string, '/*'); |
|
| 44 | + $char = ($numChars - strlen(ltrim($string, '/*'))); |
|
| 45 | + $openTag = substr($string, 0, $char); |
|
| 46 | + $string = ltrim($string, '/*'); |
|
| 47 | 47 | |
| 48 | - $tokens[$stackPtr] = [ |
|
| 49 | - 'content' => $openTag, |
|
| 50 | - 'code' => T_DOC_COMMENT_OPEN_TAG, |
|
| 51 | - 'type' => 'T_DOC_COMMENT_OPEN_TAG', |
|
| 52 | - 'comment_tags' => [], |
|
| 53 | - ]; |
|
| 48 | + $tokens[$stackPtr] = [ |
|
| 49 | + 'content' => $openTag, |
|
| 50 | + 'code' => T_DOC_COMMENT_OPEN_TAG, |
|
| 51 | + 'type' => 'T_DOC_COMMENT_OPEN_TAG', |
|
| 52 | + 'comment_tags' => [], |
|
| 53 | + ]; |
|
| 54 | 54 | |
| 55 | - $openPtr = $stackPtr; |
|
| 56 | - $stackPtr++; |
|
| 55 | + $openPtr = $stackPtr; |
|
| 56 | + $stackPtr++; |
|
| 57 | 57 | |
| 58 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 59 | - $content = Util\Common::prepareForOutput($openTag); |
|
| 60 | - echo "\t\tCreate comment token: T_DOC_COMMENT_OPEN_TAG => $content".PHP_EOL; |
|
| 61 | - } |
|
| 58 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 59 | + $content = Util\Common::prepareForOutput($openTag); |
|
| 60 | + echo "\t\tCreate comment token: T_DOC_COMMENT_OPEN_TAG => $content".PHP_EOL; |
|
| 61 | + } |
|
| 62 | 62 | |
| 63 | - /* |
|
| 63 | + /* |
|
| 64 | 64 | Strip off the close tag so it doesn't interfere with any |
| 65 | 65 | of our comment line processing. The token will be added to the |
| 66 | 66 | stack just before we return it. |
| 67 | 67 | */ |
| 68 | 68 | |
| 69 | - $closeTag = [ |
|
| 70 | - 'content' => substr($string, strlen(rtrim($string, '/*'))), |
|
| 71 | - 'code' => T_DOC_COMMENT_CLOSE_TAG, |
|
| 72 | - 'type' => 'T_DOC_COMMENT_CLOSE_TAG', |
|
| 73 | - 'comment_opener' => $openPtr, |
|
| 74 | - ]; |
|
| 69 | + $closeTag = [ |
|
| 70 | + 'content' => substr($string, strlen(rtrim($string, '/*'))), |
|
| 71 | + 'code' => T_DOC_COMMENT_CLOSE_TAG, |
|
| 72 | + 'type' => 'T_DOC_COMMENT_CLOSE_TAG', |
|
| 73 | + 'comment_opener' => $openPtr, |
|
| 74 | + ]; |
|
| 75 | 75 | |
| 76 | - if ($closeTag['content'] === false) { |
|
| 77 | - $closeTag['content'] = ''; |
|
| 78 | - } |
|
| 76 | + if ($closeTag['content'] === false) { |
|
| 77 | + $closeTag['content'] = ''; |
|
| 78 | + } |
|
| 79 | 79 | |
| 80 | - $string = rtrim($string, '/*'); |
|
| 80 | + $string = rtrim($string, '/*'); |
|
| 81 | 81 | |
| 82 | - /* |
|
| 82 | + /* |
|
| 83 | 83 | Process each line of the comment. |
| 84 | 84 | */ |
| 85 | 85 | |
| 86 | - $lines = explode($eolChar, $string); |
|
| 87 | - $numLines = count($lines); |
|
| 88 | - foreach ($lines as $lineNum => $string) { |
|
| 89 | - if ($lineNum !== ($numLines - 1)) { |
|
| 90 | - $string .= $eolChar; |
|
| 91 | - } |
|
| 92 | - |
|
| 93 | - $char = 0; |
|
| 94 | - $numChars = strlen($string); |
|
| 95 | - |
|
| 96 | - // We've started a new line, so process the indent. |
|
| 97 | - $space = $this->collectWhitespace($string, $char, $numChars); |
|
| 98 | - if ($space !== null) { |
|
| 99 | - $tokens[$stackPtr] = $space; |
|
| 100 | - $stackPtr++; |
|
| 101 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 102 | - $content = Util\Common::prepareForOutput($space['content']); |
|
| 103 | - echo "\t\tCreate comment token: T_DOC_COMMENT_WHITESPACE => $content".PHP_EOL; |
|
| 104 | - } |
|
| 105 | - |
|
| 106 | - $char += strlen($space['content']); |
|
| 107 | - if ($char === $numChars) { |
|
| 108 | - break; |
|
| 109 | - } |
|
| 110 | - } |
|
| 111 | - |
|
| 112 | - if ($string === '') { |
|
| 113 | - continue; |
|
| 114 | - } |
|
| 115 | - |
|
| 116 | - if ($lineNum > 0 && $string[$char] === '*') { |
|
| 117 | - // This is a function or class doc block line. |
|
| 118 | - $char++; |
|
| 119 | - $tokens[$stackPtr] = [ |
|
| 120 | - 'content' => '*', |
|
| 121 | - 'code' => T_DOC_COMMENT_STAR, |
|
| 122 | - 'type' => 'T_DOC_COMMENT_STAR', |
|
| 123 | - ]; |
|
| 124 | - |
|
| 125 | - $stackPtr++; |
|
| 126 | - |
|
| 127 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 128 | - echo "\t\tCreate comment token: T_DOC_COMMENT_STAR => *".PHP_EOL; |
|
| 129 | - } |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - // Now we are ready to process the actual content of the line. |
|
| 133 | - $lineTokens = $this->processLine($string, $eolChar, $char, $numChars); |
|
| 134 | - foreach ($lineTokens as $lineToken) { |
|
| 135 | - $tokens[$stackPtr] = $lineToken; |
|
| 136 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 137 | - $content = Util\Common::prepareForOutput($lineToken['content']); |
|
| 138 | - $type = $lineToken['type']; |
|
| 139 | - echo "\t\tCreate comment token: $type => $content".PHP_EOL; |
|
| 140 | - } |
|
| 141 | - |
|
| 142 | - if ($lineToken['code'] === T_DOC_COMMENT_TAG) { |
|
| 143 | - $tokens[$openPtr]['comment_tags'][] = $stackPtr; |
|
| 144 | - } |
|
| 145 | - |
|
| 146 | - $stackPtr++; |
|
| 147 | - } |
|
| 148 | - }//end foreach |
|
| 149 | - |
|
| 150 | - $tokens[$stackPtr] = $closeTag; |
|
| 151 | - $tokens[$openPtr]['comment_closer'] = $stackPtr; |
|
| 152 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 153 | - $content = Util\Common::prepareForOutput($closeTag['content']); |
|
| 154 | - echo "\t\tCreate comment token: T_DOC_COMMENT_CLOSE_TAG => $content".PHP_EOL; |
|
| 155 | - } |
|
| 156 | - |
|
| 157 | - if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 158 | - echo "\t\t*** END COMMENT TOKENIZING ***".PHP_EOL; |
|
| 159 | - } |
|
| 160 | - |
|
| 161 | - return $tokens; |
|
| 162 | - |
|
| 163 | - }//end tokenizeString() |
|
| 164 | - |
|
| 165 | - |
|
| 166 | - /** |
|
| 167 | - * Process a single line of a comment. |
|
| 168 | - * |
|
| 169 | - * @param string $string The comment string being tokenized. |
|
| 170 | - * @param string $eolChar The EOL character to use for splitting strings. |
|
| 171 | - * @param int $start The position in the string to start processing. |
|
| 172 | - * @param int $end The position in the string to end processing. |
|
| 173 | - * |
|
| 174 | - * @return array |
|
| 175 | - */ |
|
| 176 | - private function processLine($string, $eolChar, $start, $end) |
|
| 177 | - { |
|
| 178 | - $tokens = []; |
|
| 179 | - |
|
| 180 | - // Collect content padding. |
|
| 181 | - $space = $this->collectWhitespace($string, $start, $end); |
|
| 182 | - if ($space !== null) { |
|
| 183 | - $tokens[] = $space; |
|
| 184 | - $start += strlen($space['content']); |
|
| 185 | - } |
|
| 186 | - |
|
| 187 | - if (isset($string[$start]) === false) { |
|
| 188 | - return $tokens; |
|
| 189 | - } |
|
| 190 | - |
|
| 191 | - if ($string[$start] === '@') { |
|
| 192 | - // The content up until the first whitespace is the tag name. |
|
| 193 | - $matches = []; |
|
| 194 | - preg_match('/@[^\s]+/', $string, $matches, 0, $start); |
|
| 195 | - if (isset($matches[0]) === true |
|
| 196 | - && substr(strtolower($matches[0]), 0, 7) !== '@phpcs:' |
|
| 197 | - ) { |
|
| 198 | - $tagName = $matches[0]; |
|
| 199 | - $start += strlen($tagName); |
|
| 200 | - $tokens[] = [ |
|
| 201 | - 'content' => $tagName, |
|
| 202 | - 'code' => T_DOC_COMMENT_TAG, |
|
| 203 | - 'type' => 'T_DOC_COMMENT_TAG', |
|
| 204 | - ]; |
|
| 205 | - |
|
| 206 | - // Then there will be some whitespace. |
|
| 207 | - $space = $this->collectWhitespace($string, $start, $end); |
|
| 208 | - if ($space !== null) { |
|
| 209 | - $tokens[] = $space; |
|
| 210 | - $start += strlen($space['content']); |
|
| 211 | - } |
|
| 212 | - } |
|
| 213 | - }//end if |
|
| 214 | - |
|
| 215 | - // Process the rest of the line. |
|
| 216 | - $eol = strpos($string, $eolChar, $start); |
|
| 217 | - if ($eol === false) { |
|
| 218 | - $eol = $end; |
|
| 219 | - } |
|
| 220 | - |
|
| 221 | - if ($eol > $start) { |
|
| 222 | - $tokens[] = [ |
|
| 223 | - 'content' => substr($string, $start, ($eol - $start)), |
|
| 224 | - 'code' => T_DOC_COMMENT_STRING, |
|
| 225 | - 'type' => 'T_DOC_COMMENT_STRING', |
|
| 226 | - ]; |
|
| 227 | - } |
|
| 228 | - |
|
| 229 | - if ($eol !== $end) { |
|
| 230 | - $tokens[] = [ |
|
| 231 | - 'content' => substr($string, $eol, strlen($eolChar)), |
|
| 232 | - 'code' => T_DOC_COMMENT_WHITESPACE, |
|
| 233 | - 'type' => 'T_DOC_COMMENT_WHITESPACE', |
|
| 234 | - ]; |
|
| 235 | - } |
|
| 236 | - |
|
| 237 | - return $tokens; |
|
| 238 | - |
|
| 239 | - }//end processLine() |
|
| 240 | - |
|
| 241 | - |
|
| 242 | - /** |
|
| 243 | - * Collect consecutive whitespace into a single token. |
|
| 244 | - * |
|
| 245 | - * @param string $string The comment string being tokenized. |
|
| 246 | - * @param int $start The position in the string to start processing. |
|
| 247 | - * @param int $end The position in the string to end processing. |
|
| 248 | - * |
|
| 249 | - * @return array|null |
|
| 250 | - */ |
|
| 251 | - private function collectWhitespace($string, $start, $end) |
|
| 252 | - { |
|
| 253 | - $space = ''; |
|
| 254 | - for ($start; $start < $end; $start++) { |
|
| 255 | - if ($string[$start] !== ' ' && $string[$start] !== "\t") { |
|
| 256 | - break; |
|
| 257 | - } |
|
| 258 | - |
|
| 259 | - $space .= $string[$start]; |
|
| 260 | - } |
|
| 261 | - |
|
| 262 | - if ($space === '') { |
|
| 263 | - return null; |
|
| 264 | - } |
|
| 265 | - |
|
| 266 | - $token = [ |
|
| 267 | - 'content' => $space, |
|
| 268 | - 'code' => T_DOC_COMMENT_WHITESPACE, |
|
| 269 | - 'type' => 'T_DOC_COMMENT_WHITESPACE', |
|
| 270 | - ]; |
|
| 271 | - |
|
| 272 | - return $token; |
|
| 273 | - |
|
| 274 | - }//end collectWhitespace() |
|
| 86 | + $lines = explode($eolChar, $string); |
|
| 87 | + $numLines = count($lines); |
|
| 88 | + foreach ($lines as $lineNum => $string) { |
|
| 89 | + if ($lineNum !== ($numLines - 1)) { |
|
| 90 | + $string .= $eolChar; |
|
| 91 | + } |
|
| 92 | + |
|
| 93 | + $char = 0; |
|
| 94 | + $numChars = strlen($string); |
|
| 95 | + |
|
| 96 | + // We've started a new line, so process the indent. |
|
| 97 | + $space = $this->collectWhitespace($string, $char, $numChars); |
|
| 98 | + if ($space !== null) { |
|
| 99 | + $tokens[$stackPtr] = $space; |
|
| 100 | + $stackPtr++; |
|
| 101 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 102 | + $content = Util\Common::prepareForOutput($space['content']); |
|
| 103 | + echo "\t\tCreate comment token: T_DOC_COMMENT_WHITESPACE => $content".PHP_EOL; |
|
| 104 | + } |
|
| 105 | + |
|
| 106 | + $char += strlen($space['content']); |
|
| 107 | + if ($char === $numChars) { |
|
| 108 | + break; |
|
| 109 | + } |
|
| 110 | + } |
|
| 111 | + |
|
| 112 | + if ($string === '') { |
|
| 113 | + continue; |
|
| 114 | + } |
|
| 115 | + |
|
| 116 | + if ($lineNum > 0 && $string[$char] === '*') { |
|
| 117 | + // This is a function or class doc block line. |
|
| 118 | + $char++; |
|
| 119 | + $tokens[$stackPtr] = [ |
|
| 120 | + 'content' => '*', |
|
| 121 | + 'code' => T_DOC_COMMENT_STAR, |
|
| 122 | + 'type' => 'T_DOC_COMMENT_STAR', |
|
| 123 | + ]; |
|
| 124 | + |
|
| 125 | + $stackPtr++; |
|
| 126 | + |
|
| 127 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 128 | + echo "\t\tCreate comment token: T_DOC_COMMENT_STAR => *".PHP_EOL; |
|
| 129 | + } |
|
| 130 | + } |
|
| 131 | + |
|
| 132 | + // Now we are ready to process the actual content of the line. |
|
| 133 | + $lineTokens = $this->processLine($string, $eolChar, $char, $numChars); |
|
| 134 | + foreach ($lineTokens as $lineToken) { |
|
| 135 | + $tokens[$stackPtr] = $lineToken; |
|
| 136 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 137 | + $content = Util\Common::prepareForOutput($lineToken['content']); |
|
| 138 | + $type = $lineToken['type']; |
|
| 139 | + echo "\t\tCreate comment token: $type => $content".PHP_EOL; |
|
| 140 | + } |
|
| 141 | + |
|
| 142 | + if ($lineToken['code'] === T_DOC_COMMENT_TAG) { |
|
| 143 | + $tokens[$openPtr]['comment_tags'][] = $stackPtr; |
|
| 144 | + } |
|
| 145 | + |
|
| 146 | + $stackPtr++; |
|
| 147 | + } |
|
| 148 | + }//end foreach |
|
| 149 | + |
|
| 150 | + $tokens[$stackPtr] = $closeTag; |
|
| 151 | + $tokens[$openPtr]['comment_closer'] = $stackPtr; |
|
| 152 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 153 | + $content = Util\Common::prepareForOutput($closeTag['content']); |
|
| 154 | + echo "\t\tCreate comment token: T_DOC_COMMENT_CLOSE_TAG => $content".PHP_EOL; |
|
| 155 | + } |
|
| 156 | + |
|
| 157 | + if (PHP_CODESNIFFER_VERBOSITY > 1) { |
|
| 158 | + echo "\t\t*** END COMMENT TOKENIZING ***".PHP_EOL; |
|
| 159 | + } |
|
| 160 | + |
|
| 161 | + return $tokens; |
|
| 162 | + |
|
| 163 | + }//end tokenizeString() |
|
| 164 | + |
|
| 165 | + |
|
| 166 | + /** |
|
| 167 | + * Process a single line of a comment. |
|
| 168 | + * |
|
| 169 | + * @param string $string The comment string being tokenized. |
|
| 170 | + * @param string $eolChar The EOL character to use for splitting strings. |
|
| 171 | + * @param int $start The position in the string to start processing. |
|
| 172 | + * @param int $end The position in the string to end processing. |
|
| 173 | + * |
|
| 174 | + * @return array |
|
| 175 | + */ |
|
| 176 | + private function processLine($string, $eolChar, $start, $end) |
|
| 177 | + { |
|
| 178 | + $tokens = []; |
|
| 179 | + |
|
| 180 | + // Collect content padding. |
|
| 181 | + $space = $this->collectWhitespace($string, $start, $end); |
|
| 182 | + if ($space !== null) { |
|
| 183 | + $tokens[] = $space; |
|
| 184 | + $start += strlen($space['content']); |
|
| 185 | + } |
|
| 186 | + |
|
| 187 | + if (isset($string[$start]) === false) { |
|
| 188 | + return $tokens; |
|
| 189 | + } |
|
| 190 | + |
|
| 191 | + if ($string[$start] === '@') { |
|
| 192 | + // The content up until the first whitespace is the tag name. |
|
| 193 | + $matches = []; |
|
| 194 | + preg_match('/@[^\s]+/', $string, $matches, 0, $start); |
|
| 195 | + if (isset($matches[0]) === true |
|
| 196 | + && substr(strtolower($matches[0]), 0, 7) !== '@phpcs:' |
|
| 197 | + ) { |
|
| 198 | + $tagName = $matches[0]; |
|
| 199 | + $start += strlen($tagName); |
|
| 200 | + $tokens[] = [ |
|
| 201 | + 'content' => $tagName, |
|
| 202 | + 'code' => T_DOC_COMMENT_TAG, |
|
| 203 | + 'type' => 'T_DOC_COMMENT_TAG', |
|
| 204 | + ]; |
|
| 205 | + |
|
| 206 | + // Then there will be some whitespace. |
|
| 207 | + $space = $this->collectWhitespace($string, $start, $end); |
|
| 208 | + if ($space !== null) { |
|
| 209 | + $tokens[] = $space; |
|
| 210 | + $start += strlen($space['content']); |
|
| 211 | + } |
|
| 212 | + } |
|
| 213 | + }//end if |
|
| 214 | + |
|
| 215 | + // Process the rest of the line. |
|
| 216 | + $eol = strpos($string, $eolChar, $start); |
|
| 217 | + if ($eol === false) { |
|
| 218 | + $eol = $end; |
|
| 219 | + } |
|
| 220 | + |
|
| 221 | + if ($eol > $start) { |
|
| 222 | + $tokens[] = [ |
|
| 223 | + 'content' => substr($string, $start, ($eol - $start)), |
|
| 224 | + 'code' => T_DOC_COMMENT_STRING, |
|
| 225 | + 'type' => 'T_DOC_COMMENT_STRING', |
|
| 226 | + ]; |
|
| 227 | + } |
|
| 228 | + |
|
| 229 | + if ($eol !== $end) { |
|
| 230 | + $tokens[] = [ |
|
| 231 | + 'content' => substr($string, $eol, strlen($eolChar)), |
|
| 232 | + 'code' => T_DOC_COMMENT_WHITESPACE, |
|
| 233 | + 'type' => 'T_DOC_COMMENT_WHITESPACE', |
|
| 234 | + ]; |
|
| 235 | + } |
|
| 236 | + |
|
| 237 | + return $tokens; |
|
| 238 | + |
|
| 239 | + }//end processLine() |
|
| 240 | + |
|
| 241 | + |
|
| 242 | + /** |
|
| 243 | + * Collect consecutive whitespace into a single token. |
|
| 244 | + * |
|
| 245 | + * @param string $string The comment string being tokenized. |
|
| 246 | + * @param int $start The position in the string to start processing. |
|
| 247 | + * @param int $end The position in the string to end processing. |
|
| 248 | + * |
|
| 249 | + * @return array|null |
|
| 250 | + */ |
|
| 251 | + private function collectWhitespace($string, $start, $end) |
|
| 252 | + { |
|
| 253 | + $space = ''; |
|
| 254 | + for ($start; $start < $end; $start++) { |
|
| 255 | + if ($string[$start] !== ' ' && $string[$start] !== "\t") { |
|
| 256 | + break; |
|
| 257 | + } |
|
| 258 | + |
|
| 259 | + $space .= $string[$start]; |
|
| 260 | + } |
|
| 261 | + |
|
| 262 | + if ($space === '') { |
|
| 263 | + return null; |
|
| 264 | + } |
|
| 265 | + |
|
| 266 | + $token = [ |
|
| 267 | + 'content' => $space, |
|
| 268 | + 'code' => T_DOC_COMMENT_WHITESPACE, |
|
| 269 | + 'type' => 'T_DOC_COMMENT_WHITESPACE', |
|
| 270 | + ]; |
|
| 271 | + |
|
| 272 | + return $token; |
|
| 273 | + |
|
| 274 | + }//end collectWhitespace() |
|
| 275 | 275 | |
| 276 | 276 | |
| 277 | 277 | }//end class |
@@ -71,7 +71,7 @@ |
||
| 71 | 71 | public function __construct( |
| 72 | 72 | array $scopeTokens, |
| 73 | 73 | array $tokens, |
| 74 | - $listenOutside=false |
|
| 74 | + $listenOutside = false |
|
| 75 | 75 | ) { |
| 76 | 76 | if (empty($scopeTokens) === true) { |
| 77 | 77 | $error = 'The scope tokens list cannot be empty'; |
@@ -32,160 +32,160 @@ |
||
| 32 | 32 | abstract class AbstractScopeSniff implements Sniff |
| 33 | 33 | { |
| 34 | 34 | |
| 35 | - /** |
|
| 36 | - * The token types that this test wishes to listen to within the scope. |
|
| 37 | - * |
|
| 38 | - * @var array |
|
| 39 | - */ |
|
| 40 | - private $tokens = []; |
|
| 41 | - |
|
| 42 | - /** |
|
| 43 | - * The type of scope opener tokens that this test wishes to listen to. |
|
| 44 | - * |
|
| 45 | - * @var string |
|
| 46 | - */ |
|
| 47 | - private $scopeTokens = []; |
|
| 48 | - |
|
| 49 | - /** |
|
| 50 | - * True if this test should fire on tokens outside of the scope. |
|
| 51 | - * |
|
| 52 | - * @var boolean |
|
| 53 | - */ |
|
| 54 | - private $listenOutside = false; |
|
| 55 | - |
|
| 56 | - |
|
| 57 | - /** |
|
| 58 | - * Constructs a new AbstractScopeTest. |
|
| 59 | - * |
|
| 60 | - * @param array $scopeTokens The type of scope the test wishes to listen to. |
|
| 61 | - * @param array $tokens The tokens that the test wishes to listen to |
|
| 62 | - * within the scope. |
|
| 63 | - * @param boolean $listenOutside If true this test will also alert the |
|
| 64 | - * extending class when a token is found outside |
|
| 65 | - * the scope, by calling the |
|
| 66 | - * processTokenOutsideScope method. |
|
| 67 | - * |
|
| 68 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens arrays are empty |
|
| 69 | - * or invalid. |
|
| 70 | - */ |
|
| 71 | - public function __construct( |
|
| 72 | - array $scopeTokens, |
|
| 73 | - array $tokens, |
|
| 74 | - $listenOutside=false |
|
| 75 | - ) { |
|
| 76 | - if (empty($scopeTokens) === true) { |
|
| 77 | - $error = 'The scope tokens list cannot be empty'; |
|
| 78 | - throw new RuntimeException($error); |
|
| 79 | - } |
|
| 80 | - |
|
| 81 | - if (empty($tokens) === true) { |
|
| 82 | - $error = 'The tokens list cannot be empty'; |
|
| 83 | - throw new RuntimeException($error); |
|
| 84 | - } |
|
| 85 | - |
|
| 86 | - $invalidScopeTokens = array_intersect($scopeTokens, $tokens); |
|
| 87 | - if (empty($invalidScopeTokens) === false) { |
|
| 88 | - $invalid = implode(', ', $invalidScopeTokens); |
|
| 89 | - $error = "Scope tokens [$invalid] can't be in the tokens array"; |
|
| 90 | - throw new RuntimeException($error); |
|
| 91 | - } |
|
| 92 | - |
|
| 93 | - $this->listenOutside = $listenOutside; |
|
| 94 | - $this->scopeTokens = array_flip($scopeTokens); |
|
| 95 | - $this->tokens = $tokens; |
|
| 96 | - |
|
| 97 | - }//end __construct() |
|
| 98 | - |
|
| 99 | - |
|
| 100 | - /** |
|
| 101 | - * The method that is called to register the tokens this test wishes to |
|
| 102 | - * listen to. |
|
| 103 | - * |
|
| 104 | - * DO NOT OVERRIDE THIS METHOD. Use the constructor of this class to register |
|
| 105 | - * for the desired tokens and scope. |
|
| 106 | - * |
|
| 107 | - * @return int[] |
|
| 108 | - * @see __constructor() |
|
| 109 | - */ |
|
| 110 | - final public function register() |
|
| 111 | - { |
|
| 112 | - return $this->tokens; |
|
| 113 | - |
|
| 114 | - }//end register() |
|
| 115 | - |
|
| 116 | - |
|
| 117 | - /** |
|
| 118 | - * Processes the tokens that this test is listening for. |
|
| 119 | - * |
|
| 120 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
| 121 | - * @param int $stackPtr The position in the stack where this |
|
| 122 | - * token was found. |
|
| 123 | - * |
|
| 124 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
| 125 | - * called again on the current file until the returned stack |
|
| 126 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
| 127 | - * the rest of the file. |
|
| 128 | - * @see processTokenWithinScope() |
|
| 129 | - */ |
|
| 130 | - final public function process(File $phpcsFile, $stackPtr) |
|
| 131 | - { |
|
| 132 | - $tokens = $phpcsFile->getTokens(); |
|
| 133 | - |
|
| 134 | - $foundScope = false; |
|
| 135 | - $skipTokens = []; |
|
| 136 | - foreach ($tokens[$stackPtr]['conditions'] as $scope => $code) { |
|
| 137 | - if (isset($this->scopeTokens[$code]) === true) { |
|
| 138 | - $skipTokens[] = $this->processTokenWithinScope($phpcsFile, $stackPtr, $scope); |
|
| 139 | - $foundScope = true; |
|
| 140 | - } |
|
| 141 | - } |
|
| 142 | - |
|
| 143 | - if ($this->listenOutside === true && $foundScope === false) { |
|
| 144 | - $skipTokens[] = $this->processTokenOutsideScope($phpcsFile, $stackPtr); |
|
| 145 | - } |
|
| 146 | - |
|
| 147 | - if (empty($skipTokens) === false) { |
|
| 148 | - return min($skipTokens); |
|
| 149 | - } |
|
| 150 | - |
|
| 151 | - return; |
|
| 152 | - |
|
| 153 | - }//end process() |
|
| 154 | - |
|
| 155 | - |
|
| 156 | - /** |
|
| 157 | - * Processes a token that is found within the scope that this test is |
|
| 158 | - * listening to. |
|
| 159 | - * |
|
| 160 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
| 161 | - * @param int $stackPtr The position in the stack where this |
|
| 162 | - * token was found. |
|
| 163 | - * @param int $currScope The position in the tokens array that |
|
| 164 | - * opened the scope that this test is |
|
| 165 | - * listening for. |
|
| 166 | - * |
|
| 167 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
| 168 | - * called again on the current file until the returned stack |
|
| 169 | - * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
| 170 | - * the rest of the file. |
|
| 171 | - */ |
|
| 172 | - abstract protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope); |
|
| 173 | - |
|
| 174 | - |
|
| 175 | - /** |
|
| 176 | - * Processes a token that is found outside the scope that this test is |
|
| 177 | - * listening to. |
|
| 178 | - * |
|
| 179 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
| 180 | - * @param int $stackPtr The position in the stack where this |
|
| 181 | - * token was found. |
|
| 182 | - * |
|
| 183 | - * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
| 184 | - * called again on the current file until the returned stack |
|
| 185 | - * pointer is reached. Return (count($tokens) + 1) to skip |
|
| 186 | - * the rest of the file. |
|
| 187 | - */ |
|
| 188 | - abstract protected function processTokenOutsideScope(File $phpcsFile, $stackPtr); |
|
| 35 | + /** |
|
| 36 | + * The token types that this test wishes to listen to within the scope. |
|
| 37 | + * |
|
| 38 | + * @var array |
|
| 39 | + */ |
|
| 40 | + private $tokens = []; |
|
| 41 | + |
|
| 42 | + /** |
|
| 43 | + * The type of scope opener tokens that this test wishes to listen to. |
|
| 44 | + * |
|
| 45 | + * @var string |
|
| 46 | + */ |
|
| 47 | + private $scopeTokens = []; |
|
| 48 | + |
|
| 49 | + /** |
|
| 50 | + * True if this test should fire on tokens outside of the scope. |
|
| 51 | + * |
|
| 52 | + * @var boolean |
|
| 53 | + */ |
|
| 54 | + private $listenOutside = false; |
|
| 55 | + |
|
| 56 | + |
|
| 57 | + /** |
|
| 58 | + * Constructs a new AbstractScopeTest. |
|
| 59 | + * |
|
| 60 | + * @param array $scopeTokens The type of scope the test wishes to listen to. |
|
| 61 | + * @param array $tokens The tokens that the test wishes to listen to |
|
| 62 | + * within the scope. |
|
| 63 | + * @param boolean $listenOutside If true this test will also alert the |
|
| 64 | + * extending class when a token is found outside |
|
| 65 | + * the scope, by calling the |
|
| 66 | + * processTokenOutsideScope method. |
|
| 67 | + * |
|
| 68 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified tokens arrays are empty |
|
| 69 | + * or invalid. |
|
| 70 | + */ |
|
| 71 | + public function __construct( |
|
| 72 | + array $scopeTokens, |
|
| 73 | + array $tokens, |
|
| 74 | + $listenOutside=false |
|
| 75 | + ) { |
|
| 76 | + if (empty($scopeTokens) === true) { |
|
| 77 | + $error = 'The scope tokens list cannot be empty'; |
|
| 78 | + throw new RuntimeException($error); |
|
| 79 | + } |
|
| 80 | + |
|
| 81 | + if (empty($tokens) === true) { |
|
| 82 | + $error = 'The tokens list cannot be empty'; |
|
| 83 | + throw new RuntimeException($error); |
|
| 84 | + } |
|
| 85 | + |
|
| 86 | + $invalidScopeTokens = array_intersect($scopeTokens, $tokens); |
|
| 87 | + if (empty($invalidScopeTokens) === false) { |
|
| 88 | + $invalid = implode(', ', $invalidScopeTokens); |
|
| 89 | + $error = "Scope tokens [$invalid] can't be in the tokens array"; |
|
| 90 | + throw new RuntimeException($error); |
|
| 91 | + } |
|
| 92 | + |
|
| 93 | + $this->listenOutside = $listenOutside; |
|
| 94 | + $this->scopeTokens = array_flip($scopeTokens); |
|
| 95 | + $this->tokens = $tokens; |
|
| 96 | + |
|
| 97 | + }//end __construct() |
|
| 98 | + |
|
| 99 | + |
|
| 100 | + /** |
|
| 101 | + * The method that is called to register the tokens this test wishes to |
|
| 102 | + * listen to. |
|
| 103 | + * |
|
| 104 | + * DO NOT OVERRIDE THIS METHOD. Use the constructor of this class to register |
|
| 105 | + * for the desired tokens and scope. |
|
| 106 | + * |
|
| 107 | + * @return int[] |
|
| 108 | + * @see __constructor() |
|
| 109 | + */ |
|
| 110 | + final public function register() |
|
| 111 | + { |
|
| 112 | + return $this->tokens; |
|
| 113 | + |
|
| 114 | + }//end register() |
|
| 115 | + |
|
| 116 | + |
|
| 117 | + /** |
|
| 118 | + * Processes the tokens that this test is listening for. |
|
| 119 | + * |
|
| 120 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
| 121 | + * @param int $stackPtr The position in the stack where this |
|
| 122 | + * token was found. |
|
| 123 | + * |
|
| 124 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
| 125 | + * called again on the current file until the returned stack |
|
| 126 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
| 127 | + * the rest of the file. |
|
| 128 | + * @see processTokenWithinScope() |
|
| 129 | + */ |
|
| 130 | + final public function process(File $phpcsFile, $stackPtr) |
|
| 131 | + { |
|
| 132 | + $tokens = $phpcsFile->getTokens(); |
|
| 133 | + |
|
| 134 | + $foundScope = false; |
|
| 135 | + $skipTokens = []; |
|
| 136 | + foreach ($tokens[$stackPtr]['conditions'] as $scope => $code) { |
|
| 137 | + if (isset($this->scopeTokens[$code]) === true) { |
|
| 138 | + $skipTokens[] = $this->processTokenWithinScope($phpcsFile, $stackPtr, $scope); |
|
| 139 | + $foundScope = true; |
|
| 140 | + } |
|
| 141 | + } |
|
| 142 | + |
|
| 143 | + if ($this->listenOutside === true && $foundScope === false) { |
|
| 144 | + $skipTokens[] = $this->processTokenOutsideScope($phpcsFile, $stackPtr); |
|
| 145 | + } |
|
| 146 | + |
|
| 147 | + if (empty($skipTokens) === false) { |
|
| 148 | + return min($skipTokens); |
|
| 149 | + } |
|
| 150 | + |
|
| 151 | + return; |
|
| 152 | + |
|
| 153 | + }//end process() |
|
| 154 | + |
|
| 155 | + |
|
| 156 | + /** |
|
| 157 | + * Processes a token that is found within the scope that this test is |
|
| 158 | + * listening to. |
|
| 159 | + * |
|
| 160 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
| 161 | + * @param int $stackPtr The position in the stack where this |
|
| 162 | + * token was found. |
|
| 163 | + * @param int $currScope The position in the tokens array that |
|
| 164 | + * opened the scope that this test is |
|
| 165 | + * listening for. |
|
| 166 | + * |
|
| 167 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
| 168 | + * called again on the current file until the returned stack |
|
| 169 | + * pointer is reached. Return ($phpcsFile->numTokens + 1) to skip |
|
| 170 | + * the rest of the file. |
|
| 171 | + */ |
|
| 172 | + abstract protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope); |
|
| 173 | + |
|
| 174 | + |
|
| 175 | + /** |
|
| 176 | + * Processes a token that is found outside the scope that this test is |
|
| 177 | + * listening to. |
|
| 178 | + * |
|
| 179 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. |
|
| 180 | + * @param int $stackPtr The position in the stack where this |
|
| 181 | + * token was found. |
|
| 182 | + * |
|
| 183 | + * @return void|int Optionally returns a stack pointer. The sniff will not be |
|
| 184 | + * called again on the current file until the returned stack |
|
| 185 | + * pointer is reached. Return (count($tokens) + 1) to skip |
|
| 186 | + * the rest of the file. |
|
| 187 | + */ |
|
| 188 | + abstract protected function processTokenOutsideScope(File $phpcsFile, $stackPtr); |
|
| 189 | 189 | |
| 190 | 190 | |
| 191 | 191 | }//end class |
@@ -866,26 +866,26 @@ |
||
| 866 | 866 | $nestedBraces = 0; |
| 867 | 867 | for ($start = $from; $start >= 0; $start--) { |
| 868 | 868 | switch ($pattern[$start]) { |
| 869 | - case '(': |
|
| 870 | - if ($nestedParenthesis === 0) { |
|
| 871 | - $skip['to'] = 'parenthesis_closer'; |
|
| 872 | - } |
|
| 873 | - |
|
| 874 | - $nestedParenthesis--; |
|
| 875 | - break; |
|
| 876 | - case '{': |
|
| 877 | - if ($nestedBraces === 0) { |
|
| 878 | - $skip['to'] = 'scope_closer'; |
|
| 879 | - } |
|
| 880 | - |
|
| 881 | - $nestedBraces--; |
|
| 882 | - break; |
|
| 883 | - case '}': |
|
| 884 | - $nestedBraces++; |
|
| 885 | - break; |
|
| 886 | - case ')': |
|
| 887 | - $nestedParenthesis++; |
|
| 888 | - break; |
|
| 869 | + case '(': |
|
| 870 | + if ($nestedParenthesis === 0) { |
|
| 871 | + $skip['to'] = 'parenthesis_closer'; |
|
| 872 | + } |
|
| 873 | + |
|
| 874 | + $nestedParenthesis--; |
|
| 875 | + break; |
|
| 876 | + case '{': |
|
| 877 | + if ($nestedBraces === 0) { |
|
| 878 | + $skip['to'] = 'scope_closer'; |
|
| 879 | + } |
|
| 880 | + |
|
| 881 | + $nestedBraces--; |
|
| 882 | + break; |
|
| 883 | + case '}': |
|
| 884 | + $nestedBraces++; |
|
| 885 | + break; |
|
| 886 | + case ')': |
|
| 887 | + $nestedParenthesis++; |
|
| 888 | + break; |
|
| 889 | 889 | }//end switch |
| 890 | 890 | |
| 891 | 891 | if (isset($skip['to']) === true) { |
@@ -61,7 +61,7 @@ |
||
| 61 | 61 | * |
| 62 | 62 | * @param boolean $ignoreComments If true, comments will be ignored. |
| 63 | 63 | */ |
| 64 | - public function __construct($ignoreComments=null) |
|
| 64 | + public function __construct($ignoreComments = null) |
|
| 65 | 65 | { |
| 66 | 66 | // This is here for backwards compatibility. |
| 67 | 67 | if ($ignoreComments !== null) { |
@@ -17,920 +17,920 @@ |
||
| 17 | 17 | abstract class AbstractPatternSniff implements Sniff |
| 18 | 18 | { |
| 19 | 19 | |
| 20 | - /** |
|
| 21 | - * If true, comments will be ignored if they are found in the code. |
|
| 22 | - * |
|
| 23 | - * @var boolean |
|
| 24 | - */ |
|
| 25 | - public $ignoreComments = false; |
|
| 26 | - |
|
| 27 | - /** |
|
| 28 | - * The current file being checked. |
|
| 29 | - * |
|
| 30 | - * @var string |
|
| 31 | - */ |
|
| 32 | - protected $currFile = ''; |
|
| 33 | - |
|
| 34 | - /** |
|
| 35 | - * The parsed patterns array. |
|
| 36 | - * |
|
| 37 | - * @var array |
|
| 38 | - */ |
|
| 39 | - private $parsedPatterns = []; |
|
| 40 | - |
|
| 41 | - /** |
|
| 42 | - * Tokens that this sniff wishes to process outside of the patterns. |
|
| 43 | - * |
|
| 44 | - * @var int[] |
|
| 45 | - * @see registerSupplementary() |
|
| 46 | - * @see processSupplementary() |
|
| 47 | - */ |
|
| 48 | - private $supplementaryTokens = []; |
|
| 49 | - |
|
| 50 | - /** |
|
| 51 | - * Positions in the stack where errors have occurred. |
|
| 52 | - * |
|
| 53 | - * @var array<int, bool> |
|
| 54 | - */ |
|
| 55 | - private $errorPos = []; |
|
| 56 | - |
|
| 57 | - |
|
| 58 | - /** |
|
| 59 | - * Constructs a AbstractPatternSniff. |
|
| 60 | - * |
|
| 61 | - * @param boolean $ignoreComments If true, comments will be ignored. |
|
| 62 | - */ |
|
| 63 | - public function __construct($ignoreComments=null) |
|
| 64 | - { |
|
| 65 | - // This is here for backwards compatibility. |
|
| 66 | - if ($ignoreComments !== null) { |
|
| 67 | - $this->ignoreComments = $ignoreComments; |
|
| 68 | - } |
|
| 69 | - |
|
| 70 | - $this->supplementaryTokens = $this->registerSupplementary(); |
|
| 71 | - |
|
| 72 | - }//end __construct() |
|
| 73 | - |
|
| 74 | - |
|
| 75 | - /** |
|
| 76 | - * Registers the tokens to listen to. |
|
| 77 | - * |
|
| 78 | - * Classes extending <i>AbstractPatternTest</i> should implement the |
|
| 79 | - * <i>getPatterns()</i> method to register the patterns they wish to test. |
|
| 80 | - * |
|
| 81 | - * @return int[] |
|
| 82 | - * @see process() |
|
| 83 | - */ |
|
| 84 | - final public function register() |
|
| 85 | - { |
|
| 86 | - $listenTypes = []; |
|
| 87 | - $patterns = $this->getPatterns(); |
|
| 88 | - |
|
| 89 | - foreach ($patterns as $pattern) { |
|
| 90 | - $parsedPattern = $this->parse($pattern); |
|
| 91 | - |
|
| 92 | - // Find a token position in the pattern that we can use |
|
| 93 | - // for a listener token. |
|
| 94 | - $pos = $this->getListenerTokenPos($parsedPattern); |
|
| 95 | - $tokenType = $parsedPattern[$pos]['token']; |
|
| 96 | - $listenTypes[] = $tokenType; |
|
| 97 | - |
|
| 98 | - $patternArray = [ |
|
| 99 | - 'listen_pos' => $pos, |
|
| 100 | - 'pattern' => $parsedPattern, |
|
| 101 | - 'pattern_code' => $pattern, |
|
| 102 | - ]; |
|
| 103 | - |
|
| 104 | - if (isset($this->parsedPatterns[$tokenType]) === false) { |
|
| 105 | - $this->parsedPatterns[$tokenType] = []; |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - $this->parsedPatterns[$tokenType][] = $patternArray; |
|
| 109 | - }//end foreach |
|
| 110 | - |
|
| 111 | - return array_unique(array_merge($listenTypes, $this->supplementaryTokens)); |
|
| 112 | - |
|
| 113 | - }//end register() |
|
| 114 | - |
|
| 115 | - |
|
| 116 | - /** |
|
| 117 | - * Returns the token types that the specified pattern is checking for. |
|
| 118 | - * |
|
| 119 | - * Returned array is in the format: |
|
| 120 | - * <code> |
|
| 121 | - * array( |
|
| 122 | - * T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token |
|
| 123 | - * // should occur in the pattern. |
|
| 124 | - * ); |
|
| 125 | - * </code> |
|
| 126 | - * |
|
| 127 | - * @param array $pattern The parsed pattern to find the acquire the token |
|
| 128 | - * types from. |
|
| 129 | - * |
|
| 130 | - * @return array<int, int> |
|
| 131 | - */ |
|
| 132 | - private function getPatternTokenTypes($pattern) |
|
| 133 | - { |
|
| 134 | - $tokenTypes = []; |
|
| 135 | - foreach ($pattern as $pos => $patternInfo) { |
|
| 136 | - if ($patternInfo['type'] === 'token') { |
|
| 137 | - if (isset($tokenTypes[$patternInfo['token']]) === false) { |
|
| 138 | - $tokenTypes[$patternInfo['token']] = $pos; |
|
| 139 | - } |
|
| 140 | - } |
|
| 141 | - } |
|
| 142 | - |
|
| 143 | - return $tokenTypes; |
|
| 144 | - |
|
| 145 | - }//end getPatternTokenTypes() |
|
| 146 | - |
|
| 147 | - |
|
| 148 | - /** |
|
| 149 | - * Returns the position in the pattern that this test should register as |
|
| 150 | - * a listener for the pattern. |
|
| 151 | - * |
|
| 152 | - * @param array $pattern The pattern to acquire the listener for. |
|
| 153 | - * |
|
| 154 | - * @return int The position in the pattern that this test should register |
|
| 155 | - * as the listener. |
|
| 156 | - * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If we could not determine a token to listen for. |
|
| 157 | - */ |
|
| 158 | - private function getListenerTokenPos($pattern) |
|
| 159 | - { |
|
| 160 | - $tokenTypes = $this->getPatternTokenTypes($pattern); |
|
| 161 | - $tokenCodes = array_keys($tokenTypes); |
|
| 162 | - $token = Tokens::getHighestWeightedToken($tokenCodes); |
|
| 163 | - |
|
| 164 | - // If we could not get a token. |
|
| 165 | - if ($token === false) { |
|
| 166 | - $error = 'Could not determine a token to listen for'; |
|
| 167 | - throw new RuntimeException($error); |
|
| 168 | - } |
|
| 169 | - |
|
| 170 | - return $tokenTypes[$token]; |
|
| 171 | - |
|
| 172 | - }//end getListenerTokenPos() |
|
| 173 | - |
|
| 174 | - |
|
| 175 | - /** |
|
| 176 | - * Processes the test. |
|
| 177 | - * |
|
| 178 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
| 179 | - * token occurred. |
|
| 180 | - * @param int $stackPtr The position in the tokens stack |
|
| 181 | - * where the listening token type |
|
| 182 | - * was found. |
|
| 183 | - * |
|
| 184 | - * @return void |
|
| 185 | - * @see register() |
|
| 186 | - */ |
|
| 187 | - final public function process(File $phpcsFile, $stackPtr) |
|
| 188 | - { |
|
| 189 | - $file = $phpcsFile->getFilename(); |
|
| 190 | - if ($this->currFile !== $file) { |
|
| 191 | - // We have changed files, so clean up. |
|
| 192 | - $this->errorPos = []; |
|
| 193 | - $this->currFile = $file; |
|
| 194 | - } |
|
| 195 | - |
|
| 196 | - $tokens = $phpcsFile->getTokens(); |
|
| 197 | - |
|
| 198 | - if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === true) { |
|
| 199 | - $this->processSupplementary($phpcsFile, $stackPtr); |
|
| 200 | - } |
|
| 201 | - |
|
| 202 | - $type = $tokens[$stackPtr]['code']; |
|
| 203 | - |
|
| 204 | - // If the type is not set, then it must have been a token registered |
|
| 205 | - // with registerSupplementary(). |
|
| 206 | - if (isset($this->parsedPatterns[$type]) === false) { |
|
| 207 | - return; |
|
| 208 | - } |
|
| 209 | - |
|
| 210 | - $allErrors = []; |
|
| 211 | - |
|
| 212 | - // Loop over each pattern that is listening to the current token type |
|
| 213 | - // that we are processing. |
|
| 214 | - foreach ($this->parsedPatterns[$type] as $patternInfo) { |
|
| 215 | - // If processPattern returns false, then the pattern that we are |
|
| 216 | - // checking the code with must not be designed to check that code. |
|
| 217 | - $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr); |
|
| 218 | - if ($errors === false) { |
|
| 219 | - // The pattern didn't match. |
|
| 220 | - continue; |
|
| 221 | - } else if (empty($errors) === true) { |
|
| 222 | - // The pattern matched, but there were no errors. |
|
| 223 | - break; |
|
| 224 | - } |
|
| 225 | - |
|
| 226 | - foreach ($errors as $stackPtr => $error) { |
|
| 227 | - if (isset($this->errorPos[$stackPtr]) === false) { |
|
| 228 | - $this->errorPos[$stackPtr] = true; |
|
| 229 | - $allErrors[$stackPtr] = $error; |
|
| 230 | - } |
|
| 231 | - } |
|
| 232 | - } |
|
| 233 | - |
|
| 234 | - foreach ($allErrors as $stackPtr => $error) { |
|
| 235 | - $phpcsFile->addError($error, $stackPtr, 'Found'); |
|
| 236 | - } |
|
| 237 | - |
|
| 238 | - }//end process() |
|
| 239 | - |
|
| 240 | - |
|
| 241 | - /** |
|
| 242 | - * Processes the pattern and verifies the code at $stackPtr. |
|
| 243 | - * |
|
| 244 | - * @param array $patternInfo Information about the pattern used |
|
| 245 | - * for checking, which includes are |
|
| 246 | - * parsed token representation of the |
|
| 247 | - * pattern. |
|
| 248 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
| 249 | - * token occurred. |
|
| 250 | - * @param int $stackPtr The position in the tokens stack where |
|
| 251 | - * the listening token type was found. |
|
| 252 | - * |
|
| 253 | - * @return array |
|
| 254 | - */ |
|
| 255 | - protected function processPattern($patternInfo, File $phpcsFile, $stackPtr) |
|
| 256 | - { |
|
| 257 | - $tokens = $phpcsFile->getTokens(); |
|
| 258 | - $pattern = $patternInfo['pattern']; |
|
| 259 | - $patternCode = $patternInfo['pattern_code']; |
|
| 260 | - $errors = []; |
|
| 261 | - $found = ''; |
|
| 262 | - |
|
| 263 | - $ignoreTokens = [T_WHITESPACE => T_WHITESPACE]; |
|
| 264 | - if ($this->ignoreComments === true) { |
|
| 265 | - $ignoreTokens += Tokens::$commentTokens; |
|
| 266 | - } |
|
| 267 | - |
|
| 268 | - $origStackPtr = $stackPtr; |
|
| 269 | - $hasError = false; |
|
| 270 | - |
|
| 271 | - if ($patternInfo['listen_pos'] > 0) { |
|
| 272 | - $stackPtr--; |
|
| 273 | - |
|
| 274 | - for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) { |
|
| 275 | - if ($pattern[$i]['type'] === 'token') { |
|
| 276 | - if ($pattern[$i]['token'] === T_WHITESPACE) { |
|
| 277 | - if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
| 278 | - $found = $tokens[$stackPtr]['content'].$found; |
|
| 279 | - } |
|
| 280 | - |
|
| 281 | - // Only check the size of the whitespace if this is not |
|
| 282 | - // the first token. We don't care about the size of |
|
| 283 | - // leading whitespace, just that there is some. |
|
| 284 | - if ($i !== 0) { |
|
| 285 | - if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) { |
|
| 286 | - $hasError = true; |
|
| 287 | - } |
|
| 288 | - } |
|
| 289 | - } else { |
|
| 290 | - // Check to see if this important token is the same as the |
|
| 291 | - // previous important token in the pattern. If it is not, |
|
| 292 | - // then the pattern cannot be for this piece of code. |
|
| 293 | - $prev = $phpcsFile->findPrevious( |
|
| 294 | - $ignoreTokens, |
|
| 295 | - $stackPtr, |
|
| 296 | - null, |
|
| 297 | - true |
|
| 298 | - ); |
|
| 299 | - |
|
| 300 | - if ($prev === false |
|
| 301 | - || $tokens[$prev]['code'] !== $pattern[$i]['token'] |
|
| 302 | - ) { |
|
| 303 | - return false; |
|
| 304 | - } |
|
| 305 | - |
|
| 306 | - // If we skipped past some whitespace tokens, then add them |
|
| 307 | - // to the found string. |
|
| 308 | - $tokenContent = $phpcsFile->getTokensAsString( |
|
| 309 | - ($prev + 1), |
|
| 310 | - ($stackPtr - $prev - 1) |
|
| 311 | - ); |
|
| 312 | - |
|
| 313 | - $found = $tokens[$prev]['content'].$tokenContent.$found; |
|
| 314 | - |
|
| 315 | - if (isset($pattern[($i - 1)]) === true |
|
| 316 | - && $pattern[($i - 1)]['type'] === 'skip' |
|
| 317 | - ) { |
|
| 318 | - $stackPtr = $prev; |
|
| 319 | - } else { |
|
| 320 | - $stackPtr = ($prev - 1); |
|
| 321 | - } |
|
| 322 | - }//end if |
|
| 323 | - } else if ($pattern[$i]['type'] === 'skip') { |
|
| 324 | - // Skip to next piece of relevant code. |
|
| 325 | - if ($pattern[$i]['to'] === 'parenthesis_closer') { |
|
| 326 | - $to = 'parenthesis_opener'; |
|
| 327 | - } else { |
|
| 328 | - $to = 'scope_opener'; |
|
| 329 | - } |
|
| 330 | - |
|
| 331 | - // Find the previous opener. |
|
| 332 | - $next = $phpcsFile->findPrevious( |
|
| 333 | - $ignoreTokens, |
|
| 334 | - $stackPtr, |
|
| 335 | - null, |
|
| 336 | - true |
|
| 337 | - ); |
|
| 338 | - |
|
| 339 | - if ($next === false || isset($tokens[$next][$to]) === false) { |
|
| 340 | - // If there was not opener, then we must be |
|
| 341 | - // using the wrong pattern. |
|
| 342 | - return false; |
|
| 343 | - } |
|
| 344 | - |
|
| 345 | - if ($to === 'parenthesis_opener') { |
|
| 346 | - $found = '{'.$found; |
|
| 347 | - } else { |
|
| 348 | - $found = '('.$found; |
|
| 349 | - } |
|
| 350 | - |
|
| 351 | - $found = '...'.$found; |
|
| 352 | - |
|
| 353 | - // Skip to the opening token. |
|
| 354 | - $stackPtr = ($tokens[$next][$to] - 1); |
|
| 355 | - } else if ($pattern[$i]['type'] === 'string') { |
|
| 356 | - $found = 'abc'; |
|
| 357 | - } else if ($pattern[$i]['type'] === 'newline') { |
|
| 358 | - if ($this->ignoreComments === true |
|
| 359 | - && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true |
|
| 360 | - ) { |
|
| 361 | - $startComment = $phpcsFile->findPrevious( |
|
| 362 | - Tokens::$commentTokens, |
|
| 363 | - ($stackPtr - 1), |
|
| 364 | - null, |
|
| 365 | - true |
|
| 366 | - ); |
|
| 367 | - |
|
| 368 | - if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) { |
|
| 369 | - $startComment++; |
|
| 370 | - } |
|
| 371 | - |
|
| 372 | - $tokenContent = $phpcsFile->getTokensAsString( |
|
| 373 | - $startComment, |
|
| 374 | - ($stackPtr - $startComment + 1) |
|
| 375 | - ); |
|
| 376 | - |
|
| 377 | - $found = $tokenContent.$found; |
|
| 378 | - $stackPtr = ($startComment - 1); |
|
| 379 | - } |
|
| 380 | - |
|
| 381 | - if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
| 382 | - if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) { |
|
| 383 | - $found = $tokens[$stackPtr]['content'].$found; |
|
| 384 | - |
|
| 385 | - // This may just be an indent that comes after a newline |
|
| 386 | - // so check the token before to make sure. If it is a newline, we |
|
| 387 | - // can ignore the error here. |
|
| 388 | - if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar) |
|
| 389 | - && ($this->ignoreComments === true |
|
| 390 | - && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false) |
|
| 391 | - ) { |
|
| 392 | - $hasError = true; |
|
| 393 | - } else { |
|
| 394 | - $stackPtr--; |
|
| 395 | - } |
|
| 396 | - } else { |
|
| 397 | - $found = 'EOL'.$found; |
|
| 398 | - } |
|
| 399 | - } else { |
|
| 400 | - $found = $tokens[$stackPtr]['content'].$found; |
|
| 401 | - $hasError = true; |
|
| 402 | - }//end if |
|
| 403 | - |
|
| 404 | - if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') { |
|
| 405 | - // Make sure they only have 1 newline. |
|
| 406 | - $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true); |
|
| 407 | - if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) { |
|
| 408 | - $hasError = true; |
|
| 409 | - } |
|
| 410 | - } |
|
| 411 | - }//end if |
|
| 412 | - }//end for |
|
| 413 | - }//end if |
|
| 414 | - |
|
| 415 | - $stackPtr = $origStackPtr; |
|
| 416 | - $lastAddedStackPtr = null; |
|
| 417 | - $patternLen = count($pattern); |
|
| 418 | - |
|
| 419 | - for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) { |
|
| 420 | - if (isset($tokens[$stackPtr]) === false) { |
|
| 421 | - break; |
|
| 422 | - } |
|
| 423 | - |
|
| 424 | - if ($pattern[$i]['type'] === 'token') { |
|
| 425 | - if ($pattern[$i]['token'] === T_WHITESPACE) { |
|
| 426 | - if ($this->ignoreComments === true) { |
|
| 427 | - // If we are ignoring comments, check to see if this current |
|
| 428 | - // token is a comment. If so skip it. |
|
| 429 | - if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) { |
|
| 430 | - continue; |
|
| 431 | - } |
|
| 432 | - |
|
| 433 | - // If the next token is a comment, the we need to skip the |
|
| 434 | - // current token as we should allow a space before a |
|
| 435 | - // comment for readability. |
|
| 436 | - if (isset($tokens[($stackPtr + 1)]) === true |
|
| 437 | - && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true |
|
| 438 | - ) { |
|
| 439 | - continue; |
|
| 440 | - } |
|
| 441 | - } |
|
| 442 | - |
|
| 443 | - $tokenContent = ''; |
|
| 444 | - if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
| 445 | - if (isset($pattern[($i + 1)]) === false) { |
|
| 446 | - // This is the last token in the pattern, so just compare |
|
| 447 | - // the next token of content. |
|
| 448 | - $tokenContent = $tokens[$stackPtr]['content']; |
|
| 449 | - } else { |
|
| 450 | - // Get all the whitespace to the next token. |
|
| 451 | - $next = $phpcsFile->findNext( |
|
| 452 | - Tokens::$emptyTokens, |
|
| 453 | - $stackPtr, |
|
| 454 | - null, |
|
| 455 | - true |
|
| 456 | - ); |
|
| 457 | - |
|
| 458 | - $tokenContent = $phpcsFile->getTokensAsString( |
|
| 459 | - $stackPtr, |
|
| 460 | - ($next - $stackPtr) |
|
| 461 | - ); |
|
| 462 | - |
|
| 463 | - $lastAddedStackPtr = $stackPtr; |
|
| 464 | - $stackPtr = $next; |
|
| 465 | - }//end if |
|
| 466 | - |
|
| 467 | - if ($stackPtr !== $lastAddedStackPtr) { |
|
| 468 | - $found .= $tokenContent; |
|
| 469 | - } |
|
| 470 | - } else { |
|
| 471 | - if ($stackPtr !== $lastAddedStackPtr) { |
|
| 472 | - $found .= $tokens[$stackPtr]['content']; |
|
| 473 | - $lastAddedStackPtr = $stackPtr; |
|
| 474 | - } |
|
| 475 | - }//end if |
|
| 476 | - |
|
| 477 | - if (isset($pattern[($i + 1)]) === true |
|
| 478 | - && $pattern[($i + 1)]['type'] === 'skip' |
|
| 479 | - ) { |
|
| 480 | - // The next token is a skip token, so we just need to make |
|
| 481 | - // sure the whitespace we found has *at least* the |
|
| 482 | - // whitespace required. |
|
| 483 | - if (strpos($tokenContent, $pattern[$i]['value']) !== 0) { |
|
| 484 | - $hasError = true; |
|
| 485 | - } |
|
| 486 | - } else { |
|
| 487 | - if ($tokenContent !== $pattern[$i]['value']) { |
|
| 488 | - $hasError = true; |
|
| 489 | - } |
|
| 490 | - } |
|
| 491 | - } else { |
|
| 492 | - // Check to see if this important token is the same as the |
|
| 493 | - // next important token in the pattern. If it is not, then |
|
| 494 | - // the pattern cannot be for this piece of code. |
|
| 495 | - $next = $phpcsFile->findNext( |
|
| 496 | - $ignoreTokens, |
|
| 497 | - $stackPtr, |
|
| 498 | - null, |
|
| 499 | - true |
|
| 500 | - ); |
|
| 501 | - |
|
| 502 | - if ($next === false |
|
| 503 | - || $tokens[$next]['code'] !== $pattern[$i]['token'] |
|
| 504 | - ) { |
|
| 505 | - // The next important token did not match the pattern. |
|
| 506 | - return false; |
|
| 507 | - } |
|
| 508 | - |
|
| 509 | - if ($lastAddedStackPtr !== null) { |
|
| 510 | - if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET |
|
| 511 | - || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) |
|
| 512 | - && isset($tokens[$next]['scope_condition']) === true |
|
| 513 | - && $tokens[$next]['scope_condition'] > $lastAddedStackPtr |
|
| 514 | - ) { |
|
| 515 | - // This is a brace, but the owner of it is after the current |
|
| 516 | - // token, which means it does not belong to any token in |
|
| 517 | - // our pattern. This means the pattern is not for us. |
|
| 518 | - return false; |
|
| 519 | - } |
|
| 520 | - |
|
| 521 | - if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS |
|
| 522 | - || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS) |
|
| 523 | - && isset($tokens[$next]['parenthesis_owner']) === true |
|
| 524 | - && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr |
|
| 525 | - ) { |
|
| 526 | - // This is a bracket, but the owner of it is after the current |
|
| 527 | - // token, which means it does not belong to any token in |
|
| 528 | - // our pattern. This means the pattern is not for us. |
|
| 529 | - return false; |
|
| 530 | - } |
|
| 531 | - }//end if |
|
| 532 | - |
|
| 533 | - // If we skipped past some whitespace tokens, then add them |
|
| 534 | - // to the found string. |
|
| 535 | - if (($next - $stackPtr) > 0) { |
|
| 536 | - $hasComment = false; |
|
| 537 | - for ($j = $stackPtr; $j < $next; $j++) { |
|
| 538 | - $found .= $tokens[$j]['content']; |
|
| 539 | - if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) { |
|
| 540 | - $hasComment = true; |
|
| 541 | - } |
|
| 542 | - } |
|
| 543 | - |
|
| 544 | - // If we are not ignoring comments, this additional |
|
| 545 | - // whitespace or comment is not allowed. If we are |
|
| 546 | - // ignoring comments, there needs to be at least one |
|
| 547 | - // comment for this to be allowed. |
|
| 548 | - if ($this->ignoreComments === false |
|
| 549 | - || ($this->ignoreComments === true |
|
| 550 | - && $hasComment === false) |
|
| 551 | - ) { |
|
| 552 | - $hasError = true; |
|
| 553 | - } |
|
| 554 | - |
|
| 555 | - // Even when ignoring comments, we are not allowed to include |
|
| 556 | - // newlines without the pattern specifying them, so |
|
| 557 | - // everything should be on the same line. |
|
| 558 | - if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { |
|
| 559 | - $hasError = true; |
|
| 560 | - } |
|
| 561 | - }//end if |
|
| 562 | - |
|
| 563 | - if ($next !== $lastAddedStackPtr) { |
|
| 564 | - $found .= $tokens[$next]['content']; |
|
| 565 | - $lastAddedStackPtr = $next; |
|
| 566 | - } |
|
| 567 | - |
|
| 568 | - if (isset($pattern[($i + 1)]) === true |
|
| 569 | - && $pattern[($i + 1)]['type'] === 'skip' |
|
| 570 | - ) { |
|
| 571 | - $stackPtr = $next; |
|
| 572 | - } else { |
|
| 573 | - $stackPtr = ($next + 1); |
|
| 574 | - } |
|
| 575 | - }//end if |
|
| 576 | - } else if ($pattern[$i]['type'] === 'skip') { |
|
| 577 | - if ($pattern[$i]['to'] === 'unknown') { |
|
| 578 | - $next = $phpcsFile->findNext( |
|
| 579 | - $pattern[($i + 1)]['token'], |
|
| 580 | - $stackPtr |
|
| 581 | - ); |
|
| 582 | - |
|
| 583 | - if ($next === false) { |
|
| 584 | - // Couldn't find the next token, so we must |
|
| 585 | - // be using the wrong pattern. |
|
| 586 | - return false; |
|
| 587 | - } |
|
| 588 | - |
|
| 589 | - $found .= '...'; |
|
| 590 | - $stackPtr = $next; |
|
| 591 | - } else { |
|
| 592 | - // Find the previous opener. |
|
| 593 | - $next = $phpcsFile->findPrevious( |
|
| 594 | - Tokens::$blockOpeners, |
|
| 595 | - $stackPtr |
|
| 596 | - ); |
|
| 597 | - |
|
| 598 | - if ($next === false |
|
| 599 | - || isset($tokens[$next][$pattern[$i]['to']]) === false |
|
| 600 | - ) { |
|
| 601 | - // If there was not opener, then we must |
|
| 602 | - // be using the wrong pattern. |
|
| 603 | - return false; |
|
| 604 | - } |
|
| 605 | - |
|
| 606 | - $found .= '...'; |
|
| 607 | - if ($pattern[$i]['to'] === 'parenthesis_closer') { |
|
| 608 | - $found .= ')'; |
|
| 609 | - } else { |
|
| 610 | - $found .= '}'; |
|
| 611 | - } |
|
| 612 | - |
|
| 613 | - // Skip to the closing token. |
|
| 614 | - $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1); |
|
| 615 | - }//end if |
|
| 616 | - } else if ($pattern[$i]['type'] === 'string') { |
|
| 617 | - if ($tokens[$stackPtr]['code'] !== T_STRING) { |
|
| 618 | - $hasError = true; |
|
| 619 | - } |
|
| 620 | - |
|
| 621 | - if ($stackPtr !== $lastAddedStackPtr) { |
|
| 622 | - $found .= 'abc'; |
|
| 623 | - $lastAddedStackPtr = $stackPtr; |
|
| 624 | - } |
|
| 625 | - |
|
| 626 | - $stackPtr++; |
|
| 627 | - } else if ($pattern[$i]['type'] === 'newline') { |
|
| 628 | - // Find the next token that contains a newline character. |
|
| 629 | - $newline = 0; |
|
| 630 | - for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) { |
|
| 631 | - if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) { |
|
| 632 | - $newline = $j; |
|
| 633 | - break; |
|
| 634 | - } |
|
| 635 | - } |
|
| 636 | - |
|
| 637 | - if ($newline === 0) { |
|
| 638 | - // We didn't find a newline character in the rest of the file. |
|
| 639 | - $next = ($phpcsFile->numTokens - 1); |
|
| 640 | - $hasError = true; |
|
| 641 | - } else { |
|
| 642 | - if ($this->ignoreComments === false) { |
|
| 643 | - // The newline character cannot be part of a comment. |
|
| 644 | - if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) { |
|
| 645 | - $hasError = true; |
|
| 646 | - } |
|
| 647 | - } |
|
| 648 | - |
|
| 649 | - if ($newline === $stackPtr) { |
|
| 650 | - $next = ($stackPtr + 1); |
|
| 651 | - } else { |
|
| 652 | - // Check that there were no significant tokens that we |
|
| 653 | - // skipped over to find our newline character. |
|
| 654 | - $next = $phpcsFile->findNext( |
|
| 655 | - $ignoreTokens, |
|
| 656 | - $stackPtr, |
|
| 657 | - null, |
|
| 658 | - true |
|
| 659 | - ); |
|
| 660 | - |
|
| 661 | - if ($next < $newline) { |
|
| 662 | - // We skipped a non-ignored token. |
|
| 663 | - $hasError = true; |
|
| 664 | - } else { |
|
| 665 | - $next = ($newline + 1); |
|
| 666 | - } |
|
| 667 | - } |
|
| 668 | - }//end if |
|
| 669 | - |
|
| 670 | - if ($stackPtr !== $lastAddedStackPtr) { |
|
| 671 | - $found .= $phpcsFile->getTokensAsString( |
|
| 672 | - $stackPtr, |
|
| 673 | - ($next - $stackPtr) |
|
| 674 | - ); |
|
| 675 | - |
|
| 676 | - $lastAddedStackPtr = ($next - 1); |
|
| 677 | - } |
|
| 678 | - |
|
| 679 | - $stackPtr = $next; |
|
| 680 | - }//end if |
|
| 681 | - }//end for |
|
| 682 | - |
|
| 683 | - if ($hasError === true) { |
|
| 684 | - $error = $this->prepareError($found, $patternCode); |
|
| 685 | - $errors[$origStackPtr] = $error; |
|
| 686 | - } |
|
| 687 | - |
|
| 688 | - return $errors; |
|
| 689 | - |
|
| 690 | - }//end processPattern() |
|
| 691 | - |
|
| 692 | - |
|
| 693 | - /** |
|
| 694 | - * Prepares an error for the specified patternCode. |
|
| 695 | - * |
|
| 696 | - * @param string $found The actual found string in the code. |
|
| 697 | - * @param string $patternCode The expected pattern code. |
|
| 698 | - * |
|
| 699 | - * @return string The error message. |
|
| 700 | - */ |
|
| 701 | - protected function prepareError($found, $patternCode) |
|
| 702 | - { |
|
| 703 | - $found = str_replace("\r\n", '\n', $found); |
|
| 704 | - $found = str_replace("\n", '\n', $found); |
|
| 705 | - $found = str_replace("\r", '\n', $found); |
|
| 706 | - $found = str_replace("\t", '\t', $found); |
|
| 707 | - $found = str_replace('EOL', '\n', $found); |
|
| 708 | - $expected = str_replace('EOL', '\n', $patternCode); |
|
| 709 | - |
|
| 710 | - $error = "Expected \"$expected\"; found \"$found\""; |
|
| 711 | - |
|
| 712 | - return $error; |
|
| 713 | - |
|
| 714 | - }//end prepareError() |
|
| 715 | - |
|
| 716 | - |
|
| 717 | - /** |
|
| 718 | - * Returns the patterns that should be checked. |
|
| 719 | - * |
|
| 720 | - * @return string[] |
|
| 721 | - */ |
|
| 722 | - abstract protected function getPatterns(); |
|
| 723 | - |
|
| 724 | - |
|
| 725 | - /** |
|
| 726 | - * Registers any supplementary tokens that this test might wish to process. |
|
| 727 | - * |
|
| 728 | - * A sniff may wish to register supplementary tests when it wishes to group |
|
| 729 | - * an arbitrary validation that cannot be performed using a pattern, with |
|
| 730 | - * other pattern tests. |
|
| 731 | - * |
|
| 732 | - * @return int[] |
|
| 733 | - * @see processSupplementary() |
|
| 734 | - */ |
|
| 735 | - protected function registerSupplementary() |
|
| 736 | - { |
|
| 737 | - return []; |
|
| 738 | - |
|
| 739 | - }//end registerSupplementary() |
|
| 740 | - |
|
| 741 | - |
|
| 742 | - /** |
|
| 743 | - * Processes any tokens registered with registerSupplementary(). |
|
| 744 | - * |
|
| 745 | - * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to |
|
| 746 | - * process the skip. |
|
| 747 | - * @param int $stackPtr The position in the tokens stack to |
|
| 748 | - * process. |
|
| 749 | - * |
|
| 750 | - * @return void |
|
| 751 | - * @see registerSupplementary() |
|
| 752 | - */ |
|
| 753 | - protected function processSupplementary(File $phpcsFile, $stackPtr) |
|
| 754 | - { |
|
| 755 | - |
|
| 756 | - }//end processSupplementary() |
|
| 757 | - |
|
| 758 | - |
|
| 759 | - /** |
|
| 760 | - * Parses a pattern string into an array of pattern steps. |
|
| 761 | - * |
|
| 762 | - * @param string $pattern The pattern to parse. |
|
| 763 | - * |
|
| 764 | - * @return array The parsed pattern array. |
|
| 765 | - * @see createSkipPattern() |
|
| 766 | - * @see createTokenPattern() |
|
| 767 | - */ |
|
| 768 | - private function parse($pattern) |
|
| 769 | - { |
|
| 770 | - $patterns = []; |
|
| 771 | - $length = strlen($pattern); |
|
| 772 | - $lastToken = 0; |
|
| 773 | - $firstToken = 0; |
|
| 774 | - |
|
| 775 | - for ($i = 0; $i < $length; $i++) { |
|
| 776 | - $specialPattern = false; |
|
| 777 | - $isLastChar = ($i === ($length - 1)); |
|
| 778 | - $oldFirstToken = $firstToken; |
|
| 779 | - |
|
| 780 | - if (substr($pattern, $i, 3) === '...') { |
|
| 781 | - // It's a skip pattern. The skip pattern requires the |
|
| 782 | - // content of the token in the "from" position and the token |
|
| 783 | - // to skip to. |
|
| 784 | - $specialPattern = $this->createSkipPattern($pattern, ($i - 1)); |
|
| 785 | - $lastToken = ($i - $firstToken); |
|
| 786 | - $firstToken = ($i + 3); |
|
| 787 | - $i += 2; |
|
| 788 | - |
|
| 789 | - if ($specialPattern['to'] !== 'unknown') { |
|
| 790 | - $firstToken++; |
|
| 791 | - } |
|
| 792 | - } else if (substr($pattern, $i, 3) === 'abc') { |
|
| 793 | - $specialPattern = ['type' => 'string']; |
|
| 794 | - $lastToken = ($i - $firstToken); |
|
| 795 | - $firstToken = ($i + 3); |
|
| 796 | - $i += 2; |
|
| 797 | - } else if (substr($pattern, $i, 3) === 'EOL') { |
|
| 798 | - $specialPattern = ['type' => 'newline']; |
|
| 799 | - $lastToken = ($i - $firstToken); |
|
| 800 | - $firstToken = ($i + 3); |
|
| 801 | - $i += 2; |
|
| 802 | - }//end if |
|
| 803 | - |
|
| 804 | - if ($specialPattern !== false || $isLastChar === true) { |
|
| 805 | - // If we are at the end of the string, don't worry about a limit. |
|
| 806 | - if ($isLastChar === true) { |
|
| 807 | - // Get the string from the end of the last skip pattern, if any, |
|
| 808 | - // to the end of the pattern string. |
|
| 809 | - $str = substr($pattern, $oldFirstToken); |
|
| 810 | - } else { |
|
| 811 | - // Get the string from the end of the last special pattern, |
|
| 812 | - // if any, to the start of this special pattern. |
|
| 813 | - if ($lastToken === 0) { |
|
| 814 | - // Note that if the last special token was zero characters ago, |
|
| 815 | - // there will be nothing to process so we can skip this bit. |
|
| 816 | - // This happens if you have something like: EOL... in your pattern. |
|
| 817 | - $str = ''; |
|
| 818 | - } else { |
|
| 819 | - $str = substr($pattern, $oldFirstToken, $lastToken); |
|
| 820 | - } |
|
| 821 | - } |
|
| 822 | - |
|
| 823 | - if ($str !== '') { |
|
| 824 | - $tokenPatterns = $this->createTokenPattern($str); |
|
| 825 | - foreach ($tokenPatterns as $tokenPattern) { |
|
| 826 | - $patterns[] = $tokenPattern; |
|
| 827 | - } |
|
| 828 | - } |
|
| 829 | - |
|
| 830 | - // Make sure we don't skip the last token. |
|
| 831 | - if ($isLastChar === false && $i === ($length - 1)) { |
|
| 832 | - $i--; |
|
| 833 | - } |
|
| 834 | - }//end if |
|
| 835 | - |
|
| 836 | - // Add the skip pattern *after* we have processed |
|
| 837 | - // all the tokens from the end of the last skip pattern |
|
| 838 | - // to the start of this skip pattern. |
|
| 839 | - if ($specialPattern !== false) { |
|
| 840 | - $patterns[] = $specialPattern; |
|
| 841 | - } |
|
| 842 | - }//end for |
|
| 843 | - |
|
| 844 | - return $patterns; |
|
| 845 | - |
|
| 846 | - }//end parse() |
|
| 847 | - |
|
| 848 | - |
|
| 849 | - /** |
|
| 850 | - * Creates a skip pattern. |
|
| 851 | - * |
|
| 852 | - * @param string $pattern The pattern being parsed. |
|
| 853 | - * @param string $from The token content that the skip pattern starts from. |
|
| 854 | - * |
|
| 855 | - * @return array The pattern step. |
|
| 856 | - * @see createTokenPattern() |
|
| 857 | - * @see parse() |
|
| 858 | - */ |
|
| 859 | - private function createSkipPattern($pattern, $from) |
|
| 860 | - { |
|
| 861 | - $skip = ['type' => 'skip']; |
|
| 862 | - |
|
| 863 | - $nestedParenthesis = 0; |
|
| 864 | - $nestedBraces = 0; |
|
| 865 | - for ($start = $from; $start >= 0; $start--) { |
|
| 866 | - switch ($pattern[$start]) { |
|
| 867 | - case '(': |
|
| 868 | - if ($nestedParenthesis === 0) { |
|
| 869 | - $skip['to'] = 'parenthesis_closer'; |
|
| 870 | - } |
|
| 871 | - |
|
| 872 | - $nestedParenthesis--; |
|
| 873 | - break; |
|
| 874 | - case '{': |
|
| 875 | - if ($nestedBraces === 0) { |
|
| 876 | - $skip['to'] = 'scope_closer'; |
|
| 877 | - } |
|
| 878 | - |
|
| 879 | - $nestedBraces--; |
|
| 880 | - break; |
|
| 881 | - case '}': |
|
| 882 | - $nestedBraces++; |
|
| 883 | - break; |
|
| 884 | - case ')': |
|
| 885 | - $nestedParenthesis++; |
|
| 886 | - break; |
|
| 887 | - }//end switch |
|
| 888 | - |
|
| 889 | - if (isset($skip['to']) === true) { |
|
| 890 | - break; |
|
| 891 | - } |
|
| 892 | - }//end for |
|
| 893 | - |
|
| 894 | - if (isset($skip['to']) === false) { |
|
| 895 | - $skip['to'] = 'unknown'; |
|
| 896 | - } |
|
| 897 | - |
|
| 898 | - return $skip; |
|
| 899 | - |
|
| 900 | - }//end createSkipPattern() |
|
| 901 | - |
|
| 902 | - |
|
| 903 | - /** |
|
| 904 | - * Creates a token pattern. |
|
| 905 | - * |
|
| 906 | - * @param string $str The tokens string that the pattern should match. |
|
| 907 | - * |
|
| 908 | - * @return array The pattern step. |
|
| 909 | - * @see createSkipPattern() |
|
| 910 | - * @see parse() |
|
| 911 | - */ |
|
| 912 | - private function createTokenPattern($str) |
|
| 913 | - { |
|
| 914 | - // Don't add a space after the closing php tag as it will add a new |
|
| 915 | - // whitespace token. |
|
| 916 | - $tokenizer = new PHP('<?php '.$str.'?>', null); |
|
| 917 | - |
|
| 918 | - // Remove the <?php tag from the front and the end php tag from the back. |
|
| 919 | - $tokens = $tokenizer->getTokens(); |
|
| 920 | - $tokens = array_slice($tokens, 1, (count($tokens) - 2)); |
|
| 921 | - |
|
| 922 | - $patterns = []; |
|
| 923 | - foreach ($tokens as $patternInfo) { |
|
| 924 | - $patterns[] = [ |
|
| 925 | - 'type' => 'token', |
|
| 926 | - 'token' => $patternInfo['code'], |
|
| 927 | - 'value' => $patternInfo['content'], |
|
| 928 | - ]; |
|
| 929 | - } |
|
| 930 | - |
|
| 931 | - return $patterns; |
|
| 932 | - |
|
| 933 | - }//end createTokenPattern() |
|
| 20 | + /** |
|
| 21 | + * If true, comments will be ignored if they are found in the code. |
|
| 22 | + * |
|
| 23 | + * @var boolean |
|
| 24 | + */ |
|
| 25 | + public $ignoreComments = false; |
|
| 26 | + |
|
| 27 | + /** |
|
| 28 | + * The current file being checked. |
|
| 29 | + * |
|
| 30 | + * @var string |
|
| 31 | + */ |
|
| 32 | + protected $currFile = ''; |
|
| 33 | + |
|
| 34 | + /** |
|
| 35 | + * The parsed patterns array. |
|
| 36 | + * |
|
| 37 | + * @var array |
|
| 38 | + */ |
|
| 39 | + private $parsedPatterns = []; |
|
| 40 | + |
|
| 41 | + /** |
|
| 42 | + * Tokens that this sniff wishes to process outside of the patterns. |
|
| 43 | + * |
|
| 44 | + * @var int[] |
|
| 45 | + * @see registerSupplementary() |
|
| 46 | + * @see processSupplementary() |
|
| 47 | + */ |
|
| 48 | + private $supplementaryTokens = []; |
|
| 49 | + |
|
| 50 | + /** |
|
| 51 | + * Positions in the stack where errors have occurred. |
|
| 52 | + * |
|
| 53 | + * @var array<int, bool> |
|
| 54 | + */ |
|
| 55 | + private $errorPos = []; |
|
| 56 | + |
|
| 57 | + |
|
| 58 | + /** |
|
| 59 | + * Constructs a AbstractPatternSniff. |
|
| 60 | + * |
|
| 61 | + * @param boolean $ignoreComments If true, comments will be ignored. |
|
| 62 | + */ |
|
| 63 | + public function __construct($ignoreComments=null) |
|
| 64 | + { |
|
| 65 | + // This is here for backwards compatibility. |
|
| 66 | + if ($ignoreComments !== null) { |
|
| 67 | + $this->ignoreComments = $ignoreComments; |
|
| 68 | + } |
|
| 69 | + |
|
| 70 | + $this->supplementaryTokens = $this->registerSupplementary(); |
|
| 71 | + |
|
| 72 | + }//end __construct() |
|
| 73 | + |
|
| 74 | + |
|
| 75 | + /** |
|
| 76 | + * Registers the tokens to listen to. |
|
| 77 | + * |
|
| 78 | + * Classes extending <i>AbstractPatternTest</i> should implement the |
|
| 79 | + * <i>getPatterns()</i> method to register the patterns they wish to test. |
|
| 80 | + * |
|
| 81 | + * @return int[] |
|
| 82 | + * @see process() |
|
| 83 | + */ |
|
| 84 | + final public function register() |
|
| 85 | + { |
|
| 86 | + $listenTypes = []; |
|
| 87 | + $patterns = $this->getPatterns(); |
|
| 88 | + |
|
| 89 | + foreach ($patterns as $pattern) { |
|
| 90 | + $parsedPattern = $this->parse($pattern); |
|
| 91 | + |
|
| 92 | + // Find a token position in the pattern that we can use |
|
| 93 | + // for a listener token. |
|
| 94 | + $pos = $this->getListenerTokenPos($parsedPattern); |
|
| 95 | + $tokenType = $parsedPattern[$pos]['token']; |
|
| 96 | + $listenTypes[] = $tokenType; |
|
| 97 | + |
|
| 98 | + $patternArray = [ |
|
| 99 | + 'listen_pos' => $pos, |
|
| 100 | + 'pattern' => $parsedPattern, |
|
| 101 | + 'pattern_code' => $pattern, |
|
| 102 | + ]; |
|
| 103 | + |
|
| 104 | + if (isset($this->parsedPatterns[$tokenType]) === false) { |
|
| 105 | + $this->parsedPatterns[$tokenType] = []; |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + $this->parsedPatterns[$tokenType][] = $patternArray; |
|
| 109 | + }//end foreach |
|
| 110 | + |
|
| 111 | + return array_unique(array_merge($listenTypes, $this->supplementaryTokens)); |
|
| 112 | + |
|
| 113 | + }//end register() |
|
| 114 | + |
|
| 115 | + |
|
| 116 | + /** |
|
| 117 | + * Returns the token types that the specified pattern is checking for. |
|
| 118 | + * |
|
| 119 | + * Returned array is in the format: |
|
| 120 | + * <code> |
|
| 121 | + * array( |
|
| 122 | + * T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token |
|
| 123 | + * // should occur in the pattern. |
|
| 124 | + * ); |
|
| 125 | + * </code> |
|
| 126 | + * |
|
| 127 | + * @param array $pattern The parsed pattern to find the acquire the token |
|
| 128 | + * types from. |
|
| 129 | + * |
|
| 130 | + * @return array<int, int> |
|
| 131 | + */ |
|
| 132 | + private function getPatternTokenTypes($pattern) |
|
| 133 | + { |
|
| 134 | + $tokenTypes = []; |
|
| 135 | + foreach ($pattern as $pos => $patternInfo) { |
|
| 136 | + if ($patternInfo['type'] === 'token') { |
|
| 137 | + if (isset($tokenTypes[$patternInfo['token']]) === false) { |
|
| 138 | + $tokenTypes[$patternInfo['token']] = $pos; |
|
| 139 | + } |
|
| 140 | + } |
|
| 141 | + } |
|
| 142 | + |
|
| 143 | + return $tokenTypes; |
|
| 144 | + |
|
| 145 | + }//end getPatternTokenTypes() |
|
| 146 | + |
|
| 147 | + |
|
| 148 | + /** |
|
| 149 | + * Returns the position in the pattern that this test should register as |
|
| 150 | + * a listener for the pattern. |
|
| 151 | + * |
|
| 152 | + * @param array $pattern The pattern to acquire the listener for. |
|
| 153 | + * |
|
| 154 | + * @return int The position in the pattern that this test should register |
|
| 155 | + * as the listener. |
|
| 156 | + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If we could not determine a token to listen for. |
|
| 157 | + */ |
|
| 158 | + private function getListenerTokenPos($pattern) |
|
| 159 | + { |
|
| 160 | + $tokenTypes = $this->getPatternTokenTypes($pattern); |
|
| 161 | + $tokenCodes = array_keys($tokenTypes); |
|
| 162 | + $token = Tokens::getHighestWeightedToken($tokenCodes); |
|
| 163 | + |
|
| 164 | + // If we could not get a token. |
|
| 165 | + if ($token === false) { |
|
| 166 | + $error = 'Could not determine a token to listen for'; |
|
| 167 | + throw new RuntimeException($error); |
|
| 168 | + } |
|
| 169 | + |
|
| 170 | + return $tokenTypes[$token]; |
|
| 171 | + |
|
| 172 | + }//end getListenerTokenPos() |
|
| 173 | + |
|
| 174 | + |
|
| 175 | + /** |
|
| 176 | + * Processes the test. |
|
| 177 | + * |
|
| 178 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
| 179 | + * token occurred. |
|
| 180 | + * @param int $stackPtr The position in the tokens stack |
|
| 181 | + * where the listening token type |
|
| 182 | + * was found. |
|
| 183 | + * |
|
| 184 | + * @return void |
|
| 185 | + * @see register() |
|
| 186 | + */ |
|
| 187 | + final public function process(File $phpcsFile, $stackPtr) |
|
| 188 | + { |
|
| 189 | + $file = $phpcsFile->getFilename(); |
|
| 190 | + if ($this->currFile !== $file) { |
|
| 191 | + // We have changed files, so clean up. |
|
| 192 | + $this->errorPos = []; |
|
| 193 | + $this->currFile = $file; |
|
| 194 | + } |
|
| 195 | + |
|
| 196 | + $tokens = $phpcsFile->getTokens(); |
|
| 197 | + |
|
| 198 | + if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === true) { |
|
| 199 | + $this->processSupplementary($phpcsFile, $stackPtr); |
|
| 200 | + } |
|
| 201 | + |
|
| 202 | + $type = $tokens[$stackPtr]['code']; |
|
| 203 | + |
|
| 204 | + // If the type is not set, then it must have been a token registered |
|
| 205 | + // with registerSupplementary(). |
|
| 206 | + if (isset($this->parsedPatterns[$type]) === false) { |
|
| 207 | + return; |
|
| 208 | + } |
|
| 209 | + |
|
| 210 | + $allErrors = []; |
|
| 211 | + |
|
| 212 | + // Loop over each pattern that is listening to the current token type |
|
| 213 | + // that we are processing. |
|
| 214 | + foreach ($this->parsedPatterns[$type] as $patternInfo) { |
|
| 215 | + // If processPattern returns false, then the pattern that we are |
|
| 216 | + // checking the code with must not be designed to check that code. |
|
| 217 | + $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr); |
|
| 218 | + if ($errors === false) { |
|
| 219 | + // The pattern didn't match. |
|
| 220 | + continue; |
|
| 221 | + } else if (empty($errors) === true) { |
|
| 222 | + // The pattern matched, but there were no errors. |
|
| 223 | + break; |
|
| 224 | + } |
|
| 225 | + |
|
| 226 | + foreach ($errors as $stackPtr => $error) { |
|
| 227 | + if (isset($this->errorPos[$stackPtr]) === false) { |
|
| 228 | + $this->errorPos[$stackPtr] = true; |
|
| 229 | + $allErrors[$stackPtr] = $error; |
|
| 230 | + } |
|
| 231 | + } |
|
| 232 | + } |
|
| 233 | + |
|
| 234 | + foreach ($allErrors as $stackPtr => $error) { |
|
| 235 | + $phpcsFile->addError($error, $stackPtr, 'Found'); |
|
| 236 | + } |
|
| 237 | + |
|
| 238 | + }//end process() |
|
| 239 | + |
|
| 240 | + |
|
| 241 | + /** |
|
| 242 | + * Processes the pattern and verifies the code at $stackPtr. |
|
| 243 | + * |
|
| 244 | + * @param array $patternInfo Information about the pattern used |
|
| 245 | + * for checking, which includes are |
|
| 246 | + * parsed token representation of the |
|
| 247 | + * pattern. |
|
| 248 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the |
|
| 249 | + * token occurred. |
|
| 250 | + * @param int $stackPtr The position in the tokens stack where |
|
| 251 | + * the listening token type was found. |
|
| 252 | + * |
|
| 253 | + * @return array |
|
| 254 | + */ |
|
| 255 | + protected function processPattern($patternInfo, File $phpcsFile, $stackPtr) |
|
| 256 | + { |
|
| 257 | + $tokens = $phpcsFile->getTokens(); |
|
| 258 | + $pattern = $patternInfo['pattern']; |
|
| 259 | + $patternCode = $patternInfo['pattern_code']; |
|
| 260 | + $errors = []; |
|
| 261 | + $found = ''; |
|
| 262 | + |
|
| 263 | + $ignoreTokens = [T_WHITESPACE => T_WHITESPACE]; |
|
| 264 | + if ($this->ignoreComments === true) { |
|
| 265 | + $ignoreTokens += Tokens::$commentTokens; |
|
| 266 | + } |
|
| 267 | + |
|
| 268 | + $origStackPtr = $stackPtr; |
|
| 269 | + $hasError = false; |
|
| 270 | + |
|
| 271 | + if ($patternInfo['listen_pos'] > 0) { |
|
| 272 | + $stackPtr--; |
|
| 273 | + |
|
| 274 | + for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) { |
|
| 275 | + if ($pattern[$i]['type'] === 'token') { |
|
| 276 | + if ($pattern[$i]['token'] === T_WHITESPACE) { |
|
| 277 | + if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
| 278 | + $found = $tokens[$stackPtr]['content'].$found; |
|
| 279 | + } |
|
| 280 | + |
|
| 281 | + // Only check the size of the whitespace if this is not |
|
| 282 | + // the first token. We don't care about the size of |
|
| 283 | + // leading whitespace, just that there is some. |
|
| 284 | + if ($i !== 0) { |
|
| 285 | + if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) { |
|
| 286 | + $hasError = true; |
|
| 287 | + } |
|
| 288 | + } |
|
| 289 | + } else { |
|
| 290 | + // Check to see if this important token is the same as the |
|
| 291 | + // previous important token in the pattern. If it is not, |
|
| 292 | + // then the pattern cannot be for this piece of code. |
|
| 293 | + $prev = $phpcsFile->findPrevious( |
|
| 294 | + $ignoreTokens, |
|
| 295 | + $stackPtr, |
|
| 296 | + null, |
|
| 297 | + true |
|
| 298 | + ); |
|
| 299 | + |
|
| 300 | + if ($prev === false |
|
| 301 | + || $tokens[$prev]['code'] !== $pattern[$i]['token'] |
|
| 302 | + ) { |
|
| 303 | + return false; |
|
| 304 | + } |
|
| 305 | + |
|
| 306 | + // If we skipped past some whitespace tokens, then add them |
|
| 307 | + // to the found string. |
|
| 308 | + $tokenContent = $phpcsFile->getTokensAsString( |
|
| 309 | + ($prev + 1), |
|
| 310 | + ($stackPtr - $prev - 1) |
|
| 311 | + ); |
|
| 312 | + |
|
| 313 | + $found = $tokens[$prev]['content'].$tokenContent.$found; |
|
| 314 | + |
|
| 315 | + if (isset($pattern[($i - 1)]) === true |
|
| 316 | + && $pattern[($i - 1)]['type'] === 'skip' |
|
| 317 | + ) { |
|
| 318 | + $stackPtr = $prev; |
|
| 319 | + } else { |
|
| 320 | + $stackPtr = ($prev - 1); |
|
| 321 | + } |
|
| 322 | + }//end if |
|
| 323 | + } else if ($pattern[$i]['type'] === 'skip') { |
|
| 324 | + // Skip to next piece of relevant code. |
|
| 325 | + if ($pattern[$i]['to'] === 'parenthesis_closer') { |
|
| 326 | + $to = 'parenthesis_opener'; |
|
| 327 | + } else { |
|
| 328 | + $to = 'scope_opener'; |
|
| 329 | + } |
|
| 330 | + |
|
| 331 | + // Find the previous opener. |
|
| 332 | + $next = $phpcsFile->findPrevious( |
|
| 333 | + $ignoreTokens, |
|
| 334 | + $stackPtr, |
|
| 335 | + null, |
|
| 336 | + true |
|
| 337 | + ); |
|
| 338 | + |
|
| 339 | + if ($next === false || isset($tokens[$next][$to]) === false) { |
|
| 340 | + // If there was not opener, then we must be |
|
| 341 | + // using the wrong pattern. |
|
| 342 | + return false; |
|
| 343 | + } |
|
| 344 | + |
|
| 345 | + if ($to === 'parenthesis_opener') { |
|
| 346 | + $found = '{'.$found; |
|
| 347 | + } else { |
|
| 348 | + $found = '('.$found; |
|
| 349 | + } |
|
| 350 | + |
|
| 351 | + $found = '...'.$found; |
|
| 352 | + |
|
| 353 | + // Skip to the opening token. |
|
| 354 | + $stackPtr = ($tokens[$next][$to] - 1); |
|
| 355 | + } else if ($pattern[$i]['type'] === 'string') { |
|
| 356 | + $found = 'abc'; |
|
| 357 | + } else if ($pattern[$i]['type'] === 'newline') { |
|
| 358 | + if ($this->ignoreComments === true |
|
| 359 | + && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true |
|
| 360 | + ) { |
|
| 361 | + $startComment = $phpcsFile->findPrevious( |
|
| 362 | + Tokens::$commentTokens, |
|
| 363 | + ($stackPtr - 1), |
|
| 364 | + null, |
|
| 365 | + true |
|
| 366 | + ); |
|
| 367 | + |
|
| 368 | + if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) { |
|
| 369 | + $startComment++; |
|
| 370 | + } |
|
| 371 | + |
|
| 372 | + $tokenContent = $phpcsFile->getTokensAsString( |
|
| 373 | + $startComment, |
|
| 374 | + ($stackPtr - $startComment + 1) |
|
| 375 | + ); |
|
| 376 | + |
|
| 377 | + $found = $tokenContent.$found; |
|
| 378 | + $stackPtr = ($startComment - 1); |
|
| 379 | + } |
|
| 380 | + |
|
| 381 | + if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
| 382 | + if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) { |
|
| 383 | + $found = $tokens[$stackPtr]['content'].$found; |
|
| 384 | + |
|
| 385 | + // This may just be an indent that comes after a newline |
|
| 386 | + // so check the token before to make sure. If it is a newline, we |
|
| 387 | + // can ignore the error here. |
|
| 388 | + if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar) |
|
| 389 | + && ($this->ignoreComments === true |
|
| 390 | + && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false) |
|
| 391 | + ) { |
|
| 392 | + $hasError = true; |
|
| 393 | + } else { |
|
| 394 | + $stackPtr--; |
|
| 395 | + } |
|
| 396 | + } else { |
|
| 397 | + $found = 'EOL'.$found; |
|
| 398 | + } |
|
| 399 | + } else { |
|
| 400 | + $found = $tokens[$stackPtr]['content'].$found; |
|
| 401 | + $hasError = true; |
|
| 402 | + }//end if |
|
| 403 | + |
|
| 404 | + if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') { |
|
| 405 | + // Make sure they only have 1 newline. |
|
| 406 | + $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true); |
|
| 407 | + if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) { |
|
| 408 | + $hasError = true; |
|
| 409 | + } |
|
| 410 | + } |
|
| 411 | + }//end if |
|
| 412 | + }//end for |
|
| 413 | + }//end if |
|
| 414 | + |
|
| 415 | + $stackPtr = $origStackPtr; |
|
| 416 | + $lastAddedStackPtr = null; |
|
| 417 | + $patternLen = count($pattern); |
|
| 418 | + |
|
| 419 | + for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) { |
|
| 420 | + if (isset($tokens[$stackPtr]) === false) { |
|
| 421 | + break; |
|
| 422 | + } |
|
| 423 | + |
|
| 424 | + if ($pattern[$i]['type'] === 'token') { |
|
| 425 | + if ($pattern[$i]['token'] === T_WHITESPACE) { |
|
| 426 | + if ($this->ignoreComments === true) { |
|
| 427 | + // If we are ignoring comments, check to see if this current |
|
| 428 | + // token is a comment. If so skip it. |
|
| 429 | + if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) { |
|
| 430 | + continue; |
|
| 431 | + } |
|
| 432 | + |
|
| 433 | + // If the next token is a comment, the we need to skip the |
|
| 434 | + // current token as we should allow a space before a |
|
| 435 | + // comment for readability. |
|
| 436 | + if (isset($tokens[($stackPtr + 1)]) === true |
|
| 437 | + && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true |
|
| 438 | + ) { |
|
| 439 | + continue; |
|
| 440 | + } |
|
| 441 | + } |
|
| 442 | + |
|
| 443 | + $tokenContent = ''; |
|
| 444 | + if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { |
|
| 445 | + if (isset($pattern[($i + 1)]) === false) { |
|
| 446 | + // This is the last token in the pattern, so just compare |
|
| 447 | + // the next token of content. |
|
| 448 | + $tokenContent = $tokens[$stackPtr]['content']; |
|
| 449 | + } else { |
|
| 450 | + // Get all the whitespace to the next token. |
|
| 451 | + $next = $phpcsFile->findNext( |
|
| 452 | + Tokens::$emptyTokens, |
|
| 453 | + $stackPtr, |
|
| 454 | + null, |
|
| 455 | + true |
|
| 456 | + ); |
|
| 457 | + |
|
| 458 | + $tokenContent = $phpcsFile->getTokensAsString( |
|
| 459 | + $stackPtr, |
|
| 460 | + ($next - $stackPtr) |
|
| 461 | + ); |
|
| 462 | + |
|
| 463 | + $lastAddedStackPtr = $stackPtr; |
|
| 464 | + $stackPtr = $next; |
|
| 465 | + }//end if |
|
| 466 | + |
|
| 467 | + if ($stackPtr !== $lastAddedStackPtr) { |
|
| 468 | + $found .= $tokenContent; |
|
| 469 | + } |
|
| 470 | + } else { |
|
| 471 | + if ($stackPtr !== $lastAddedStackPtr) { |
|
| 472 | + $found .= $tokens[$stackPtr]['content']; |
|
| 473 | + $lastAddedStackPtr = $stackPtr; |
|
| 474 | + } |
|
| 475 | + }//end if |
|
| 476 | + |
|
| 477 | + if (isset($pattern[($i + 1)]) === true |
|
| 478 | + && $pattern[($i + 1)]['type'] === 'skip' |
|
| 479 | + ) { |
|
| 480 | + // The next token is a skip token, so we just need to make |
|
| 481 | + // sure the whitespace we found has *at least* the |
|
| 482 | + // whitespace required. |
|
| 483 | + if (strpos($tokenContent, $pattern[$i]['value']) !== 0) { |
|
| 484 | + $hasError = true; |
|
| 485 | + } |
|
| 486 | + } else { |
|
| 487 | + if ($tokenContent !== $pattern[$i]['value']) { |
|
| 488 | + $hasError = true; |
|
| 489 | + } |
|
| 490 | + } |
|
| 491 | + } else { |
|
| 492 | + // Check to see if this important token is the same as the |
|
| 493 | + // next important token in the pattern. If it is not, then |
|
| 494 | + // the pattern cannot be for this piece of code. |
|
| 495 | + $next = $phpcsFile->findNext( |
|
| 496 | + $ignoreTokens, |
|
| 497 | + $stackPtr, |
|
| 498 | + null, |
|
| 499 | + true |
|
| 500 | + ); |
|
| 501 | + |
|
| 502 | + if ($next === false |
|
| 503 | + || $tokens[$next]['code'] !== $pattern[$i]['token'] |
|
| 504 | + ) { |
|
| 505 | + // The next important token did not match the pattern. |
|
| 506 | + return false; |
|
| 507 | + } |
|
| 508 | + |
|
| 509 | + if ($lastAddedStackPtr !== null) { |
|
| 510 | + if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET |
|
| 511 | + || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) |
|
| 512 | + && isset($tokens[$next]['scope_condition']) === true |
|
| 513 | + && $tokens[$next]['scope_condition'] > $lastAddedStackPtr |
|
| 514 | + ) { |
|
| 515 | + // This is a brace, but the owner of it is after the current |
|
| 516 | + // token, which means it does not belong to any token in |
|
| 517 | + // our pattern. This means the pattern is not for us. |
|
| 518 | + return false; |
|
| 519 | + } |
|
| 520 | + |
|
| 521 | + if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS |
|
| 522 | + || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS) |
|
| 523 | + && isset($tokens[$next]['parenthesis_owner']) === true |
|
| 524 | + && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr |
|
| 525 | + ) { |
|
| 526 | + // This is a bracket, but the owner of it is after the current |
|
| 527 | + // token, which means it does not belong to any token in |
|
| 528 | + // our pattern. This means the pattern is not for us. |
|
| 529 | + return false; |
|
| 530 | + } |
|
| 531 | + }//end if |
|
| 532 | + |
|
| 533 | + // If we skipped past some whitespace tokens, then add them |
|
| 534 | + // to the found string. |
|
| 535 | + if (($next - $stackPtr) > 0) { |
|
| 536 | + $hasComment = false; |
|
| 537 | + for ($j = $stackPtr; $j < $next; $j++) { |
|
| 538 | + $found .= $tokens[$j]['content']; |
|
| 539 | + if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) { |
|
| 540 | + $hasComment = true; |
|
| 541 | + } |
|
| 542 | + } |
|
| 543 | + |
|
| 544 | + // If we are not ignoring comments, this additional |
|
| 545 | + // whitespace or comment is not allowed. If we are |
|
| 546 | + // ignoring comments, there needs to be at least one |
|
| 547 | + // comment for this to be allowed. |
|
| 548 | + if ($this->ignoreComments === false |
|
| 549 | + || ($this->ignoreComments === true |
|
| 550 | + && $hasComment === false) |
|
| 551 | + ) { |
|
| 552 | + $hasError = true; |
|
| 553 | + } |
|
| 554 | + |
|
| 555 | + // Even when ignoring comments, we are not allowed to include |
|
| 556 | + // newlines without the pattern specifying them, so |
|
| 557 | + // everything should be on the same line. |
|
| 558 | + if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { |
|
| 559 | + $hasError = true; |
|
| 560 | + } |
|
| 561 | + }//end if |
|
| 562 | + |
|
| 563 | + if ($next !== $lastAddedStackPtr) { |
|
| 564 | + $found .= $tokens[$next]['content']; |
|
| 565 | + $lastAddedStackPtr = $next; |
|
| 566 | + } |
|
| 567 | + |
|
| 568 | + if (isset($pattern[($i + 1)]) === true |
|
| 569 | + && $pattern[($i + 1)]['type'] === 'skip' |
|
| 570 | + ) { |
|
| 571 | + $stackPtr = $next; |
|
| 572 | + } else { |
|
| 573 | + $stackPtr = ($next + 1); |
|
| 574 | + } |
|
| 575 | + }//end if |
|
| 576 | + } else if ($pattern[$i]['type'] === 'skip') { |
|
| 577 | + if ($pattern[$i]['to'] === 'unknown') { |
|
| 578 | + $next = $phpcsFile->findNext( |
|
| 579 | + $pattern[($i + 1)]['token'], |
|
| 580 | + $stackPtr |
|
| 581 | + ); |
|
| 582 | + |
|
| 583 | + if ($next === false) { |
|
| 584 | + // Couldn't find the next token, so we must |
|
| 585 | + // be using the wrong pattern. |
|
| 586 | + return false; |
|
| 587 | + } |
|
| 588 | + |
|
| 589 | + $found .= '...'; |
|
| 590 | + $stackPtr = $next; |
|
| 591 | + } else { |
|
| 592 | + // Find the previous opener. |
|
| 593 | + $next = $phpcsFile->findPrevious( |
|
| 594 | + Tokens::$blockOpeners, |
|
| 595 | + $stackPtr |
|
| 596 | + ); |
|
| 597 | + |
|
| 598 | + if ($next === false |
|
| 599 | + || isset($tokens[$next][$pattern[$i]['to']]) === false |
|
| 600 | + ) { |
|
| 601 | + // If there was not opener, then we must |
|
| 602 | + // be using the wrong pattern. |
|
| 603 | + return false; |
|
| 604 | + } |
|
| 605 | + |
|
| 606 | + $found .= '...'; |
|
| 607 | + if ($pattern[$i]['to'] === 'parenthesis_closer') { |
|
| 608 | + $found .= ')'; |
|
| 609 | + } else { |
|
| 610 | + $found .= '}'; |
|
| 611 | + } |
|
| 612 | + |
|
| 613 | + // Skip to the closing token. |
|
| 614 | + $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1); |
|
| 615 | + }//end if |
|
| 616 | + } else if ($pattern[$i]['type'] === 'string') { |
|
| 617 | + if ($tokens[$stackPtr]['code'] !== T_STRING) { |
|
| 618 | + $hasError = true; |
|
| 619 | + } |
|
| 620 | + |
|
| 621 | + if ($stackPtr !== $lastAddedStackPtr) { |
|
| 622 | + $found .= 'abc'; |
|
| 623 | + $lastAddedStackPtr = $stackPtr; |
|
| 624 | + } |
|
| 625 | + |
|
| 626 | + $stackPtr++; |
|
| 627 | + } else if ($pattern[$i]['type'] === 'newline') { |
|
| 628 | + // Find the next token that contains a newline character. |
|
| 629 | + $newline = 0; |
|
| 630 | + for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) { |
|
| 631 | + if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) { |
|
| 632 | + $newline = $j; |
|
| 633 | + break; |
|
| 634 | + } |
|
| 635 | + } |
|
| 636 | + |
|
| 637 | + if ($newline === 0) { |
|
| 638 | + // We didn't find a newline character in the rest of the file. |
|
| 639 | + $next = ($phpcsFile->numTokens - 1); |
|
| 640 | + $hasError = true; |
|
| 641 | + } else { |
|
| 642 | + if ($this->ignoreComments === false) { |
|
| 643 | + // The newline character cannot be part of a comment. |
|
| 644 | + if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) { |
|
| 645 | + $hasError = true; |
|
| 646 | + } |
|
| 647 | + } |
|
| 648 | + |
|
| 649 | + if ($newline === $stackPtr) { |
|
| 650 | + $next = ($stackPtr + 1); |
|
| 651 | + } else { |
|
| 652 | + // Check that there were no significant tokens that we |
|
| 653 | + // skipped over to find our newline character. |
|
| 654 | + $next = $phpcsFile->findNext( |
|
| 655 | + $ignoreTokens, |
|
| 656 | + $stackPtr, |
|
| 657 | + null, |
|
| 658 | + true |
|
| 659 | + ); |
|
| 660 | + |
|
| 661 | + if ($next < $newline) { |
|
| 662 | + // We skipped a non-ignored token. |
|
| 663 | + $hasError = true; |
|
| 664 | + } else { |
|
| 665 | + $next = ($newline + 1); |
|
| 666 | + } |
|
| 667 | + } |
|
| 668 | + }//end if |
|
| 669 | + |
|
| 670 | + if ($stackPtr !== $lastAddedStackPtr) { |
|
| 671 | + $found .= $phpcsFile->getTokensAsString( |
|
| 672 | + $stackPtr, |
|
| 673 | + ($next - $stackPtr) |
|
| 674 | + ); |
|
| 675 | + |
|
| 676 | + $lastAddedStackPtr = ($next - 1); |
|
| 677 | + } |
|
| 678 | + |
|
| 679 | + $stackPtr = $next; |
|
| 680 | + }//end if |
|
| 681 | + }//end for |
|
| 682 | + |
|
| 683 | + if ($hasError === true) { |
|
| 684 | + $error = $this->prepareError($found, $patternCode); |
|
| 685 | + $errors[$origStackPtr] = $error; |
|
| 686 | + } |
|
| 687 | + |
|
| 688 | + return $errors; |
|
| 689 | + |
|
| 690 | + }//end processPattern() |
|
| 691 | + |
|
| 692 | + |
|
| 693 | + /** |
|
| 694 | + * Prepares an error for the specified patternCode. |
|
| 695 | + * |
|
| 696 | + * @param string $found The actual found string in the code. |
|
| 697 | + * @param string $patternCode The expected pattern code. |
|
| 698 | + * |
|
| 699 | + * @return string The error message. |
|
| 700 | + */ |
|
| 701 | + protected function prepareError($found, $patternCode) |
|
| 702 | + { |
|
| 703 | + $found = str_replace("\r\n", '\n', $found); |
|
| 704 | + $found = str_replace("\n", '\n', $found); |
|
| 705 | + $found = str_replace("\r", '\n', $found); |
|
| 706 | + $found = str_replace("\t", '\t', $found); |
|
| 707 | + $found = str_replace('EOL', '\n', $found); |
|
| 708 | + $expected = str_replace('EOL', '\n', $patternCode); |
|
| 709 | + |
|
| 710 | + $error = "Expected \"$expected\"; found \"$found\""; |
|
| 711 | + |
|
| 712 | + return $error; |
|
| 713 | + |
|
| 714 | + }//end prepareError() |
|
| 715 | + |
|
| 716 | + |
|
| 717 | + /** |
|
| 718 | + * Returns the patterns that should be checked. |
|
| 719 | + * |
|
| 720 | + * @return string[] |
|
| 721 | + */ |
|
| 722 | + abstract protected function getPatterns(); |
|
| 723 | + |
|
| 724 | + |
|
| 725 | + /** |
|
| 726 | + * Registers any supplementary tokens that this test might wish to process. |
|
| 727 | + * |
|
| 728 | + * A sniff may wish to register supplementary tests when it wishes to group |
|
| 729 | + * an arbitrary validation that cannot be performed using a pattern, with |
|
| 730 | + * other pattern tests. |
|
| 731 | + * |
|
| 732 | + * @return int[] |
|
| 733 | + * @see processSupplementary() |
|
| 734 | + */ |
|
| 735 | + protected function registerSupplementary() |
|
| 736 | + { |
|
| 737 | + return []; |
|
| 738 | + |
|
| 739 | + }//end registerSupplementary() |
|
| 740 | + |
|
| 741 | + |
|
| 742 | + /** |
|
| 743 | + * Processes any tokens registered with registerSupplementary(). |
|
| 744 | + * |
|
| 745 | + * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to |
|
| 746 | + * process the skip. |
|
| 747 | + * @param int $stackPtr The position in the tokens stack to |
|
| 748 | + * process. |
|
| 749 | + * |
|
| 750 | + * @return void |
|
| 751 | + * @see registerSupplementary() |
|
| 752 | + */ |
|
| 753 | + protected function processSupplementary(File $phpcsFile, $stackPtr) |
|
| 754 | + { |
|
| 755 | + |
|
| 756 | + }//end processSupplementary() |
|
| 757 | + |
|
| 758 | + |
|
| 759 | + /** |
|
| 760 | + * Parses a pattern string into an array of pattern steps. |
|
| 761 | + * |
|
| 762 | + * @param string $pattern The pattern to parse. |
|
| 763 | + * |
|
| 764 | + * @return array The parsed pattern array. |
|
| 765 | + * @see createSkipPattern() |
|
| 766 | + * @see createTokenPattern() |
|
| 767 | + */ |
|
| 768 | + private function parse($pattern) |
|
| 769 | + { |
|
| 770 | + $patterns = []; |
|
| 771 | + $length = strlen($pattern); |
|
| 772 | + $lastToken = 0; |
|
| 773 | + $firstToken = 0; |
|
| 774 | + |
|
| 775 | + for ($i = 0; $i < $length; $i++) { |
|
| 776 | + $specialPattern = false; |
|
| 777 | + $isLastChar = ($i === ($length - 1)); |
|
| 778 | + $oldFirstToken = $firstToken; |
|
| 779 | + |
|
| 780 | + if (substr($pattern, $i, 3) === '...') { |
|
| 781 | + // It's a skip pattern. The skip pattern requires the |
|
| 782 | + // content of the token in the "from" position and the token |
|
| 783 | + // to skip to. |
|
| 784 | + $specialPattern = $this->createSkipPattern($pattern, ($i - 1)); |
|
| 785 | + $lastToken = ($i - $firstToken); |
|
| 786 | + $firstToken = ($i + 3); |
|
| 787 | + $i += 2; |
|
| 788 | + |
|
| 789 | + if ($specialPattern['to'] !== 'unknown') { |
|
| 790 | + $firstToken++; |
|
| 791 | + } |
|
| 792 | + } else if (substr($pattern, $i, 3) === 'abc') { |
|
| 793 | + $specialPattern = ['type' => 'string']; |
|
| 794 | + $lastToken = ($i - $firstToken); |
|
| 795 | + $firstToken = ($i + 3); |
|
| 796 | + $i += 2; |
|
| 797 | + } else if (substr($pattern, $i, 3) === 'EOL') { |
|
| 798 | + $specialPattern = ['type' => 'newline']; |
|
| 799 | + $lastToken = ($i - $firstToken); |
|
| 800 | + $firstToken = ($i + 3); |
|
| 801 | + $i += 2; |
|
| 802 | + }//end if |
|
| 803 | + |
|
| 804 | + if ($specialPattern !== false || $isLastChar === true) { |
|
| 805 | + // If we are at the end of the string, don't worry about a limit. |
|
| 806 | + if ($isLastChar === true) { |
|
| 807 | + // Get the string from the end of the last skip pattern, if any, |
|
| 808 | + // to the end of the pattern string. |
|
| 809 | + $str = substr($pattern, $oldFirstToken); |
|
| 810 | + } else { |
|
| 811 | + // Get the string from the end of the last special pattern, |
|
| 812 | + // if any, to the start of this special pattern. |
|
| 813 | + if ($lastToken === 0) { |
|
| 814 | + // Note that if the last special token was zero characters ago, |
|
| 815 | + // there will be nothing to process so we can skip this bit. |
|
| 816 | + // This happens if you have something like: EOL... in your pattern. |
|
| 817 | + $str = ''; |
|
| 818 | + } else { |
|
| 819 | + $str = substr($pattern, $oldFirstToken, $lastToken); |
|
| 820 | + } |
|
| 821 | + } |
|
| 822 | + |
|
| 823 | + if ($str !== '') { |
|
| 824 | + $tokenPatterns = $this->createTokenPattern($str); |
|
| 825 | + foreach ($tokenPatterns as $tokenPattern) { |
|
| 826 | + $patterns[] = $tokenPattern; |
|
| 827 | + } |
|
| 828 | + } |
|
| 829 | + |
|
| 830 | + // Make sure we don't skip the last token. |
|
| 831 | + if ($isLastChar === false && $i === ($length - 1)) { |
|
| 832 | + $i--; |
|
| 833 | + } |
|
| 834 | + }//end if |
|
| 835 | + |
|
| 836 | + // Add the skip pattern *after* we have processed |
|
| 837 | + // all the tokens from the end of the last skip pattern |
|
| 838 | + // to the start of this skip pattern. |
|
| 839 | + if ($specialPattern !== false) { |
|
| 840 | + $patterns[] = $specialPattern; |
|
| 841 | + } |
|
| 842 | + }//end for |
|
| 843 | + |
|
| 844 | + return $patterns; |
|
| 845 | + |
|
| 846 | + }//end parse() |
|
| 847 | + |
|
| 848 | + |
|
| 849 | + /** |
|
| 850 | + * Creates a skip pattern. |
|
| 851 | + * |
|
| 852 | + * @param string $pattern The pattern being parsed. |
|
| 853 | + * @param string $from The token content that the skip pattern starts from. |
|
| 854 | + * |
|
| 855 | + * @return array The pattern step. |
|
| 856 | + * @see createTokenPattern() |
|
| 857 | + * @see parse() |
|
| 858 | + */ |
|
| 859 | + private function createSkipPattern($pattern, $from) |
|
| 860 | + { |
|
| 861 | + $skip = ['type' => 'skip']; |
|
| 862 | + |
|
| 863 | + $nestedParenthesis = 0; |
|
| 864 | + $nestedBraces = 0; |
|
| 865 | + for ($start = $from; $start >= 0; $start--) { |
|
| 866 | + switch ($pattern[$start]) { |
|
| 867 | + case '(': |
|
| 868 | + if ($nestedParenthesis === 0) { |
|
| 869 | + $skip['to'] = 'parenthesis_closer'; |
|
| 870 | + } |
|
| 871 | + |
|
| 872 | + $nestedParenthesis--; |
|
| 873 | + break; |
|
| 874 | + case '{': |
|
| 875 | + if ($nestedBraces === 0) { |
|
| 876 | + $skip['to'] = 'scope_closer'; |
|
| 877 | + } |
|
| 878 | + |
|
| 879 | + $nestedBraces--; |
|
| 880 | + break; |
|
| 881 | + case '}': |
|
| 882 | + $nestedBraces++; |
|
| 883 | + break; |
|
| 884 | + case ')': |
|
| 885 | + $nestedParenthesis++; |
|
| 886 | + break; |
|
| 887 | + }//end switch |
|
| 888 | + |
|
| 889 | + if (isset($skip['to']) === true) { |
|
| 890 | + break; |
|
| 891 | + } |
|
| 892 | + }//end for |
|
| 893 | + |
|
| 894 | + if (isset($skip['to']) === false) { |
|
| 895 | + $skip['to'] = 'unknown'; |
|
| 896 | + } |
|
| 897 | + |
|
| 898 | + return $skip; |
|
| 899 | + |
|
| 900 | + }//end createSkipPattern() |
|
| 901 | + |
|
| 902 | + |
|
| 903 | + /** |
|
| 904 | + * Creates a token pattern. |
|
| 905 | + * |
|
| 906 | + * @param string $str The tokens string that the pattern should match. |
|
| 907 | + * |
|
| 908 | + * @return array The pattern step. |
|
| 909 | + * @see createSkipPattern() |
|
| 910 | + * @see parse() |
|
| 911 | + */ |
|
| 912 | + private function createTokenPattern($str) |
|
| 913 | + { |
|
| 914 | + // Don't add a space after the closing php tag as it will add a new |
|
| 915 | + // whitespace token. |
|
| 916 | + $tokenizer = new PHP('<?php '.$str.'?>', null); |
|
| 917 | + |
|
| 918 | + // Remove the <?php tag from the front and the end php tag from the back. |
|
| 919 | + $tokens = $tokenizer->getTokens(); |
|
| 920 | + $tokens = array_slice($tokens, 1, (count($tokens) - 2)); |
|
| 921 | + |
|
| 922 | + $patterns = []; |
|
| 923 | + foreach ($tokens as $patternInfo) { |
|
| 924 | + $patterns[] = [ |
|
| 925 | + 'type' => 'token', |
|
| 926 | + 'token' => $patternInfo['code'], |
|
| 927 | + 'value' => $patternInfo['content'], |
|
| 928 | + ]; |
|
| 929 | + } |
|
| 930 | + |
|
| 931 | + return $patterns; |
|
| 932 | + |
|
| 933 | + }//end createTokenPattern() |
|
| 934 | 934 | |
| 935 | 935 | |
| 936 | 936 | }//end class |
@@ -19,100 +19,100 @@ |
||
| 19 | 19 | abstract class Generator |
| 20 | 20 | { |
| 21 | 21 | |
| 22 | - /** |
|
| 23 | - * The ruleset used for the run. |
|
| 24 | - * |
|
| 25 | - * @var \PHP_CodeSniffer\Ruleset |
|
| 26 | - */ |
|
| 27 | - public $ruleset = null; |
|
| 28 | - |
|
| 29 | - /** |
|
| 30 | - * XML documentation files used to produce the final output. |
|
| 31 | - * |
|
| 32 | - * @var string[] |
|
| 33 | - */ |
|
| 34 | - public $docFiles = []; |
|
| 35 | - |
|
| 36 | - |
|
| 37 | - /** |
|
| 38 | - * Constructs a doc generator. |
|
| 39 | - * |
|
| 40 | - * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run. |
|
| 41 | - * |
|
| 42 | - * @see generate() |
|
| 43 | - */ |
|
| 44 | - public function __construct(Ruleset $ruleset) |
|
| 45 | - { |
|
| 46 | - $this->ruleset = $ruleset; |
|
| 47 | - |
|
| 48 | - foreach ($ruleset->sniffs as $className => $sniffClass) { |
|
| 49 | - $file = Autoload::getLoadedFileName($className); |
|
| 50 | - $docFile = str_replace( |
|
| 51 | - DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR, |
|
| 52 | - DIRECTORY_SEPARATOR.'Docs'.DIRECTORY_SEPARATOR, |
|
| 53 | - $file |
|
| 54 | - ); |
|
| 55 | - $docFile = str_replace('Sniff.php', 'Standard.xml', $docFile); |
|
| 56 | - |
|
| 57 | - if (is_file($docFile) === true) { |
|
| 58 | - $this->docFiles[] = $docFile; |
|
| 59 | - } |
|
| 60 | - } |
|
| 61 | - |
|
| 62 | - }//end __construct() |
|
| 63 | - |
|
| 64 | - |
|
| 65 | - /** |
|
| 66 | - * Retrieves the title of the sniff from the DOMNode supplied. |
|
| 67 | - * |
|
| 68 | - * @param \DOMNode $doc The DOMNode object for the sniff. |
|
| 69 | - * It represents the "documentation" tag in the XML |
|
| 70 | - * standard file. |
|
| 71 | - * |
|
| 72 | - * @return string |
|
| 73 | - */ |
|
| 74 | - protected function getTitle(\DOMNode $doc) |
|
| 75 | - { |
|
| 76 | - return $doc->getAttribute('title'); |
|
| 77 | - |
|
| 78 | - }//end getTitle() |
|
| 79 | - |
|
| 80 | - |
|
| 81 | - /** |
|
| 82 | - * Generates the documentation for a standard. |
|
| 83 | - * |
|
| 84 | - * It's probably wise for doc generators to override this method so they |
|
| 85 | - * have control over how the docs are produced. Otherwise, the processSniff |
|
| 86 | - * method should be overridden to output content for each sniff. |
|
| 87 | - * |
|
| 88 | - * @return void |
|
| 89 | - * @see processSniff() |
|
| 90 | - */ |
|
| 91 | - public function generate() |
|
| 92 | - { |
|
| 93 | - foreach ($this->docFiles as $file) { |
|
| 94 | - $doc = new \DOMDocument(); |
|
| 95 | - $doc->load($file); |
|
| 96 | - $documentation = $doc->getElementsByTagName('documentation')->item(0); |
|
| 97 | - $this->processSniff($documentation); |
|
| 98 | - } |
|
| 99 | - |
|
| 100 | - }//end generate() |
|
| 101 | - |
|
| 102 | - |
|
| 103 | - /** |
|
| 104 | - * Process the documentation for a single sniff. |
|
| 105 | - * |
|
| 106 | - * Doc generators must implement this function to produce output. |
|
| 107 | - * |
|
| 108 | - * @param \DOMNode $doc The DOMNode object for the sniff. |
|
| 109 | - * It represents the "documentation" tag in the XML |
|
| 110 | - * standard file. |
|
| 111 | - * |
|
| 112 | - * @return void |
|
| 113 | - * @see generate() |
|
| 114 | - */ |
|
| 115 | - abstract protected function processSniff(\DOMNode $doc); |
|
| 22 | + /** |
|
| 23 | + * The ruleset used for the run. |
|
| 24 | + * |
|
| 25 | + * @var \PHP_CodeSniffer\Ruleset |
|
| 26 | + */ |
|
| 27 | + public $ruleset = null; |
|
| 28 | + |
|
| 29 | + /** |
|
| 30 | + * XML documentation files used to produce the final output. |
|
| 31 | + * |
|
| 32 | + * @var string[] |
|
| 33 | + */ |
|
| 34 | + public $docFiles = []; |
|
| 35 | + |
|
| 36 | + |
|
| 37 | + /** |
|
| 38 | + * Constructs a doc generator. |
|
| 39 | + * |
|
| 40 | + * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run. |
|
| 41 | + * |
|
| 42 | + * @see generate() |
|
| 43 | + */ |
|
| 44 | + public function __construct(Ruleset $ruleset) |
|
| 45 | + { |
|
| 46 | + $this->ruleset = $ruleset; |
|
| 47 | + |
|
| 48 | + foreach ($ruleset->sniffs as $className => $sniffClass) { |
|
| 49 | + $file = Autoload::getLoadedFileName($className); |
|
| 50 | + $docFile = str_replace( |
|
| 51 | + DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR, |
|
| 52 | + DIRECTORY_SEPARATOR.'Docs'.DIRECTORY_SEPARATOR, |
|
| 53 | + $file |
|
| 54 | + ); |
|
| 55 | + $docFile = str_replace('Sniff.php', 'Standard.xml', $docFile); |
|
| 56 | + |
|
| 57 | + if (is_file($docFile) === true) { |
|
| 58 | + $this->docFiles[] = $docFile; |
|
| 59 | + } |
|
| 60 | + } |
|
| 61 | + |
|
| 62 | + }//end __construct() |
|
| 63 | + |
|
| 64 | + |
|
| 65 | + /** |
|
| 66 | + * Retrieves the title of the sniff from the DOMNode supplied. |
|
| 67 | + * |
|
| 68 | + * @param \DOMNode $doc The DOMNode object for the sniff. |
|
| 69 | + * It represents the "documentation" tag in the XML |
|
| 70 | + * standard file. |
|
| 71 | + * |
|
| 72 | + * @return string |
|
| 73 | + */ |
|
| 74 | + protected function getTitle(\DOMNode $doc) |
|
| 75 | + { |
|
| 76 | + return $doc->getAttribute('title'); |
|
| 77 | + |
|
| 78 | + }//end getTitle() |
|
| 79 | + |
|
| 80 | + |
|
| 81 | + /** |
|
| 82 | + * Generates the documentation for a standard. |
|
| 83 | + * |
|
| 84 | + * It's probably wise for doc generators to override this method so they |
|
| 85 | + * have control over how the docs are produced. Otherwise, the processSniff |
|
| 86 | + * method should be overridden to output content for each sniff. |
|
| 87 | + * |
|
| 88 | + * @return void |
|
| 89 | + * @see processSniff() |
|
| 90 | + */ |
|
| 91 | + public function generate() |
|
| 92 | + { |
|
| 93 | + foreach ($this->docFiles as $file) { |
|
| 94 | + $doc = new \DOMDocument(); |
|
| 95 | + $doc->load($file); |
|
| 96 | + $documentation = $doc->getElementsByTagName('documentation')->item(0); |
|
| 97 | + $this->processSniff($documentation); |
|
| 98 | + } |
|
| 99 | + |
|
| 100 | + }//end generate() |
|
| 101 | + |
|
| 102 | + |
|
| 103 | + /** |
|
| 104 | + * Process the documentation for a single sniff. |
|
| 105 | + * |
|
| 106 | + * Doc generators must implement this function to produce output. |
|
| 107 | + * |
|
| 108 | + * @param \DOMNode $doc The DOMNode object for the sniff. |
|
| 109 | + * It represents the "documentation" tag in the XML |
|
| 110 | + * standard file. |
|
| 111 | + * |
|
| 112 | + * @return void |
|
| 113 | + * @see generate() |
|
| 114 | + */ |
|
| 115 | + abstract protected function processSniff(\DOMNode $doc); |
|
| 116 | 116 | |
| 117 | 117 | |
| 118 | 118 | }//end class |
@@ -19,47 +19,47 @@ discard block |
||
| 19 | 19 | { |
| 20 | 20 | |
| 21 | 21 | |
| 22 | - /** |
|
| 23 | - * Generates the documentation for a standard. |
|
| 24 | - * |
|
| 25 | - * @return void |
|
| 26 | - * @see processSniff() |
|
| 27 | - */ |
|
| 28 | - public function generate() |
|
| 29 | - { |
|
| 30 | - ob_start(); |
|
| 31 | - $this->printHeader(); |
|
| 32 | - $this->printToc(); |
|
| 33 | - |
|
| 34 | - foreach ($this->docFiles as $file) { |
|
| 35 | - $doc = new \DOMDocument(); |
|
| 36 | - $doc->load($file); |
|
| 37 | - $documentation = $doc->getElementsByTagName('documentation')->item(0); |
|
| 38 | - $this->processSniff($documentation); |
|
| 39 | - } |
|
| 40 | - |
|
| 41 | - $this->printFooter(); |
|
| 42 | - |
|
| 43 | - $content = ob_get_contents(); |
|
| 44 | - ob_end_clean(); |
|
| 45 | - |
|
| 46 | - echo $content; |
|
| 47 | - |
|
| 48 | - }//end generate() |
|
| 49 | - |
|
| 50 | - |
|
| 51 | - /** |
|
| 52 | - * Print the header of the HTML page. |
|
| 53 | - * |
|
| 54 | - * @return void |
|
| 55 | - */ |
|
| 56 | - protected function printHeader() |
|
| 57 | - { |
|
| 58 | - $standard = $this->ruleset->name; |
|
| 59 | - echo '<html>'.PHP_EOL; |
|
| 60 | - echo ' <head>'.PHP_EOL; |
|
| 61 | - echo " <title>$standard Coding Standards</title>".PHP_EOL; |
|
| 62 | - echo ' <style> |
|
| 22 | + /** |
|
| 23 | + * Generates the documentation for a standard. |
|
| 24 | + * |
|
| 25 | + * @return void |
|
| 26 | + * @see processSniff() |
|
| 27 | + */ |
|
| 28 | + public function generate() |
|
| 29 | + { |
|
| 30 | + ob_start(); |
|
| 31 | + $this->printHeader(); |
|
| 32 | + $this->printToc(); |
|
| 33 | + |
|
| 34 | + foreach ($this->docFiles as $file) { |
|
| 35 | + $doc = new \DOMDocument(); |
|
| 36 | + $doc->load($file); |
|
| 37 | + $documentation = $doc->getElementsByTagName('documentation')->item(0); |
|
| 38 | + $this->processSniff($documentation); |
|
| 39 | + } |
|
| 40 | + |
|
| 41 | + $this->printFooter(); |
|
| 42 | + |
|
| 43 | + $content = ob_get_contents(); |
|
| 44 | + ob_end_clean(); |
|
| 45 | + |
|
| 46 | + echo $content; |
|
| 47 | + |
|
| 48 | + }//end generate() |
|
| 49 | + |
|
| 50 | + |
|
| 51 | + /** |
|
| 52 | + * Print the header of the HTML page. |
|
| 53 | + * |
|
| 54 | + * @return void |
|
| 55 | + */ |
|
| 56 | + protected function printHeader() |
|
| 57 | + { |
|
| 58 | + $standard = $this->ruleset->name; |
|
| 59 | + echo '<html>'.PHP_EOL; |
|
| 60 | + echo ' <head>'.PHP_EOL; |
|
| 61 | + echo " <title>$standard Coding Standards</title>".PHP_EOL; |
|
| 62 | + echo ' <style> |
|
| 63 | 63 | body { |
| 64 | 64 | background-color: #FFFFFF; |
| 65 | 65 | font-size: 14px; |
@@ -125,146 +125,146 @@ discard block |
||
| 125 | 125 | color: #000000; |
| 126 | 126 | } |
| 127 | 127 | </style>'.PHP_EOL; |
| 128 | - echo ' </head>'.PHP_EOL; |
|
| 129 | - echo ' <body>'.PHP_EOL; |
|
| 130 | - echo " <h1>$standard Coding Standards</h1>".PHP_EOL; |
|
| 131 | - |
|
| 132 | - }//end printHeader() |
|
| 133 | - |
|
| 134 | - |
|
| 135 | - /** |
|
| 136 | - * Print the table of contents for the standard. |
|
| 137 | - * |
|
| 138 | - * The TOC is just an unordered list of bookmarks to sniffs on the page. |
|
| 139 | - * |
|
| 140 | - * @return void |
|
| 141 | - */ |
|
| 142 | - protected function printToc() |
|
| 143 | - { |
|
| 144 | - echo ' <h2>Table of Contents</h2>'.PHP_EOL; |
|
| 145 | - echo ' <ul class="toc">'.PHP_EOL; |
|
| 146 | - |
|
| 147 | - foreach ($this->docFiles as $file) { |
|
| 148 | - $doc = new \DOMDocument(); |
|
| 149 | - $doc->load($file); |
|
| 150 | - $documentation = $doc->getElementsByTagName('documentation')->item(0); |
|
| 151 | - $title = $this->getTitle($documentation); |
|
| 152 | - echo ' <li><a href="#'.str_replace(' ', '-', $title)."\">$title</a></li>".PHP_EOL; |
|
| 153 | - } |
|
| 154 | - |
|
| 155 | - echo ' </ul>'.PHP_EOL; |
|
| 156 | - |
|
| 157 | - }//end printToc() |
|
| 158 | - |
|
| 159 | - |
|
| 160 | - /** |
|
| 161 | - * Print the footer of the HTML page. |
|
| 162 | - * |
|
| 163 | - * @return void |
|
| 164 | - */ |
|
| 165 | - protected function printFooter() |
|
| 166 | - { |
|
| 167 | - // Turn off errors so we don't get timezone warnings if people |
|
| 168 | - // don't have their timezone set. |
|
| 169 | - $errorLevel = error_reporting(0); |
|
| 170 | - echo ' <div class="tag-line">'; |
|
| 171 | - echo 'Documentation generated on '.date('r'); |
|
| 172 | - echo ' by <a href="https://github.com/squizlabs/PHP_CodeSniffer">PHP_CodeSniffer '.Config::VERSION.'</a>'; |
|
| 173 | - echo '</div>'.PHP_EOL; |
|
| 174 | - error_reporting($errorLevel); |
|
| 175 | - |
|
| 176 | - echo ' </body>'.PHP_EOL; |
|
| 177 | - echo '</html>'.PHP_EOL; |
|
| 178 | - |
|
| 179 | - }//end printFooter() |
|
| 180 | - |
|
| 181 | - |
|
| 182 | - /** |
|
| 183 | - * Process the documentation for a single sniff. |
|
| 184 | - * |
|
| 185 | - * @param \DOMNode $doc The DOMNode object for the sniff. |
|
| 186 | - * It represents the "documentation" tag in the XML |
|
| 187 | - * standard file. |
|
| 188 | - * |
|
| 189 | - * @return void |
|
| 190 | - */ |
|
| 191 | - public function processSniff(\DOMNode $doc) |
|
| 192 | - { |
|
| 193 | - $title = $this->getTitle($doc); |
|
| 194 | - echo ' <a name="'.str_replace(' ', '-', $title).'" />'.PHP_EOL; |
|
| 195 | - echo " <h2>$title</h2>".PHP_EOL; |
|
| 196 | - |
|
| 197 | - foreach ($doc->childNodes as $node) { |
|
| 198 | - if ($node->nodeName === 'standard') { |
|
| 199 | - $this->printTextBlock($node); |
|
| 200 | - } else if ($node->nodeName === 'code_comparison') { |
|
| 201 | - $this->printCodeComparisonBlock($node); |
|
| 202 | - } |
|
| 203 | - } |
|
| 204 | - |
|
| 205 | - }//end processSniff() |
|
| 206 | - |
|
| 207 | - |
|
| 208 | - /** |
|
| 209 | - * Print a text block found in a standard. |
|
| 210 | - * |
|
| 211 | - * @param \DOMNode $node The DOMNode object for the text block. |
|
| 212 | - * |
|
| 213 | - * @return void |
|
| 214 | - */ |
|
| 215 | - protected function printTextBlock(\DOMNode $node) |
|
| 216 | - { |
|
| 217 | - $content = trim($node->nodeValue); |
|
| 218 | - $content = htmlspecialchars($content); |
|
| 219 | - |
|
| 220 | - // Allow em tags only. |
|
| 221 | - $content = str_replace('<em>', '<em>', $content); |
|
| 222 | - $content = str_replace('</em>', '</em>', $content); |
|
| 223 | - |
|
| 224 | - echo " <p class=\"text\">$content</p>".PHP_EOL; |
|
| 225 | - |
|
| 226 | - }//end printTextBlock() |
|
| 227 | - |
|
| 228 | - |
|
| 229 | - /** |
|
| 230 | - * Print a code comparison block found in a standard. |
|
| 231 | - * |
|
| 232 | - * @param \DOMNode $node The DOMNode object for the code comparison block. |
|
| 233 | - * |
|
| 234 | - * @return void |
|
| 235 | - */ |
|
| 236 | - protected function printCodeComparisonBlock(\DOMNode $node) |
|
| 237 | - { |
|
| 238 | - $codeBlocks = $node->getElementsByTagName('code'); |
|
| 239 | - |
|
| 240 | - $firstTitle = $codeBlocks->item(0)->getAttribute('title'); |
|
| 241 | - $first = trim($codeBlocks->item(0)->nodeValue); |
|
| 242 | - $first = str_replace('<?php', '<?php', $first); |
|
| 243 | - $first = str_replace("\n", '</br>', $first); |
|
| 244 | - $first = str_replace(' ', ' ', $first); |
|
| 245 | - $first = str_replace('<em>', '<span class="code-comparison-highlight">', $first); |
|
| 246 | - $first = str_replace('</em>', '</span>', $first); |
|
| 247 | - |
|
| 248 | - $secondTitle = $codeBlocks->item(1)->getAttribute('title'); |
|
| 249 | - $second = trim($codeBlocks->item(1)->nodeValue); |
|
| 250 | - $second = str_replace('<?php', '<?php', $second); |
|
| 251 | - $second = str_replace("\n", '</br>', $second); |
|
| 252 | - $second = str_replace(' ', ' ', $second); |
|
| 253 | - $second = str_replace('<em>', '<span class="code-comparison-highlight">', $second); |
|
| 254 | - $second = str_replace('</em>', '</span>', $second); |
|
| 255 | - |
|
| 256 | - echo ' <table class="code-comparison">'.PHP_EOL; |
|
| 257 | - echo ' <tr>'.PHP_EOL; |
|
| 258 | - echo " <td class=\"code-comparison-title\">$firstTitle</td>".PHP_EOL; |
|
| 259 | - echo " <td class=\"code-comparison-title\">$secondTitle</td>".PHP_EOL; |
|
| 260 | - echo ' </tr>'.PHP_EOL; |
|
| 261 | - echo ' <tr>'.PHP_EOL; |
|
| 262 | - echo " <td class=\"code-comparison-code\">$first</td>".PHP_EOL; |
|
| 263 | - echo " <td class=\"code-comparison-code\">$second</td>".PHP_EOL; |
|
| 264 | - echo ' </tr>'.PHP_EOL; |
|
| 265 | - echo ' </table>'.PHP_EOL; |
|
| 266 | - |
|
| 267 | - }//end printCodeComparisonBlock() |
|
| 128 | + echo ' </head>'.PHP_EOL; |
|
| 129 | + echo ' <body>'.PHP_EOL; |
|
| 130 | + echo " <h1>$standard Coding Standards</h1>".PHP_EOL; |
|
| 131 | + |
|
| 132 | + }//end printHeader() |
|
| 133 | + |
|
| 134 | + |
|
| 135 | + /** |
|
| 136 | + * Print the table of contents for the standard. |
|
| 137 | + * |
|
| 138 | + * The TOC is just an unordered list of bookmarks to sniffs on the page. |
|
| 139 | + * |
|
| 140 | + * @return void |
|
| 141 | + */ |
|
| 142 | + protected function printToc() |
|
| 143 | + { |
|
| 144 | + echo ' <h2>Table of Contents</h2>'.PHP_EOL; |
|
| 145 | + echo ' <ul class="toc">'.PHP_EOL; |
|
| 146 | + |
|
| 147 | + foreach ($this->docFiles as $file) { |
|
| 148 | + $doc = new \DOMDocument(); |
|
| 149 | + $doc->load($file); |
|
| 150 | + $documentation = $doc->getElementsByTagName('documentation')->item(0); |
|
| 151 | + $title = $this->getTitle($documentation); |
|
| 152 | + echo ' <li><a href="#'.str_replace(' ', '-', $title)."\">$title</a></li>".PHP_EOL; |
|
| 153 | + } |
|
| 154 | + |
|
| 155 | + echo ' </ul>'.PHP_EOL; |
|
| 156 | + |
|
| 157 | + }//end printToc() |
|
| 158 | + |
|
| 159 | + |
|
| 160 | + /** |
|
| 161 | + * Print the footer of the HTML page. |
|
| 162 | + * |
|
| 163 | + * @return void |
|
| 164 | + */ |
|
| 165 | + protected function printFooter() |
|
| 166 | + { |
|
| 167 | + // Turn off errors so we don't get timezone warnings if people |
|
| 168 | + // don't have their timezone set. |
|
| 169 | + $errorLevel = error_reporting(0); |
|
| 170 | + echo ' <div class="tag-line">'; |
|
| 171 | + echo 'Documentation generated on '.date('r'); |
|
| 172 | + echo ' by <a href="https://github.com/squizlabs/PHP_CodeSniffer">PHP_CodeSniffer '.Config::VERSION.'</a>'; |
|
| 173 | + echo '</div>'.PHP_EOL; |
|
| 174 | + error_reporting($errorLevel); |
|
| 175 | + |
|
| 176 | + echo ' </body>'.PHP_EOL; |
|
| 177 | + echo '</html>'.PHP_EOL; |
|
| 178 | + |
|
| 179 | + }//end printFooter() |
|
| 180 | + |
|
| 181 | + |
|
| 182 | + /** |
|
| 183 | + * Process the documentation for a single sniff. |
|
| 184 | + * |
|
| 185 | + * @param \DOMNode $doc The DOMNode object for the sniff. |
|
| 186 | + * It represents the "documentation" tag in the XML |
|
| 187 | + * standard file. |
|
| 188 | + * |
|
| 189 | + * @return void |
|
| 190 | + */ |
|
| 191 | + public function processSniff(\DOMNode $doc) |
|
| 192 | + { |
|
| 193 | + $title = $this->getTitle($doc); |
|
| 194 | + echo ' <a name="'.str_replace(' ', '-', $title).'" />'.PHP_EOL; |
|
| 195 | + echo " <h2>$title</h2>".PHP_EOL; |
|
| 196 | + |
|
| 197 | + foreach ($doc->childNodes as $node) { |
|
| 198 | + if ($node->nodeName === 'standard') { |
|
| 199 | + $this->printTextBlock($node); |
|
| 200 | + } else if ($node->nodeName === 'code_comparison') { |
|
| 201 | + $this->printCodeComparisonBlock($node); |
|
| 202 | + } |
|
| 203 | + } |
|
| 204 | + |
|
| 205 | + }//end processSniff() |
|
| 206 | + |
|
| 207 | + |
|
| 208 | + /** |
|
| 209 | + * Print a text block found in a standard. |
|
| 210 | + * |
|
| 211 | + * @param \DOMNode $node The DOMNode object for the text block. |
|
| 212 | + * |
|
| 213 | + * @return void |
|
| 214 | + */ |
|
| 215 | + protected function printTextBlock(\DOMNode $node) |
|
| 216 | + { |
|
| 217 | + $content = trim($node->nodeValue); |
|
| 218 | + $content = htmlspecialchars($content); |
|
| 219 | + |
|
| 220 | + // Allow em tags only. |
|
| 221 | + $content = str_replace('<em>', '<em>', $content); |
|
| 222 | + $content = str_replace('</em>', '</em>', $content); |
|
| 223 | + |
|
| 224 | + echo " <p class=\"text\">$content</p>".PHP_EOL; |
|
| 225 | + |
|
| 226 | + }//end printTextBlock() |
|
| 227 | + |
|
| 228 | + |
|
| 229 | + /** |
|
| 230 | + * Print a code comparison block found in a standard. |
|
| 231 | + * |
|
| 232 | + * @param \DOMNode $node The DOMNode object for the code comparison block. |
|
| 233 | + * |
|
| 234 | + * @return void |
|
| 235 | + */ |
|
| 236 | + protected function printCodeComparisonBlock(\DOMNode $node) |
|
| 237 | + { |
|
| 238 | + $codeBlocks = $node->getElementsByTagName('code'); |
|
| 239 | + |
|
| 240 | + $firstTitle = $codeBlocks->item(0)->getAttribute('title'); |
|
| 241 | + $first = trim($codeBlocks->item(0)->nodeValue); |
|
| 242 | + $first = str_replace('<?php', '<?php', $first); |
|
| 243 | + $first = str_replace("\n", '</br>', $first); |
|
| 244 | + $first = str_replace(' ', ' ', $first); |
|
| 245 | + $first = str_replace('<em>', '<span class="code-comparison-highlight">', $first); |
|
| 246 | + $first = str_replace('</em>', '</span>', $first); |
|
| 247 | + |
|
| 248 | + $secondTitle = $codeBlocks->item(1)->getAttribute('title'); |
|
| 249 | + $second = trim($codeBlocks->item(1)->nodeValue); |
|
| 250 | + $second = str_replace('<?php', '<?php', $second); |
|
| 251 | + $second = str_replace("\n", '</br>', $second); |
|
| 252 | + $second = str_replace(' ', ' ', $second); |
|
| 253 | + $second = str_replace('<em>', '<span class="code-comparison-highlight">', $second); |
|
| 254 | + $second = str_replace('</em>', '</span>', $second); |
|
| 255 | + |
|
| 256 | + echo ' <table class="code-comparison">'.PHP_EOL; |
|
| 257 | + echo ' <tr>'.PHP_EOL; |
|
| 258 | + echo " <td class=\"code-comparison-title\">$firstTitle</td>".PHP_EOL; |
|
| 259 | + echo " <td class=\"code-comparison-title\">$secondTitle</td>".PHP_EOL; |
|
| 260 | + echo ' </tr>'.PHP_EOL; |
|
| 261 | + echo ' <tr>'.PHP_EOL; |
|
| 262 | + echo " <td class=\"code-comparison-code\">$first</td>".PHP_EOL; |
|
| 263 | + echo " <td class=\"code-comparison-code\">$second</td>".PHP_EOL; |
|
| 264 | + echo ' </tr>'.PHP_EOL; |
|
| 265 | + echo ' </table>'.PHP_EOL; |
|
| 266 | + |
|
| 267 | + }//end printCodeComparisonBlock() |
|
| 268 | 268 | |
| 269 | 269 | |
| 270 | 270 | }//end class |
@@ -46,7 +46,7 @@ |
||
| 46 | 46 | |
| 47 | 47 | $suite = new TestSuite('PHP CodeSniffer Standards'); |
| 48 | 48 | |
| 49 | - $isInstalled = !is_file(__DIR__.'/../../autoload.php'); |
|
| 49 | + $isInstalled = ! is_file(__DIR__.'/../../autoload.php'); |
|
| 50 | 50 | |
| 51 | 51 | // Optionally allow for ignoring the tests for one or more standards. |
| 52 | 52 | $ignoreTestsForStandards = getenv('PHPCS_IGNORE_TESTS'); |
@@ -18,106 +18,106 @@ |
||
| 18 | 18 | { |
| 19 | 19 | |
| 20 | 20 | |
| 21 | - /** |
|
| 22 | - * Prepare the test runner. |
|
| 23 | - * |
|
| 24 | - * @return void |
|
| 25 | - */ |
|
| 26 | - public static function main() |
|
| 27 | - { |
|
| 28 | - TestRunner::run(self::suite()); |
|
| 29 | - |
|
| 30 | - }//end main() |
|
| 31 | - |
|
| 32 | - |
|
| 33 | - /** |
|
| 34 | - * Add all sniff unit tests into a test suite. |
|
| 35 | - * |
|
| 36 | - * Sniff unit tests are found by recursing through the 'Tests' directory |
|
| 37 | - * of each installed coding standard. |
|
| 38 | - * |
|
| 39 | - * @return \PHPUnit\Framework\TestSuite |
|
| 40 | - */ |
|
| 41 | - public static function suite() |
|
| 42 | - { |
|
| 43 | - $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'] = []; |
|
| 44 | - $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'] = []; |
|
| 45 | - $GLOBALS['PHP_CODESNIFFER_SNIFF_CASE_FILES'] = []; |
|
| 46 | - |
|
| 47 | - $suite = new TestSuite('PHP CodeSniffer Standards'); |
|
| 48 | - |
|
| 49 | - $isInstalled = !is_file(__DIR__.'/../../autoload.php'); |
|
| 50 | - |
|
| 51 | - // Optionally allow for ignoring the tests for one or more standards. |
|
| 52 | - $ignoreTestsForStandards = getenv('PHPCS_IGNORE_TESTS'); |
|
| 53 | - if ($ignoreTestsForStandards === false) { |
|
| 54 | - $ignoreTestsForStandards = []; |
|
| 55 | - } else { |
|
| 56 | - $ignoreTestsForStandards = explode(',', $ignoreTestsForStandards); |
|
| 57 | - } |
|
| 58 | - |
|
| 59 | - $installedStandards = self::getInstalledStandardDetails(); |
|
| 60 | - |
|
| 61 | - foreach ($installedStandards as $standard => $details) { |
|
| 62 | - Autoload::addSearchPath($details['path'], $details['namespace']); |
|
| 63 | - |
|
| 64 | - // If the test is running PEAR installed, the built-in standards |
|
| 65 | - // are split into different directories; one for the sniffs and |
|
| 66 | - // a different file system location for tests. |
|
| 67 | - if ($isInstalled === true && is_dir(dirname($details['path']).DIRECTORY_SEPARATOR.'Generic') === true) { |
|
| 68 | - $testPath = realpath(__DIR__.'/../../src/Standards/'.$standard); |
|
| 69 | - } else { |
|
| 70 | - $testPath = $details['path']; |
|
| 71 | - } |
|
| 72 | - |
|
| 73 | - if (in_array($standard, $ignoreTestsForStandards, true) === true) { |
|
| 74 | - continue; |
|
| 75 | - } |
|
| 76 | - |
|
| 77 | - $testsDir = $testPath.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR; |
|
| 78 | - if (is_dir($testsDir) === false) { |
|
| 79 | - // No tests for this standard. |
|
| 80 | - continue; |
|
| 81 | - } |
|
| 82 | - |
|
| 83 | - $di = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($testsDir)); |
|
| 84 | - |
|
| 85 | - foreach ($di as $file) { |
|
| 86 | - // Skip hidden files. |
|
| 87 | - if (substr($file->getFilename(), 0, 1) === '.') { |
|
| 88 | - continue; |
|
| 89 | - } |
|
| 90 | - |
|
| 91 | - // Tests must have the extension 'php'. |
|
| 92 | - $parts = explode('.', $file); |
|
| 93 | - $ext = array_pop($parts); |
|
| 94 | - if ($ext !== 'php') { |
|
| 95 | - continue; |
|
| 96 | - } |
|
| 97 | - |
|
| 98 | - $className = Autoload::loadFile($file->getPathname()); |
|
| 99 | - $GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'][$className] = $details['path']; |
|
| 100 | - $GLOBALS['PHP_CODESNIFFER_TEST_DIRS'][$className] = $testsDir; |
|
| 101 | - $suite->addTestSuite($className); |
|
| 102 | - } |
|
| 103 | - }//end foreach |
|
| 104 | - |
|
| 105 | - return $suite; |
|
| 106 | - |
|
| 107 | - }//end suite() |
|
| 108 | - |
|
| 109 | - |
|
| 110 | - /** |
|
| 111 | - * Get the details of all coding standards installed. |
|
| 112 | - * |
|
| 113 | - * @return array |
|
| 114 | - * @see Standards::getInstalledStandardDetails() |
|
| 115 | - */ |
|
| 116 | - protected static function getInstalledStandardDetails() |
|
| 117 | - { |
|
| 118 | - return Standards::getInstalledStandardDetails(true); |
|
| 119 | - |
|
| 120 | - }//end getInstalledStandardDetails() |
|
| 21 | + /** |
|
| 22 | + * Prepare the test runner. |
|
| 23 | + * |
|
| 24 | + * @return void |
|
| 25 | + */ |
|
| 26 | + public static function main() |
|
| 27 | + { |
|
| 28 | + TestRunner::run(self::suite()); |
|
| 29 | + |
|
| 30 | + }//end main() |
|
| 31 | + |
|
| 32 | + |
|
| 33 | + /** |
|
| 34 | + * Add all sniff unit tests into a test suite. |
|
| 35 | + * |
|
| 36 | + * Sniff unit tests are found by recursing through the 'Tests' directory |
|
| 37 | + * of each installed coding standard. |
|
| 38 | + * |
|
| 39 | + * @return \PHPUnit\Framework\TestSuite |
|
| 40 | + */ |
|
| 41 | + public static function suite() |
|
| 42 | + { |
|
| 43 | + $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'] = []; |
|
| 44 | + $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'] = []; |
|
| 45 | + $GLOBALS['PHP_CODESNIFFER_SNIFF_CASE_FILES'] = []; |
|
| 46 | + |
|
| 47 | + $suite = new TestSuite('PHP CodeSniffer Standards'); |
|
| 48 | + |
|
| 49 | + $isInstalled = !is_file(__DIR__.'/../../autoload.php'); |
|
| 50 | + |
|
| 51 | + // Optionally allow for ignoring the tests for one or more standards. |
|
| 52 | + $ignoreTestsForStandards = getenv('PHPCS_IGNORE_TESTS'); |
|
| 53 | + if ($ignoreTestsForStandards === false) { |
|
| 54 | + $ignoreTestsForStandards = []; |
|
| 55 | + } else { |
|
| 56 | + $ignoreTestsForStandards = explode(',', $ignoreTestsForStandards); |
|
| 57 | + } |
|
| 58 | + |
|
| 59 | + $installedStandards = self::getInstalledStandardDetails(); |
|
| 60 | + |
|
| 61 | + foreach ($installedStandards as $standard => $details) { |
|
| 62 | + Autoload::addSearchPath($details['path'], $details['namespace']); |
|
| 63 | + |
|
| 64 | + // If the test is running PEAR installed, the built-in standards |
|
| 65 | + // are split into different directories; one for the sniffs and |
|
| 66 | + // a different file system location for tests. |
|
| 67 | + if ($isInstalled === true && is_dir(dirname($details['path']).DIRECTORY_SEPARATOR.'Generic') === true) { |
|
| 68 | + $testPath = realpath(__DIR__.'/../../src/Standards/'.$standard); |
|
| 69 | + } else { |
|
| 70 | + $testPath = $details['path']; |
|
| 71 | + } |
|
| 72 | + |
|
| 73 | + if (in_array($standard, $ignoreTestsForStandards, true) === true) { |
|
| 74 | + continue; |
|
| 75 | + } |
|
| 76 | + |
|
| 77 | + $testsDir = $testPath.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR; |
|
| 78 | + if (is_dir($testsDir) === false) { |
|
| 79 | + // No tests for this standard. |
|
| 80 | + continue; |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + $di = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($testsDir)); |
|
| 84 | + |
|
| 85 | + foreach ($di as $file) { |
|
| 86 | + // Skip hidden files. |
|
| 87 | + if (substr($file->getFilename(), 0, 1) === '.') { |
|
| 88 | + continue; |
|
| 89 | + } |
|
| 90 | + |
|
| 91 | + // Tests must have the extension 'php'. |
|
| 92 | + $parts = explode('.', $file); |
|
| 93 | + $ext = array_pop($parts); |
|
| 94 | + if ($ext !== 'php') { |
|
| 95 | + continue; |
|
| 96 | + } |
|
| 97 | + |
|
| 98 | + $className = Autoload::loadFile($file->getPathname()); |
|
| 99 | + $GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'][$className] = $details['path']; |
|
| 100 | + $GLOBALS['PHP_CODESNIFFER_TEST_DIRS'][$className] = $testsDir; |
|
| 101 | + $suite->addTestSuite($className); |
|
| 102 | + } |
|
| 103 | + }//end foreach |
|
| 104 | + |
|
| 105 | + return $suite; |
|
| 106 | + |
|
| 107 | + }//end suite() |
|
| 108 | + |
|
| 109 | + |
|
| 110 | + /** |
|
| 111 | + * Get the details of all coding standards installed. |
|
| 112 | + * |
|
| 113 | + * @return array |
|
| 114 | + * @see Standards::getInstalledStandardDetails() |
|
| 115 | + */ |
|
| 116 | + protected static function getInstalledStandardDetails() |
|
| 117 | + { |
|
| 118 | + return Standards::getInstalledStandardDetails(true); |
|
| 119 | + |
|
| 120 | + }//end getInstalledStandardDetails() |
|
| 121 | 121 | |
| 122 | 122 | |
| 123 | 123 | }//end class |