Chris@17
|
1 <?php
|
Chris@17
|
2 /**
|
Chris@17
|
3 * The base tokenizer class.
|
Chris@17
|
4 *
|
Chris@17
|
5 * @author Greg Sherwood <gsherwood@squiz.net>
|
Chris@17
|
6 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
|
Chris@17
|
7 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
|
Chris@17
|
8 */
|
Chris@17
|
9
|
Chris@17
|
10 namespace PHP_CodeSniffer\Tokenizers;
|
Chris@17
|
11
|
Chris@17
|
12 use PHP_CodeSniffer\Exceptions\RuntimeException;
|
Chris@17
|
13 use PHP_CodeSniffer\Util;
|
Chris@17
|
14
|
Chris@17
|
15 abstract class Tokenizer
|
Chris@17
|
16 {
|
Chris@17
|
17
|
Chris@17
|
18 /**
|
Chris@17
|
19 * The config data for the run.
|
Chris@17
|
20 *
|
Chris@17
|
21 * @var \PHP_CodeSniffer\Config
|
Chris@17
|
22 */
|
Chris@17
|
23 protected $config = null;
|
Chris@17
|
24
|
Chris@17
|
25 /**
|
Chris@17
|
26 * The EOL char used in the content.
|
Chris@17
|
27 *
|
Chris@17
|
28 * @var string
|
Chris@17
|
29 */
|
Chris@17
|
30 protected $eolChar = [];
|
Chris@17
|
31
|
Chris@17
|
32 /**
|
Chris@17
|
33 * A token-based representation of the content.
|
Chris@17
|
34 *
|
Chris@17
|
35 * @var array
|
Chris@17
|
36 */
|
Chris@17
|
37 protected $tokens = [];
|
Chris@17
|
38
|
Chris@17
|
39 /**
|
Chris@18
|
40 * The number of tokens in the tokens array.
|
Chris@18
|
41 *
|
Chris@18
|
42 * @var integer
|
Chris@18
|
43 */
|
Chris@18
|
44 protected $numTokens = 0;
|
Chris@18
|
45
|
Chris@18
|
46 /**
|
Chris@18
|
47 * A list of tokens that are allowed to open a scope.
|
Chris@18
|
48 *
|
Chris@18
|
49 * @var array
|
Chris@18
|
50 */
|
Chris@18
|
51 public $scopeOpeners = [];
|
Chris@18
|
52
|
Chris@18
|
53 /**
|
Chris@18
|
54 * A list of tokens that end the scope.
|
Chris@18
|
55 *
|
Chris@18
|
56 * @var array
|
Chris@18
|
57 */
|
Chris@18
|
58 public $endScopeTokens = [];
|
Chris@18
|
59
|
Chris@18
|
60 /**
|
Chris@17
|
61 * Known lengths of tokens.
|
Chris@17
|
62 *
|
Chris@17
|
63 * @var array<int, int>
|
Chris@17
|
64 */
|
Chris@17
|
65 public $knownLengths = [];
|
Chris@17
|
66
|
Chris@17
|
67 /**
|
Chris@17
|
68 * A list of lines being ignored due to error suppression comments.
|
Chris@17
|
69 *
|
Chris@17
|
70 * @var array
|
Chris@17
|
71 */
|
Chris@17
|
72 public $ignoredLines = [];
|
Chris@17
|
73
|
Chris@17
|
74
|
Chris@17
|
75 /**
|
Chris@17
|
76 * Initialise and run the tokenizer.
|
Chris@17
|
77 *
|
Chris@17
|
78 * @param string $content The content to tokenize,
|
Chris@17
|
79 * @param \PHP_CodeSniffer\Config | null $config The config data for the run.
|
Chris@17
|
80 * @param string $eolChar The EOL char used in the content.
|
Chris@17
|
81 *
|
Chris@17
|
82 * @return void
|
Chris@18
|
83 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the file appears to be minified.
|
Chris@17
|
84 */
|
Chris@17
|
85 public function __construct($content, $config, $eolChar='\n')
|
Chris@17
|
86 {
|
Chris@17
|
87 $this->eolChar = $eolChar;
|
Chris@17
|
88
|
Chris@17
|
89 $this->config = $config;
|
Chris@17
|
90 $this->tokens = $this->tokenize($content);
|
Chris@17
|
91
|
Chris@17
|
92 if ($config === null) {
|
Chris@17
|
93 return;
|
Chris@17
|
94 }
|
Chris@17
|
95
|
Chris@17
|
96 $this->createPositionMap();
|
Chris@17
|
97 $this->createTokenMap();
|
Chris@17
|
98 $this->createParenthesisNestingMap();
|
Chris@17
|
99 $this->createScopeMap();
|
Chris@17
|
100 $this->createLevelMap();
|
Chris@17
|
101
|
Chris@17
|
102 // Allow the tokenizer to do additional processing if required.
|
Chris@17
|
103 $this->processAdditional();
|
Chris@17
|
104
|
Chris@17
|
105 }//end __construct()
|
Chris@17
|
106
|
Chris@17
|
107
|
Chris@17
|
108 /**
|
Chris@17
|
109 * Checks the content to see if it looks minified.
|
Chris@17
|
110 *
|
Chris@17
|
111 * @param string $content The content to tokenize.
|
Chris@17
|
112 * @param string $eolChar The EOL char used in the content.
|
Chris@17
|
113 *
|
Chris@17
|
114 * @return boolean
|
Chris@17
|
115 */
|
Chris@17
|
116 protected function isMinifiedContent($content, $eolChar='\n')
|
Chris@17
|
117 {
|
Chris@17
|
118 // Minified files often have a very large number of characters per line
|
Chris@17
|
119 // and cause issues when tokenizing.
|
Chris@17
|
120 $numChars = strlen($content);
|
Chris@17
|
121 $numLines = (substr_count($content, $eolChar) + 1);
|
Chris@17
|
122 $average = ($numChars / $numLines);
|
Chris@17
|
123 if ($average > 100) {
|
Chris@17
|
124 return true;
|
Chris@17
|
125 }
|
Chris@17
|
126
|
Chris@17
|
127 return false;
|
Chris@17
|
128
|
Chris@17
|
129 }//end isMinifiedContent()
|
Chris@17
|
130
|
Chris@17
|
131
|
Chris@17
|
132 /**
|
Chris@17
|
133 * Gets the array of tokens.
|
Chris@17
|
134 *
|
Chris@17
|
135 * @return array
|
Chris@17
|
136 */
|
Chris@17
|
137 public function getTokens()
|
Chris@17
|
138 {
|
Chris@17
|
139 return $this->tokens;
|
Chris@17
|
140
|
Chris@17
|
141 }//end getTokens()
|
Chris@17
|
142
|
Chris@17
|
143
|
Chris@17
|
144 /**
|
Chris@17
|
145 * Creates an array of tokens when given some content.
|
Chris@17
|
146 *
|
Chris@17
|
147 * @param string $string The string to tokenize.
|
Chris@17
|
148 *
|
Chris@17
|
149 * @return array
|
Chris@17
|
150 */
|
Chris@17
|
151 abstract protected function tokenize($string);
|
Chris@17
|
152
|
Chris@17
|
153
|
Chris@17
|
154 /**
|
Chris@17
|
155 * Performs additional processing after main tokenizing.
|
Chris@17
|
156 *
|
Chris@17
|
157 * @return void
|
Chris@17
|
158 */
|
Chris@17
|
159 abstract protected function processAdditional();
|
Chris@17
|
160
|
Chris@17
|
161
|
Chris@17
|
162 /**
|
Chris@17
|
163 * Sets token position information.
|
Chris@17
|
164 *
|
Chris@17
|
165 * Can also convert tabs into spaces. Each tab can represent between
|
Chris@17
|
166 * 1 and $width spaces, so this cannot be a straight string replace.
|
Chris@17
|
167 *
|
Chris@17
|
168 * @return void
|
Chris@17
|
169 */
|
Chris@17
|
170 private function createPositionMap()
|
Chris@17
|
171 {
|
Chris@17
|
172 $currColumn = 1;
|
Chris@17
|
173 $lineNumber = 1;
|
Chris@17
|
174 $eolLen = strlen($this->eolChar);
|
Chris@17
|
175 $ignoring = null;
|
Chris@17
|
176 $inTests = defined('PHP_CODESNIFFER_IN_TESTS');
|
Chris@17
|
177
|
Chris@17
|
178 $checkEncoding = false;
|
Chris@17
|
179 if (function_exists('iconv_strlen') === true) {
|
Chris@17
|
180 $checkEncoding = true;
|
Chris@17
|
181 }
|
Chris@17
|
182
|
Chris@17
|
183 $checkAnnotations = $this->config->annotations;
|
Chris@17
|
184 $encoding = $this->config->encoding;
|
Chris@17
|
185 $tabWidth = $this->config->tabWidth;
|
Chris@17
|
186
|
Chris@18
|
187 $tokensWithTabs = [
|
Chris@17
|
188 T_WHITESPACE => true,
|
Chris@17
|
189 T_COMMENT => true,
|
Chris@17
|
190 T_DOC_COMMENT => true,
|
Chris@17
|
191 T_DOC_COMMENT_WHITESPACE => true,
|
Chris@17
|
192 T_DOC_COMMENT_STRING => true,
|
Chris@17
|
193 T_CONSTANT_ENCAPSED_STRING => true,
|
Chris@17
|
194 T_DOUBLE_QUOTED_STRING => true,
|
Chris@17
|
195 T_HEREDOC => true,
|
Chris@17
|
196 T_NOWDOC => true,
|
Chris@17
|
197 T_INLINE_HTML => true,
|
Chris@17
|
198 ];
|
Chris@17
|
199
|
Chris@17
|
200 $this->numTokens = count($this->tokens);
|
Chris@17
|
201 for ($i = 0; $i < $this->numTokens; $i++) {
|
Chris@17
|
202 $this->tokens[$i]['line'] = $lineNumber;
|
Chris@17
|
203 $this->tokens[$i]['column'] = $currColumn;
|
Chris@17
|
204
|
Chris@17
|
205 if (isset($this->knownLengths[$this->tokens[$i]['code']]) === true) {
|
Chris@17
|
206 // There are no tabs in the tokens we know the length of.
|
Chris@17
|
207 $length = $this->knownLengths[$this->tokens[$i]['code']];
|
Chris@17
|
208 $currColumn += $length;
|
Chris@17
|
209 } else if ($tabWidth === 0
|
Chris@18
|
210 || isset($tokensWithTabs[$this->tokens[$i]['code']]) === false
|
Chris@17
|
211 || strpos($this->tokens[$i]['content'], "\t") === false
|
Chris@17
|
212 ) {
|
Chris@17
|
213 // There are no tabs in this content, or we aren't replacing them.
|
Chris@17
|
214 if ($checkEncoding === true) {
|
Chris@17
|
215 // Not using the default encoding, so take a bit more care.
|
Chris@17
|
216 $oldLevel = error_reporting();
|
Chris@17
|
217 error_reporting(0);
|
Chris@17
|
218 $length = iconv_strlen($this->tokens[$i]['content'], $encoding);
|
Chris@17
|
219 error_reporting($oldLevel);
|
Chris@17
|
220
|
Chris@17
|
221 if ($length === false) {
|
Chris@17
|
222 // String contained invalid characters, so revert to default.
|
Chris@17
|
223 $length = strlen($this->tokens[$i]['content']);
|
Chris@17
|
224 }
|
Chris@17
|
225 } else {
|
Chris@17
|
226 $length = strlen($this->tokens[$i]['content']);
|
Chris@17
|
227 }
|
Chris@17
|
228
|
Chris@17
|
229 $currColumn += $length;
|
Chris@17
|
230 } else {
|
Chris@17
|
231 $this->replaceTabsInToken($this->tokens[$i]);
|
Chris@17
|
232 $length = $this->tokens[$i]['length'];
|
Chris@17
|
233 $currColumn += $length;
|
Chris@17
|
234 }//end if
|
Chris@17
|
235
|
Chris@17
|
236 $this->tokens[$i]['length'] = $length;
|
Chris@17
|
237
|
Chris@17
|
238 if (isset($this->knownLengths[$this->tokens[$i]['code']]) === false
|
Chris@17
|
239 && strpos($this->tokens[$i]['content'], $this->eolChar) !== false
|
Chris@17
|
240 ) {
|
Chris@17
|
241 $lineNumber++;
|
Chris@17
|
242 $currColumn = 1;
|
Chris@17
|
243
|
Chris@17
|
244 // Newline chars are not counted in the token length.
|
Chris@17
|
245 $this->tokens[$i]['length'] -= $eolLen;
|
Chris@17
|
246 }
|
Chris@17
|
247
|
Chris@17
|
248 if ($this->tokens[$i]['code'] === T_COMMENT
|
Chris@17
|
249 || $this->tokens[$i]['code'] === T_DOC_COMMENT_STRING
|
Chris@17
|
250 || $this->tokens[$i]['code'] === T_DOC_COMMENT_TAG
|
Chris@17
|
251 || ($inTests === true && $this->tokens[$i]['code'] === T_INLINE_HTML)
|
Chris@17
|
252 ) {
|
Chris@17
|
253 $commentText = ltrim($this->tokens[$i]['content'], " \t/*");
|
Chris@17
|
254 $commentText = rtrim($commentText, " */\t\r\n");
|
Chris@17
|
255 $commentTextLower = strtolower($commentText);
|
Chris@17
|
256 if (strpos($commentText, '@codingStandards') !== false) {
|
Chris@17
|
257 // If this comment is the only thing on the line, it tells us
|
Chris@17
|
258 // to ignore the following line. If the line contains other content
|
Chris@17
|
259 // then we are just ignoring this one single line.
|
Chris@17
|
260 $ownLine = false;
|
Chris@17
|
261 if ($i > 0) {
|
Chris@17
|
262 for ($prev = ($i - 1); $prev >= 0; $prev--) {
|
Chris@17
|
263 if ($this->tokens[$prev]['code'] === T_WHITESPACE) {
|
Chris@17
|
264 continue;
|
Chris@17
|
265 }
|
Chris@17
|
266
|
Chris@17
|
267 break;
|
Chris@17
|
268 }
|
Chris@17
|
269
|
Chris@17
|
270 if ($this->tokens[$prev]['line'] !== $this->tokens[$i]['line']) {
|
Chris@17
|
271 $ownLine = true;
|
Chris@17
|
272 }
|
Chris@17
|
273 }
|
Chris@17
|
274
|
Chris@17
|
275 if ($ignoring === null
|
Chris@17
|
276 && strpos($commentText, '@codingStandardsIgnoreStart') !== false
|
Chris@17
|
277 ) {
|
Chris@17
|
278 $ignoring = ['.all' => true];
|
Chris@17
|
279 if ($ownLine === true) {
|
Chris@17
|
280 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
|
Chris@17
|
281 }
|
Chris@17
|
282 } else if ($ignoring !== null
|
Chris@17
|
283 && strpos($commentText, '@codingStandardsIgnoreEnd') !== false
|
Chris@17
|
284 ) {
|
Chris@17
|
285 if ($ownLine === true) {
|
Chris@17
|
286 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
|
Chris@17
|
287 } else {
|
Chris@17
|
288 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
|
Chris@17
|
289 }
|
Chris@17
|
290
|
Chris@17
|
291 $ignoring = null;
|
Chris@17
|
292 } else if ($ignoring === null
|
Chris@17
|
293 && strpos($commentText, '@codingStandardsIgnoreLine') !== false
|
Chris@17
|
294 ) {
|
Chris@17
|
295 $ignoring = ['.all' => true];
|
Chris@17
|
296 if ($ownLine === true) {
|
Chris@17
|
297 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
|
Chris@17
|
298 $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = $ignoring;
|
Chris@17
|
299 } else {
|
Chris@17
|
300 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
|
Chris@17
|
301 }
|
Chris@17
|
302
|
Chris@17
|
303 $ignoring = null;
|
Chris@17
|
304 }//end if
|
Chris@17
|
305 } else if (substr($commentTextLower, 0, 6) === 'phpcs:'
|
Chris@17
|
306 || substr($commentTextLower, 0, 7) === '@phpcs:'
|
Chris@17
|
307 ) {
|
Chris@17
|
308 // If the @phpcs: syntax is being used, strip the @ to make
|
Chris@17
|
309 // comparisons easier.
|
Chris@17
|
310 if ($commentText[0] === '@') {
|
Chris@17
|
311 $commentText = substr($commentText, 1);
|
Chris@17
|
312 $commentTextLower = strtolower($commentText);
|
Chris@17
|
313 }
|
Chris@17
|
314
|
Chris@17
|
315 // If there is a comment on the end, strip it off.
|
Chris@17
|
316 $commentStart = strpos($commentTextLower, ' --');
|
Chris@17
|
317 if ($commentStart !== false) {
|
Chris@17
|
318 $commentText = substr($commentText, 0, $commentStart);
|
Chris@17
|
319 $commentTextLower = strtolower($commentText);
|
Chris@17
|
320 }
|
Chris@17
|
321
|
Chris@17
|
322 // If this comment is the only thing on the line, it tells us
|
Chris@17
|
323 // to ignore the following line. If the line contains other content
|
Chris@17
|
324 // then we are just ignoring this one single line.
|
Chris@17
|
325 $lineHasOtherContent = false;
|
Chris@17
|
326 $lineHasOtherTokens = false;
|
Chris@17
|
327 if ($i > 0) {
|
Chris@17
|
328 for ($prev = ($i - 1); $prev > 0; $prev--) {
|
Chris@17
|
329 if ($this->tokens[$prev]['line'] !== $this->tokens[$i]['line']) {
|
Chris@17
|
330 // Changed lines.
|
Chris@17
|
331 break;
|
Chris@17
|
332 }
|
Chris@17
|
333
|
Chris@17
|
334 if ($this->tokens[$prev]['code'] === T_WHITESPACE
|
Chris@17
|
335 || ($this->tokens[$prev]['code'] === T_INLINE_HTML
|
Chris@17
|
336 && trim($this->tokens[$prev]['content']) === '')
|
Chris@17
|
337 ) {
|
Chris@17
|
338 continue;
|
Chris@17
|
339 }
|
Chris@17
|
340
|
Chris@17
|
341 $lineHasOtherTokens = true;
|
Chris@17
|
342
|
Chris@17
|
343 if ($this->tokens[$prev]['code'] === T_OPEN_TAG) {
|
Chris@17
|
344 continue;
|
Chris@17
|
345 }
|
Chris@17
|
346
|
Chris@17
|
347 $lineHasOtherContent = true;
|
Chris@17
|
348 break;
|
Chris@17
|
349 }//end for
|
Chris@17
|
350
|
Chris@17
|
351 $changedLines = false;
|
Chris@17
|
352 for ($next = $i; $next < $this->numTokens; $next++) {
|
Chris@17
|
353 if ($changedLines === true) {
|
Chris@17
|
354 // Changed lines.
|
Chris@17
|
355 break;
|
Chris@17
|
356 }
|
Chris@17
|
357
|
Chris@17
|
358 if (isset($this->knownLengths[$this->tokens[$next]['code']]) === false
|
Chris@17
|
359 && strpos($this->tokens[$next]['content'], $this->eolChar) !== false
|
Chris@17
|
360 ) {
|
Chris@17
|
361 // Last token on the current line.
|
Chris@17
|
362 $changedLines = true;
|
Chris@17
|
363 }
|
Chris@17
|
364
|
Chris@17
|
365 if ($next === $i) {
|
Chris@17
|
366 continue;
|
Chris@17
|
367 }
|
Chris@17
|
368
|
Chris@17
|
369 if ($this->tokens[$next]['code'] === T_WHITESPACE
|
Chris@17
|
370 || ($this->tokens[$next]['code'] === T_INLINE_HTML
|
Chris@17
|
371 && trim($this->tokens[$next]['content']) === '')
|
Chris@17
|
372 ) {
|
Chris@17
|
373 continue;
|
Chris@17
|
374 }
|
Chris@17
|
375
|
Chris@17
|
376 $lineHasOtherTokens = true;
|
Chris@17
|
377
|
Chris@17
|
378 if ($this->tokens[$next]['code'] === T_CLOSE_TAG) {
|
Chris@17
|
379 continue;
|
Chris@17
|
380 }
|
Chris@17
|
381
|
Chris@17
|
382 $lineHasOtherContent = true;
|
Chris@17
|
383 break;
|
Chris@17
|
384 }//end for
|
Chris@17
|
385 }//end if
|
Chris@17
|
386
|
Chris@17
|
387 if (substr($commentTextLower, 0, 9) === 'phpcs:set') {
|
Chris@17
|
388 // Ignore standards for complete lines that change sniff settings.
|
Chris@17
|
389 if ($lineHasOtherTokens === false) {
|
Chris@17
|
390 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
|
Chris@17
|
391 }
|
Chris@17
|
392
|
Chris@18
|
393 // Need to maintain case here, to get the correct sniff code.
|
Chris@18
|
394 $parts = explode(' ', substr($commentText, 10));
|
Chris@18
|
395 if (count($parts) >= 2) {
|
Chris@18
|
396 $sniffParts = explode('.', $parts[0]);
|
Chris@18
|
397 if (count($sniffParts) >= 3) {
|
Chris@18
|
398 $this->tokens[$i]['sniffCode'] = array_shift($parts);
|
Chris@18
|
399 $this->tokens[$i]['sniffProperty'] = array_shift($parts);
|
Chris@18
|
400 $this->tokens[$i]['sniffPropertyValue'] = rtrim(implode(' ', $parts), " */\r\n");
|
Chris@18
|
401 }
|
Chris@18
|
402 }
|
Chris@18
|
403
|
Chris@17
|
404 $this->tokens[$i]['code'] = T_PHPCS_SET;
|
Chris@17
|
405 $this->tokens[$i]['type'] = 'T_PHPCS_SET';
|
Chris@17
|
406 } else if (substr($commentTextLower, 0, 16) === 'phpcs:ignorefile') {
|
Chris@17
|
407 // The whole file will be ignored, but at least set the correct token.
|
Chris@17
|
408 $this->tokens[$i]['code'] = T_PHPCS_IGNORE_FILE;
|
Chris@17
|
409 $this->tokens[$i]['type'] = 'T_PHPCS_IGNORE_FILE';
|
Chris@17
|
410 } else if (substr($commentTextLower, 0, 13) === 'phpcs:disable') {
|
Chris@17
|
411 if ($lineHasOtherContent === false) {
|
Chris@17
|
412 // Completely ignore the comment line.
|
Chris@17
|
413 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
|
Chris@17
|
414 }
|
Chris@17
|
415
|
Chris@17
|
416 if ($ignoring === null) {
|
Chris@17
|
417 $ignoring = [];
|
Chris@17
|
418 }
|
Chris@17
|
419
|
Chris@17
|
420 $disabledSniffs = [];
|
Chris@17
|
421
|
Chris@17
|
422 $additionalText = substr($commentText, 14);
|
Chris@17
|
423 if ($additionalText === false) {
|
Chris@17
|
424 $ignoring = ['.all' => true];
|
Chris@17
|
425 } else {
|
Chris@17
|
426 $parts = explode(',', substr($commentText, 13));
|
Chris@17
|
427 foreach ($parts as $sniffCode) {
|
Chris@17
|
428 $sniffCode = trim($sniffCode);
|
Chris@17
|
429 $disabledSniffs[$sniffCode] = true;
|
Chris@17
|
430 $ignoring[$sniffCode] = true;
|
Chris@17
|
431
|
Chris@17
|
432 // This newly disabled sniff might be disabling an existing
|
Chris@17
|
433 // enabled exception that we are tracking.
|
Chris@17
|
434 if (isset($ignoring['.except']) === true) {
|
Chris@17
|
435 foreach (array_keys($ignoring['.except']) as $ignoredSniffCode) {
|
Chris@17
|
436 if ($ignoredSniffCode === $sniffCode
|
Chris@17
|
437 || strpos($ignoredSniffCode, $sniffCode.'.') === 0
|
Chris@17
|
438 ) {
|
Chris@17
|
439 unset($ignoring['.except'][$ignoredSniffCode]);
|
Chris@17
|
440 }
|
Chris@17
|
441 }
|
Chris@17
|
442
|
Chris@17
|
443 if (empty($ignoring['.except']) === true) {
|
Chris@17
|
444 unset($ignoring['.except']);
|
Chris@17
|
445 }
|
Chris@17
|
446 }
|
Chris@17
|
447 }//end foreach
|
Chris@17
|
448 }//end if
|
Chris@17
|
449
|
Chris@17
|
450 $this->tokens[$i]['code'] = T_PHPCS_DISABLE;
|
Chris@17
|
451 $this->tokens[$i]['type'] = 'T_PHPCS_DISABLE';
|
Chris@17
|
452 $this->tokens[$i]['sniffCodes'] = $disabledSniffs;
|
Chris@17
|
453 } else if (substr($commentTextLower, 0, 12) === 'phpcs:enable') {
|
Chris@17
|
454 if ($ignoring !== null) {
|
Chris@17
|
455 $enabledSniffs = [];
|
Chris@17
|
456
|
Chris@17
|
457 $additionalText = substr($commentText, 13);
|
Chris@17
|
458 if ($additionalText === false) {
|
Chris@17
|
459 $ignoring = null;
|
Chris@17
|
460 } else {
|
Chris@17
|
461 $parts = explode(',', substr($commentText, 13));
|
Chris@17
|
462 foreach ($parts as $sniffCode) {
|
Chris@17
|
463 $sniffCode = trim($sniffCode);
|
Chris@17
|
464 $enabledSniffs[$sniffCode] = true;
|
Chris@17
|
465
|
Chris@17
|
466 // This new enabled sniff might remove previously disabled
|
Chris@17
|
467 // sniffs if it is actually a standard or category of sniffs.
|
Chris@17
|
468 foreach (array_keys($ignoring) as $ignoredSniffCode) {
|
Chris@17
|
469 if ($ignoredSniffCode === $sniffCode
|
Chris@17
|
470 || strpos($ignoredSniffCode, $sniffCode.'.') === 0
|
Chris@17
|
471 ) {
|
Chris@17
|
472 unset($ignoring[$ignoredSniffCode]);
|
Chris@17
|
473 }
|
Chris@17
|
474 }
|
Chris@17
|
475
|
Chris@17
|
476 // This new enabled sniff might be able to clear up
|
Chris@17
|
477 // previously enabled sniffs if it is actually a standard or
|
Chris@17
|
478 // category of sniffs.
|
Chris@17
|
479 if (isset($ignoring['.except']) === true) {
|
Chris@17
|
480 foreach (array_keys($ignoring['.except']) as $ignoredSniffCode) {
|
Chris@17
|
481 if ($ignoredSniffCode === $sniffCode
|
Chris@17
|
482 || strpos($ignoredSniffCode, $sniffCode.'.') === 0
|
Chris@17
|
483 ) {
|
Chris@17
|
484 unset($ignoring['.except'][$ignoredSniffCode]);
|
Chris@17
|
485 }
|
Chris@17
|
486 }
|
Chris@17
|
487 }
|
Chris@17
|
488 }//end foreach
|
Chris@17
|
489
|
Chris@17
|
490 if (empty($ignoring) === true) {
|
Chris@17
|
491 $ignoring = null;
|
Chris@17
|
492 } else {
|
Chris@17
|
493 if (isset($ignoring['.except']) === true) {
|
Chris@17
|
494 $ignoring['.except'] += $enabledSniffs;
|
Chris@17
|
495 } else {
|
Chris@17
|
496 $ignoring['.except'] = $enabledSniffs;
|
Chris@17
|
497 }
|
Chris@17
|
498 }
|
Chris@17
|
499 }//end if
|
Chris@17
|
500
|
Chris@17
|
501 if ($lineHasOtherContent === false) {
|
Chris@17
|
502 // Completely ignore the comment line.
|
Chris@17
|
503 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
|
Chris@17
|
504 } else {
|
Chris@17
|
505 // The comment is on the same line as the code it is ignoring,
|
Chris@17
|
506 // so respect the new ignore rules.
|
Chris@17
|
507 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
|
Chris@17
|
508 }
|
Chris@17
|
509
|
Chris@17
|
510 $this->tokens[$i]['sniffCodes'] = $enabledSniffs;
|
Chris@17
|
511 }//end if
|
Chris@17
|
512
|
Chris@17
|
513 $this->tokens[$i]['code'] = T_PHPCS_ENABLE;
|
Chris@17
|
514 $this->tokens[$i]['type'] = 'T_PHPCS_ENABLE';
|
Chris@17
|
515 } else if (substr($commentTextLower, 0, 12) === 'phpcs:ignore') {
|
Chris@17
|
516 $ignoreRules = [];
|
Chris@17
|
517
|
Chris@17
|
518 $additionalText = substr($commentText, 13);
|
Chris@17
|
519 if ($additionalText === false) {
|
Chris@17
|
520 $ignoreRules = ['.all' => true];
|
Chris@17
|
521 } else {
|
Chris@17
|
522 $parts = explode(',', substr($commentText, 13));
|
Chris@17
|
523 foreach ($parts as $sniffCode) {
|
Chris@17
|
524 $ignoreRules[trim($sniffCode)] = true;
|
Chris@17
|
525 }
|
Chris@17
|
526 }
|
Chris@17
|
527
|
Chris@17
|
528 $this->tokens[$i]['code'] = T_PHPCS_IGNORE;
|
Chris@17
|
529 $this->tokens[$i]['type'] = 'T_PHPCS_IGNORE';
|
Chris@17
|
530 $this->tokens[$i]['sniffCodes'] = $ignoreRules;
|
Chris@17
|
531
|
Chris@17
|
532 if ($ignoring !== null) {
|
Chris@17
|
533 $ignoreRules += $ignoring;
|
Chris@17
|
534 }
|
Chris@17
|
535
|
Chris@17
|
536 if ($lineHasOtherContent === false) {
|
Chris@18
|
537 // Completely ignore the comment line, and set the following
|
Chris@17
|
538 // line to include the ignore rules we've set.
|
Chris@17
|
539 $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true];
|
Chris@17
|
540 $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = $ignoreRules;
|
Chris@17
|
541 } else {
|
Chris@17
|
542 // The comment is on the same line as the code it is ignoring,
|
Chris@17
|
543 // so respect the ignore rules it set.
|
Chris@17
|
544 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreRules;
|
Chris@17
|
545 }
|
Chris@17
|
546 }//end if
|
Chris@17
|
547 }//end if
|
Chris@17
|
548 }//end if
|
Chris@17
|
549
|
Chris@17
|
550 if ($ignoring !== null && isset($this->ignoredLines[$this->tokens[$i]['line']]) === false) {
|
Chris@17
|
551 $this->ignoredLines[$this->tokens[$i]['line']] = $ignoring;
|
Chris@17
|
552 }
|
Chris@17
|
553 }//end for
|
Chris@17
|
554
|
Chris@17
|
555 // If annotations are being ignored, we clear out all the ignore rules
|
Chris@17
|
556 // but leave the annotations tokenized as normal.
|
Chris@17
|
557 if ($checkAnnotations === false) {
|
Chris@17
|
558 $this->ignoredLines = [];
|
Chris@17
|
559 }
|
Chris@17
|
560
|
Chris@17
|
561 }//end createPositionMap()
|
Chris@17
|
562
|
Chris@17
|
563
|
Chris@17
|
564 /**
|
Chris@17
|
565 * Replaces tabs in original token content with spaces.
|
Chris@17
|
566 *
|
Chris@17
|
567 * Each tab can represent between 1 and $config->tabWidth spaces,
|
Chris@17
|
568 * so this cannot be a straight string replace. The original content
|
Chris@17
|
569 * is placed into an orig_content index and the new token length is also
|
Chris@17
|
570 * set in the length index.
|
Chris@17
|
571 *
|
Chris@17
|
572 * @param array $token The token to replace tabs inside.
|
Chris@17
|
573 * @param string $prefix The character to use to represent the start of a tab.
|
Chris@17
|
574 * @param string $padding The character to use to represent the end of a tab.
|
Chris@17
|
575 * @param int $tabWidth The number of spaces each tab represents.
|
Chris@17
|
576 *
|
Chris@17
|
577 * @return void
|
Chris@17
|
578 */
|
Chris@17
|
579 public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth=null)
|
Chris@17
|
580 {
|
Chris@17
|
581 $checkEncoding = false;
|
Chris@17
|
582 if (function_exists('iconv_strlen') === true) {
|
Chris@17
|
583 $checkEncoding = true;
|
Chris@17
|
584 }
|
Chris@17
|
585
|
Chris@17
|
586 $currColumn = $token['column'];
|
Chris@17
|
587 if ($tabWidth === null) {
|
Chris@17
|
588 $tabWidth = $this->config->tabWidth;
|
Chris@17
|
589 if ($tabWidth === 0) {
|
Chris@17
|
590 $tabWidth = 1;
|
Chris@17
|
591 }
|
Chris@17
|
592 }
|
Chris@17
|
593
|
Chris@17
|
594 if (rtrim($token['content'], "\t") === '') {
|
Chris@17
|
595 // String only contains tabs, so we can shortcut the process.
|
Chris@17
|
596 $numTabs = strlen($token['content']);
|
Chris@17
|
597
|
Chris@17
|
598 $firstTabSize = ($tabWidth - (($currColumn - 1) % $tabWidth));
|
Chris@17
|
599 $length = ($firstTabSize + ($tabWidth * ($numTabs - 1)));
|
Chris@17
|
600 $newContent = $prefix.str_repeat($padding, ($length - 1));
|
Chris@17
|
601 } else {
|
Chris@17
|
602 // We need to determine the length of each tab.
|
Chris@17
|
603 $tabs = explode("\t", $token['content']);
|
Chris@17
|
604
|
Chris@17
|
605 $numTabs = (count($tabs) - 1);
|
Chris@17
|
606 $tabNum = 0;
|
Chris@17
|
607 $newContent = '';
|
Chris@17
|
608 $length = 0;
|
Chris@17
|
609
|
Chris@17
|
610 foreach ($tabs as $content) {
|
Chris@17
|
611 if ($content !== '') {
|
Chris@17
|
612 $newContent .= $content;
|
Chris@17
|
613 if ($checkEncoding === true) {
|
Chris@17
|
614 // Not using the default encoding, so take a bit more care.
|
Chris@17
|
615 $oldLevel = error_reporting();
|
Chris@17
|
616 error_reporting(0);
|
Chris@17
|
617 $contentLength = iconv_strlen($content, $this->config->encoding);
|
Chris@17
|
618 error_reporting($oldLevel);
|
Chris@17
|
619 if ($contentLength === false) {
|
Chris@17
|
620 // String contained invalid characters, so revert to default.
|
Chris@17
|
621 $contentLength = strlen($content);
|
Chris@17
|
622 }
|
Chris@17
|
623 } else {
|
Chris@17
|
624 $contentLength = strlen($content);
|
Chris@17
|
625 }
|
Chris@17
|
626
|
Chris@17
|
627 $currColumn += $contentLength;
|
Chris@17
|
628 $length += $contentLength;
|
Chris@17
|
629 }
|
Chris@17
|
630
|
Chris@17
|
631 // The last piece of content does not have a tab after it.
|
Chris@17
|
632 if ($tabNum === $numTabs) {
|
Chris@17
|
633 break;
|
Chris@17
|
634 }
|
Chris@17
|
635
|
Chris@17
|
636 // Process the tab that comes after the content.
|
Chris@17
|
637 $lastCurrColumn = $currColumn;
|
Chris@17
|
638 $tabNum++;
|
Chris@17
|
639
|
Chris@17
|
640 // Move the pointer to the next tab stop.
|
Chris@17
|
641 if (($currColumn % $tabWidth) === 0) {
|
Chris@17
|
642 // This is the first tab, and we are already at a
|
Chris@17
|
643 // tab stop, so this tab counts as a single space.
|
Chris@17
|
644 $currColumn++;
|
Chris@17
|
645 } else {
|
Chris@17
|
646 $currColumn++;
|
Chris@17
|
647 while (($currColumn % $tabWidth) !== 0) {
|
Chris@17
|
648 $currColumn++;
|
Chris@17
|
649 }
|
Chris@17
|
650
|
Chris@17
|
651 $currColumn++;
|
Chris@17
|
652 }
|
Chris@17
|
653
|
Chris@17
|
654 $length += ($currColumn - $lastCurrColumn);
|
Chris@17
|
655 $newContent .= $prefix.str_repeat($padding, ($currColumn - $lastCurrColumn - 1));
|
Chris@17
|
656 }//end foreach
|
Chris@17
|
657 }//end if
|
Chris@17
|
658
|
Chris@17
|
659 $token['orig_content'] = $token['content'];
|
Chris@17
|
660 $token['content'] = $newContent;
|
Chris@17
|
661 $token['length'] = $length;
|
Chris@17
|
662
|
Chris@17
|
663 }//end replaceTabsInToken()
|
Chris@17
|
664
|
Chris@17
|
665
|
Chris@17
|
666 /**
|
Chris@17
|
667 * Creates a map of brackets positions.
|
Chris@17
|
668 *
|
Chris@17
|
669 * @return void
|
Chris@17
|
670 */
|
Chris@17
|
671 private function createTokenMap()
|
Chris@17
|
672 {
|
Chris@17
|
673 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
674 echo "\t*** START TOKEN MAP ***".PHP_EOL;
|
Chris@17
|
675 }
|
Chris@17
|
676
|
Chris@17
|
677 $squareOpeners = [];
|
Chris@17
|
678 $curlyOpeners = [];
|
Chris@17
|
679 $this->numTokens = count($this->tokens);
|
Chris@17
|
680
|
Chris@17
|
681 $openers = [];
|
Chris@17
|
682 $openOwner = null;
|
Chris@17
|
683
|
Chris@17
|
684 for ($i = 0; $i < $this->numTokens; $i++) {
|
Chris@17
|
685 /*
|
Chris@17
|
686 Parenthesis mapping.
|
Chris@17
|
687 */
|
Chris@17
|
688
|
Chris@17
|
689 if (isset(Util\Tokens::$parenthesisOpeners[$this->tokens[$i]['code']]) === true) {
|
Chris@17
|
690 $this->tokens[$i]['parenthesis_opener'] = null;
|
Chris@17
|
691 $this->tokens[$i]['parenthesis_closer'] = null;
|
Chris@17
|
692 $this->tokens[$i]['parenthesis_owner'] = $i;
|
Chris@17
|
693 $openOwner = $i;
|
Chris@17
|
694 } else if ($this->tokens[$i]['code'] === T_OPEN_PARENTHESIS) {
|
Chris@17
|
695 $openers[] = $i;
|
Chris@17
|
696 $this->tokens[$i]['parenthesis_opener'] = $i;
|
Chris@17
|
697 if ($openOwner !== null) {
|
Chris@17
|
698 $this->tokens[$openOwner]['parenthesis_opener'] = $i;
|
Chris@17
|
699 $this->tokens[$i]['parenthesis_owner'] = $openOwner;
|
Chris@17
|
700 $openOwner = null;
|
Chris@17
|
701 }
|
Chris@17
|
702 } else if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS) {
|
Chris@17
|
703 // Did we set an owner for this set of parenthesis?
|
Chris@17
|
704 $numOpeners = count($openers);
|
Chris@17
|
705 if ($numOpeners !== 0) {
|
Chris@17
|
706 $opener = array_pop($openers);
|
Chris@17
|
707 if (isset($this->tokens[$opener]['parenthesis_owner']) === true) {
|
Chris@17
|
708 $owner = $this->tokens[$opener]['parenthesis_owner'];
|
Chris@17
|
709
|
Chris@17
|
710 $this->tokens[$owner]['parenthesis_closer'] = $i;
|
Chris@17
|
711 $this->tokens[$i]['parenthesis_owner'] = $owner;
|
Chris@17
|
712 }
|
Chris@17
|
713
|
Chris@17
|
714 $this->tokens[$i]['parenthesis_opener'] = $opener;
|
Chris@17
|
715 $this->tokens[$i]['parenthesis_closer'] = $i;
|
Chris@17
|
716 $this->tokens[$opener]['parenthesis_closer'] = $i;
|
Chris@17
|
717 }
|
Chris@17
|
718 }//end if
|
Chris@17
|
719
|
Chris@17
|
720 /*
|
Chris@17
|
721 Bracket mapping.
|
Chris@17
|
722 */
|
Chris@17
|
723
|
Chris@17
|
724 switch ($this->tokens[$i]['code']) {
|
Chris@17
|
725 case T_OPEN_SQUARE_BRACKET:
|
Chris@17
|
726 $squareOpeners[] = $i;
|
Chris@17
|
727
|
Chris@17
|
728 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
729 echo str_repeat("\t", count($squareOpeners));
|
Chris@17
|
730 echo str_repeat("\t", count($curlyOpeners));
|
Chris@17
|
731 echo "=> Found square bracket opener at $i".PHP_EOL;
|
Chris@17
|
732 }
|
Chris@17
|
733 break;
|
Chris@17
|
734 case T_OPEN_CURLY_BRACKET:
|
Chris@17
|
735 if (isset($this->tokens[$i]['scope_closer']) === false) {
|
Chris@17
|
736 $curlyOpeners[] = $i;
|
Chris@17
|
737
|
Chris@17
|
738 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
739 echo str_repeat("\t", count($squareOpeners));
|
Chris@17
|
740 echo str_repeat("\t", count($curlyOpeners));
|
Chris@17
|
741 echo "=> Found curly bracket opener at $i".PHP_EOL;
|
Chris@17
|
742 }
|
Chris@17
|
743 }
|
Chris@17
|
744 break;
|
Chris@17
|
745 case T_CLOSE_SQUARE_BRACKET:
|
Chris@17
|
746 if (empty($squareOpeners) === false) {
|
Chris@17
|
747 $opener = array_pop($squareOpeners);
|
Chris@17
|
748 $this->tokens[$i]['bracket_opener'] = $opener;
|
Chris@17
|
749 $this->tokens[$i]['bracket_closer'] = $i;
|
Chris@17
|
750 $this->tokens[$opener]['bracket_opener'] = $opener;
|
Chris@17
|
751 $this->tokens[$opener]['bracket_closer'] = $i;
|
Chris@17
|
752
|
Chris@17
|
753 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
754 echo str_repeat("\t", count($squareOpeners));
|
Chris@17
|
755 echo str_repeat("\t", count($curlyOpeners));
|
Chris@17
|
756 echo "\t=> Found square bracket closer at $i for $opener".PHP_EOL;
|
Chris@17
|
757 }
|
Chris@17
|
758 }
|
Chris@17
|
759 break;
|
Chris@17
|
760 case T_CLOSE_CURLY_BRACKET:
|
Chris@17
|
761 if (empty($curlyOpeners) === false
|
Chris@17
|
762 && isset($this->tokens[$i]['scope_opener']) === false
|
Chris@17
|
763 ) {
|
Chris@17
|
764 $opener = array_pop($curlyOpeners);
|
Chris@17
|
765 $this->tokens[$i]['bracket_opener'] = $opener;
|
Chris@17
|
766 $this->tokens[$i]['bracket_closer'] = $i;
|
Chris@17
|
767 $this->tokens[$opener]['bracket_opener'] = $opener;
|
Chris@17
|
768 $this->tokens[$opener]['bracket_closer'] = $i;
|
Chris@17
|
769
|
Chris@17
|
770 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
771 echo str_repeat("\t", count($squareOpeners));
|
Chris@17
|
772 echo str_repeat("\t", count($curlyOpeners));
|
Chris@17
|
773 echo "\t=> Found curly bracket closer at $i for $opener".PHP_EOL;
|
Chris@17
|
774 }
|
Chris@17
|
775 }
|
Chris@17
|
776 break;
|
Chris@17
|
777 default:
|
Chris@17
|
778 continue 2;
|
Chris@17
|
779 }//end switch
|
Chris@17
|
780 }//end for
|
Chris@17
|
781
|
Chris@17
|
782 // Cleanup for any openers that we didn't find closers for.
|
Chris@17
|
783 // This typically means there was a syntax error breaking things.
|
Chris@17
|
784 foreach ($openers as $opener) {
|
Chris@17
|
785 unset($this->tokens[$opener]['parenthesis_opener']);
|
Chris@17
|
786 unset($this->tokens[$opener]['parenthesis_owner']);
|
Chris@17
|
787 }
|
Chris@17
|
788
|
Chris@17
|
789 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
790 echo "\t*** END TOKEN MAP ***".PHP_EOL;
|
Chris@17
|
791 }
|
Chris@17
|
792
|
Chris@17
|
793 }//end createTokenMap()
|
Chris@17
|
794
|
Chris@17
|
795
|
Chris@17
|
796 /**
|
Chris@17
|
797 * Creates a map for the parenthesis tokens that surround other tokens.
|
Chris@17
|
798 *
|
Chris@17
|
799 * @return void
|
Chris@17
|
800 */
|
Chris@17
|
801 private function createParenthesisNestingMap()
|
Chris@17
|
802 {
|
Chris@17
|
803 $map = [];
|
Chris@17
|
804 for ($i = 0; $i < $this->numTokens; $i++) {
|
Chris@17
|
805 if (isset($this->tokens[$i]['parenthesis_opener']) === true
|
Chris@17
|
806 && $i === $this->tokens[$i]['parenthesis_opener']
|
Chris@17
|
807 ) {
|
Chris@17
|
808 if (empty($map) === false) {
|
Chris@17
|
809 $this->tokens[$i]['nested_parenthesis'] = $map;
|
Chris@17
|
810 }
|
Chris@17
|
811
|
Chris@17
|
812 if (isset($this->tokens[$i]['parenthesis_closer']) === true) {
|
Chris@17
|
813 $map[$this->tokens[$i]['parenthesis_opener']]
|
Chris@17
|
814 = $this->tokens[$i]['parenthesis_closer'];
|
Chris@17
|
815 }
|
Chris@17
|
816 } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
|
Chris@17
|
817 && $i === $this->tokens[$i]['parenthesis_closer']
|
Chris@17
|
818 ) {
|
Chris@17
|
819 array_pop($map);
|
Chris@17
|
820 if (empty($map) === false) {
|
Chris@17
|
821 $this->tokens[$i]['nested_parenthesis'] = $map;
|
Chris@17
|
822 }
|
Chris@17
|
823 } else {
|
Chris@17
|
824 if (empty($map) === false) {
|
Chris@17
|
825 $this->tokens[$i]['nested_parenthesis'] = $map;
|
Chris@17
|
826 }
|
Chris@17
|
827 }//end if
|
Chris@17
|
828 }//end for
|
Chris@17
|
829
|
Chris@17
|
830 }//end createParenthesisNestingMap()
|
Chris@17
|
831
|
Chris@17
|
832
|
Chris@17
|
833 /**
|
Chris@17
|
834 * Creates a scope map of tokens that open scopes.
|
Chris@17
|
835 *
|
Chris@17
|
836 * @return void
|
Chris@17
|
837 * @see recurseScopeMap()
|
Chris@17
|
838 */
|
Chris@17
|
839 private function createScopeMap()
|
Chris@17
|
840 {
|
Chris@17
|
841 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
842 echo "\t*** START SCOPE MAP ***".PHP_EOL;
|
Chris@17
|
843 }
|
Chris@17
|
844
|
Chris@17
|
845 for ($i = 0; $i < $this->numTokens; $i++) {
|
Chris@17
|
846 // Check to see if the current token starts a new scope.
|
Chris@17
|
847 if (isset($this->scopeOpeners[$this->tokens[$i]['code']]) === true) {
|
Chris@17
|
848 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
849 $type = $this->tokens[$i]['type'];
|
Chris@17
|
850 $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
|
Chris@17
|
851 echo "\tStart scope map at $i:$type => $content".PHP_EOL;
|
Chris@17
|
852 }
|
Chris@17
|
853
|
Chris@17
|
854 if (isset($this->tokens[$i]['scope_condition']) === true) {
|
Chris@17
|
855 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
856 echo "\t* already processed, skipping *".PHP_EOL;
|
Chris@17
|
857 }
|
Chris@17
|
858
|
Chris@17
|
859 continue;
|
Chris@17
|
860 }
|
Chris@17
|
861
|
Chris@17
|
862 $i = $this->recurseScopeMap($i);
|
Chris@17
|
863 }//end if
|
Chris@17
|
864 }//end for
|
Chris@17
|
865
|
Chris@17
|
866 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
867 echo "\t*** END SCOPE MAP ***".PHP_EOL;
|
Chris@17
|
868 }
|
Chris@17
|
869
|
Chris@17
|
870 }//end createScopeMap()
|
Chris@17
|
871
|
Chris@17
|
872
|
Chris@17
|
873 /**
|
Chris@17
|
874 * Recurses though the scope openers to build a scope map.
|
Chris@17
|
875 *
|
Chris@17
|
876 * @param int $stackPtr The position in the stack of the token that
|
Chris@17
|
877 * opened the scope (eg. an IF token or FOR token).
|
Chris@17
|
878 * @param int $depth How many scope levels down we are.
|
Chris@17
|
879 * @param int $ignore How many curly braces we are ignoring.
|
Chris@17
|
880 *
|
Chris@17
|
881 * @return int The position in the stack that closed the scope.
|
Chris@17
|
882 */
|
Chris@17
|
883 private function recurseScopeMap($stackPtr, $depth=1, &$ignore=0)
|
Chris@17
|
884 {
|
Chris@17
|
885 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
886 echo str_repeat("\t", $depth);
|
Chris@17
|
887 echo "=> Begin scope map recursion at token $stackPtr with depth $depth".PHP_EOL;
|
Chris@17
|
888 }
|
Chris@17
|
889
|
Chris@17
|
890 $opener = null;
|
Chris@17
|
891 $currType = $this->tokens[$stackPtr]['code'];
|
Chris@17
|
892 $startLine = $this->tokens[$stackPtr]['line'];
|
Chris@17
|
893
|
Chris@17
|
894 // We will need this to restore the value if we end up
|
Chris@17
|
895 // returning a token ID that causes our calling function to go back
|
Chris@17
|
896 // over already ignored braces.
|
Chris@17
|
897 $originalIgnore = $ignore;
|
Chris@17
|
898
|
Chris@17
|
899 // If the start token for this scope opener is the same as
|
Chris@17
|
900 // the scope token, we have already found our opener.
|
Chris@17
|
901 if (isset($this->scopeOpeners[$currType]['start'][$currType]) === true) {
|
Chris@17
|
902 $opener = $stackPtr;
|
Chris@17
|
903 }
|
Chris@17
|
904
|
Chris@17
|
905 for ($i = ($stackPtr + 1); $i < $this->numTokens; $i++) {
|
Chris@17
|
906 $tokenType = $this->tokens[$i]['code'];
|
Chris@17
|
907
|
Chris@17
|
908 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
909 $type = $this->tokens[$i]['type'];
|
Chris@17
|
910 $line = $this->tokens[$i]['line'];
|
Chris@17
|
911 $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
|
Chris@17
|
912
|
Chris@17
|
913 echo str_repeat("\t", $depth);
|
Chris@17
|
914 echo "Process token $i on line $line [";
|
Chris@17
|
915 if ($opener !== null) {
|
Chris@17
|
916 echo "opener:$opener;";
|
Chris@17
|
917 }
|
Chris@17
|
918
|
Chris@17
|
919 if ($ignore > 0) {
|
Chris@17
|
920 echo "ignore=$ignore;";
|
Chris@17
|
921 }
|
Chris@17
|
922
|
Chris@17
|
923 echo "]: $type => $content".PHP_EOL;
|
Chris@17
|
924 }//end if
|
Chris@17
|
925
|
Chris@17
|
926 // Very special case for IF statements in PHP that can be defined without
|
Chris@17
|
927 // scope tokens. E.g., if (1) 1; 1 ? (1 ? 1 : 1) : 1;
|
Chris@17
|
928 // If an IF statement below this one has an opener but no
|
Chris@17
|
929 // keyword, the opener will be incorrectly assigned to this IF statement.
|
Chris@17
|
930 // The same case also applies to USE statements, which don't have to have
|
Chris@17
|
931 // openers, so a following USE statement can cause an incorrect brace match.
|
Chris@17
|
932 if (($currType === T_IF || $currType === T_ELSE || $currType === T_USE)
|
Chris@17
|
933 && $opener === null
|
Chris@17
|
934 && ($this->tokens[$i]['code'] === T_SEMICOLON
|
Chris@17
|
935 || $this->tokens[$i]['code'] === T_CLOSE_TAG)
|
Chris@17
|
936 ) {
|
Chris@17
|
937 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
938 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
939 echo str_repeat("\t", $depth);
|
Chris@17
|
940 if ($this->tokens[$i]['code'] === T_SEMICOLON) {
|
Chris@17
|
941 $closerType = 'semicolon';
|
Chris@17
|
942 } else {
|
Chris@17
|
943 $closerType = 'close tag';
|
Chris@17
|
944 }
|
Chris@17
|
945
|
Chris@17
|
946 echo "=> Found $closerType before scope opener for $stackPtr:$type, bailing".PHP_EOL;
|
Chris@17
|
947 }
|
Chris@17
|
948
|
Chris@17
|
949 return $i;
|
Chris@17
|
950 }
|
Chris@17
|
951
|
Chris@17
|
952 // Special case for PHP control structures that have no braces.
|
Chris@17
|
953 // If we find a curly brace closer before we find the opener,
|
Chris@17
|
954 // we're not going to find an opener. That closer probably belongs to
|
Chris@17
|
955 // a control structure higher up.
|
Chris@17
|
956 if ($opener === null
|
Chris@17
|
957 && $ignore === 0
|
Chris@17
|
958 && $tokenType === T_CLOSE_CURLY_BRACKET
|
Chris@17
|
959 && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
|
Chris@17
|
960 ) {
|
Chris@17
|
961 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
962 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
963 echo str_repeat("\t", $depth);
|
Chris@17
|
964 echo "=> Found curly brace closer before scope opener for $stackPtr:$type, bailing".PHP_EOL;
|
Chris@17
|
965 }
|
Chris@17
|
966
|
Chris@17
|
967 return ($i - 1);
|
Chris@17
|
968 }
|
Chris@17
|
969
|
Chris@17
|
970 if ($opener !== null
|
Chris@17
|
971 && (isset($this->tokens[$i]['scope_opener']) === false
|
Chris@17
|
972 || $this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true)
|
Chris@17
|
973 && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true
|
Chris@17
|
974 ) {
|
Chris@17
|
975 if ($ignore > 0 && $tokenType === T_CLOSE_CURLY_BRACKET) {
|
Chris@17
|
976 // The last opening bracket must have been for a string
|
Chris@17
|
977 // offset or alike, so let's ignore it.
|
Chris@17
|
978 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
979 echo str_repeat("\t", $depth);
|
Chris@17
|
980 echo '* finished ignoring curly brace *'.PHP_EOL;
|
Chris@17
|
981 }
|
Chris@17
|
982
|
Chris@17
|
983 $ignore--;
|
Chris@17
|
984 continue;
|
Chris@17
|
985 } else if ($this->tokens[$opener]['code'] === T_OPEN_CURLY_BRACKET
|
Chris@17
|
986 && $tokenType !== T_CLOSE_CURLY_BRACKET
|
Chris@17
|
987 ) {
|
Chris@17
|
988 // The opener is a curly bracket so the closer must be a curly bracket as well.
|
Chris@17
|
989 // We ignore this closer to handle cases such as T_ELSE or T_ELSEIF being considered
|
Chris@17
|
990 // a closer of T_IF when it should not.
|
Chris@17
|
991 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
992 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
993 echo str_repeat("\t", $depth);
|
Chris@17
|
994 echo "=> Ignoring non-curly scope closer for $stackPtr:$type".PHP_EOL;
|
Chris@17
|
995 }
|
Chris@17
|
996 } else {
|
Chris@17
|
997 $scopeCloser = $i;
|
Chris@17
|
998 $todo = [
|
Chris@17
|
999 $stackPtr,
|
Chris@17
|
1000 $opener,
|
Chris@17
|
1001 ];
|
Chris@17
|
1002
|
Chris@17
|
1003 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1004 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1005 $closerType = $this->tokens[$scopeCloser]['type'];
|
Chris@17
|
1006 echo str_repeat("\t", $depth);
|
Chris@17
|
1007 echo "=> Found scope closer ($scopeCloser:$closerType) for $stackPtr:$type".PHP_EOL;
|
Chris@17
|
1008 }
|
Chris@17
|
1009
|
Chris@17
|
1010 $validCloser = true;
|
Chris@17
|
1011 if (($this->tokens[$stackPtr]['code'] === T_IF || $this->tokens[$stackPtr]['code'] === T_ELSEIF)
|
Chris@17
|
1012 && ($tokenType === T_ELSE || $tokenType === T_ELSEIF)
|
Chris@17
|
1013 ) {
|
Chris@17
|
1014 // To be a closer, this token must have an opener.
|
Chris@17
|
1015 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1016 echo str_repeat("\t", $depth);
|
Chris@17
|
1017 echo "* closer needs to be tested *".PHP_EOL;
|
Chris@17
|
1018 }
|
Chris@17
|
1019
|
Chris@17
|
1020 $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
|
Chris@17
|
1021
|
Chris@17
|
1022 if (isset($this->tokens[$scopeCloser]['scope_opener']) === false) {
|
Chris@17
|
1023 $validCloser = false;
|
Chris@17
|
1024 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1025 echo str_repeat("\t", $depth);
|
Chris@17
|
1026 echo "* closer is not valid (no opener found) *".PHP_EOL;
|
Chris@17
|
1027 }
|
Chris@17
|
1028 } else if ($this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['code'] !== $this->tokens[$opener]['code']) {
|
Chris@17
|
1029 $validCloser = false;
|
Chris@17
|
1030 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1031 echo str_repeat("\t", $depth);
|
Chris@17
|
1032 $type = $this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['type'];
|
Chris@17
|
1033 $openerType = $this->tokens[$opener]['type'];
|
Chris@17
|
1034 echo "* closer is not valid (mismatched opener type; $type != $openerType) *".PHP_EOL;
|
Chris@17
|
1035 }
|
Chris@17
|
1036 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1037 echo str_repeat("\t", $depth);
|
Chris@17
|
1038 echo "* closer was valid *".PHP_EOL;
|
Chris@17
|
1039 }
|
Chris@17
|
1040 } else {
|
Chris@17
|
1041 // The closer was not processed, so we need to
|
Chris@17
|
1042 // complete that token as well.
|
Chris@17
|
1043 $todo[] = $scopeCloser;
|
Chris@17
|
1044 }//end if
|
Chris@17
|
1045
|
Chris@17
|
1046 if ($validCloser === true) {
|
Chris@17
|
1047 foreach ($todo as $token) {
|
Chris@17
|
1048 $this->tokens[$token]['scope_condition'] = $stackPtr;
|
Chris@17
|
1049 $this->tokens[$token]['scope_opener'] = $opener;
|
Chris@17
|
1050 $this->tokens[$token]['scope_closer'] = $scopeCloser;
|
Chris@17
|
1051 }
|
Chris@17
|
1052
|
Chris@17
|
1053 if ($this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true) {
|
Chris@17
|
1054 // As we are going back to where we started originally, restore
|
Chris@17
|
1055 // the ignore value back to its original value.
|
Chris@17
|
1056 $ignore = $originalIgnore;
|
Chris@17
|
1057 return $opener;
|
Chris@17
|
1058 } else if ($scopeCloser === $i
|
Chris@17
|
1059 && isset($this->scopeOpeners[$tokenType]) === true
|
Chris@17
|
1060 ) {
|
Chris@17
|
1061 // Unset scope_condition here or else the token will appear to have
|
Chris@17
|
1062 // already been processed, and it will be skipped. Normally we want that,
|
Chris@17
|
1063 // but in this case, the token is both a closer and an opener, so
|
Chris@17
|
1064 // it needs to act like an opener. This is also why we return the
|
Chris@17
|
1065 // token before this one; so the closer has a chance to be processed
|
Chris@17
|
1066 // a second time, but as an opener.
|
Chris@17
|
1067 unset($this->tokens[$scopeCloser]['scope_condition']);
|
Chris@17
|
1068 return ($i - 1);
|
Chris@17
|
1069 } else {
|
Chris@17
|
1070 return $i;
|
Chris@17
|
1071 }
|
Chris@17
|
1072 } else {
|
Chris@17
|
1073 continue;
|
Chris@17
|
1074 }//end if
|
Chris@17
|
1075 }//end if
|
Chris@17
|
1076 }//end if
|
Chris@17
|
1077
|
Chris@17
|
1078 // Is this an opening condition ?
|
Chris@17
|
1079 if (isset($this->scopeOpeners[$tokenType]) === true) {
|
Chris@17
|
1080 if ($opener === null) {
|
Chris@17
|
1081 if ($tokenType === T_USE) {
|
Chris@17
|
1082 // PHP use keywords are special because they can be
|
Chris@17
|
1083 // used as blocks but also inline in function definitions.
|
Chris@17
|
1084 // So if we find them nested inside another opener, just skip them.
|
Chris@17
|
1085 continue;
|
Chris@17
|
1086 }
|
Chris@17
|
1087
|
Chris@17
|
1088 if ($tokenType === T_FUNCTION
|
Chris@17
|
1089 && $this->tokens[$stackPtr]['code'] !== T_FUNCTION
|
Chris@17
|
1090 ) {
|
Chris@17
|
1091 // Probably a closure, so process it manually.
|
Chris@17
|
1092 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1093 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1094 echo str_repeat("\t", $depth);
|
Chris@17
|
1095 echo "=> Found function before scope opener for $stackPtr:$type, processing manually".PHP_EOL;
|
Chris@17
|
1096 }
|
Chris@17
|
1097
|
Chris@17
|
1098 if (isset($this->tokens[$i]['scope_closer']) === true) {
|
Chris@17
|
1099 // We've already processed this closure.
|
Chris@17
|
1100 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1101 echo str_repeat("\t", $depth);
|
Chris@17
|
1102 echo '* already processed, skipping *'.PHP_EOL;
|
Chris@17
|
1103 }
|
Chris@17
|
1104
|
Chris@17
|
1105 $i = $this->tokens[$i]['scope_closer'];
|
Chris@17
|
1106 continue;
|
Chris@17
|
1107 }
|
Chris@17
|
1108
|
Chris@17
|
1109 $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
|
Chris@17
|
1110 continue;
|
Chris@17
|
1111 }//end if
|
Chris@17
|
1112
|
Chris@17
|
1113 if ($tokenType === T_CLASS) {
|
Chris@17
|
1114 // Probably an anonymous class inside another anonymous class,
|
Chris@17
|
1115 // so process it manually.
|
Chris@17
|
1116 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1117 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1118 echo str_repeat("\t", $depth);
|
Chris@17
|
1119 echo "=> Found class before scope opener for $stackPtr:$type, processing manually".PHP_EOL;
|
Chris@17
|
1120 }
|
Chris@17
|
1121
|
Chris@17
|
1122 if (isset($this->tokens[$i]['scope_closer']) === true) {
|
Chris@17
|
1123 // We've already processed this anon class.
|
Chris@17
|
1124 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1125 echo str_repeat("\t", $depth);
|
Chris@17
|
1126 echo '* already processed, skipping *'.PHP_EOL;
|
Chris@17
|
1127 }
|
Chris@17
|
1128
|
Chris@17
|
1129 $i = $this->tokens[$i]['scope_closer'];
|
Chris@17
|
1130 continue;
|
Chris@17
|
1131 }
|
Chris@17
|
1132
|
Chris@17
|
1133 $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
|
Chris@17
|
1134 continue;
|
Chris@17
|
1135 }//end if
|
Chris@17
|
1136
|
Chris@17
|
1137 // Found another opening condition but still haven't
|
Chris@17
|
1138 // found our opener, so we are never going to find one.
|
Chris@17
|
1139 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1140 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1141 echo str_repeat("\t", $depth);
|
Chris@17
|
1142 echo "=> Found new opening condition before scope opener for $stackPtr:$type, ";
|
Chris@17
|
1143 }
|
Chris@17
|
1144
|
Chris@17
|
1145 if (($this->tokens[$stackPtr]['code'] === T_IF
|
Chris@17
|
1146 || $this->tokens[$stackPtr]['code'] === T_ELSEIF
|
Chris@17
|
1147 || $this->tokens[$stackPtr]['code'] === T_ELSE)
|
Chris@17
|
1148 && ($this->tokens[$i]['code'] === T_ELSE
|
Chris@17
|
1149 || $this->tokens[$i]['code'] === T_ELSEIF)
|
Chris@17
|
1150 ) {
|
Chris@17
|
1151 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1152 echo "continuing".PHP_EOL;
|
Chris@17
|
1153 }
|
Chris@17
|
1154
|
Chris@17
|
1155 return ($i - 1);
|
Chris@17
|
1156 } else {
|
Chris@17
|
1157 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1158 echo "backtracking".PHP_EOL;
|
Chris@17
|
1159 }
|
Chris@17
|
1160
|
Chris@17
|
1161 return $stackPtr;
|
Chris@17
|
1162 }
|
Chris@17
|
1163 }//end if
|
Chris@17
|
1164
|
Chris@17
|
1165 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1166 echo str_repeat("\t", $depth);
|
Chris@17
|
1167 echo '* token is an opening condition *'.PHP_EOL;
|
Chris@17
|
1168 }
|
Chris@17
|
1169
|
Chris@17
|
1170 $isShared = ($this->scopeOpeners[$tokenType]['shared'] === true);
|
Chris@17
|
1171
|
Chris@17
|
1172 if (isset($this->tokens[$i]['scope_condition']) === true) {
|
Chris@17
|
1173 // We've been here before.
|
Chris@17
|
1174 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1175 echo str_repeat("\t", $depth);
|
Chris@17
|
1176 echo '* already processed, skipping *'.PHP_EOL;
|
Chris@17
|
1177 }
|
Chris@17
|
1178
|
Chris@17
|
1179 if ($isShared === false
|
Chris@17
|
1180 && isset($this->tokens[$i]['scope_closer']) === true
|
Chris@17
|
1181 ) {
|
Chris@17
|
1182 $i = $this->tokens[$i]['scope_closer'];
|
Chris@17
|
1183 }
|
Chris@17
|
1184
|
Chris@17
|
1185 continue;
|
Chris@17
|
1186 } else if ($currType === $tokenType
|
Chris@17
|
1187 && $isShared === false
|
Chris@17
|
1188 && $opener === null
|
Chris@17
|
1189 ) {
|
Chris@17
|
1190 // We haven't yet found our opener, but we have found another
|
Chris@17
|
1191 // scope opener which is the same type as us, and we don't
|
Chris@17
|
1192 // share openers, so we will never find one.
|
Chris@17
|
1193 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1194 echo str_repeat("\t", $depth);
|
Chris@17
|
1195 echo '* it was another token\'s opener, bailing *'.PHP_EOL;
|
Chris@17
|
1196 }
|
Chris@17
|
1197
|
Chris@17
|
1198 return $stackPtr;
|
Chris@17
|
1199 } else {
|
Chris@17
|
1200 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1201 echo str_repeat("\t", $depth);
|
Chris@17
|
1202 echo '* searching for opener *'.PHP_EOL;
|
Chris@17
|
1203 }
|
Chris@17
|
1204
|
Chris@17
|
1205 if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
|
Chris@17
|
1206 $oldIgnore = $ignore;
|
Chris@17
|
1207 $ignore = 0;
|
Chris@17
|
1208 }
|
Chris@17
|
1209
|
Chris@17
|
1210 // PHP has a max nesting level for functions. Stop before we hit that limit
|
Chris@17
|
1211 // because too many loops means we've run into trouble anyway.
|
Chris@17
|
1212 if ($depth > 50) {
|
Chris@17
|
1213 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1214 echo str_repeat("\t", $depth);
|
Chris@17
|
1215 echo '* reached maximum nesting level; aborting *'.PHP_EOL;
|
Chris@17
|
1216 }
|
Chris@17
|
1217
|
Chris@17
|
1218 throw new RuntimeException('Maximum nesting level reached; file could not be processed');
|
Chris@17
|
1219 }
|
Chris@17
|
1220
|
Chris@17
|
1221 $oldDepth = $depth;
|
Chris@17
|
1222 if ($isShared === true
|
Chris@17
|
1223 && isset($this->scopeOpeners[$tokenType]['with'][$currType]) === true
|
Chris@17
|
1224 ) {
|
Chris@17
|
1225 // Don't allow the depth to increment because this is
|
Chris@17
|
1226 // possibly not a true nesting if we are sharing our closer.
|
Chris@17
|
1227 // This can happen, for example, when a SWITCH has a large
|
Chris@17
|
1228 // number of CASE statements with the same shared BREAK.
|
Chris@17
|
1229 $depth--;
|
Chris@17
|
1230 }
|
Chris@17
|
1231
|
Chris@17
|
1232 $i = self::recurseScopeMap($i, ($depth + 1), $ignore);
|
Chris@17
|
1233 $depth = $oldDepth;
|
Chris@17
|
1234
|
Chris@17
|
1235 if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
|
Chris@17
|
1236 $ignore = $oldIgnore;
|
Chris@17
|
1237 }
|
Chris@17
|
1238 }//end if
|
Chris@17
|
1239 }//end if
|
Chris@17
|
1240
|
Chris@17
|
1241 if (isset($this->scopeOpeners[$currType]['start'][$tokenType]) === true
|
Chris@17
|
1242 && $opener === null
|
Chris@17
|
1243 ) {
|
Chris@17
|
1244 if ($tokenType === T_OPEN_CURLY_BRACKET) {
|
Chris@17
|
1245 if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true
|
Chris@17
|
1246 && $i < $this->tokens[$stackPtr]['parenthesis_closer']
|
Chris@17
|
1247 ) {
|
Chris@17
|
1248 // We found a curly brace inside the condition of the
|
Chris@17
|
1249 // current scope opener, so it must be a string offset.
|
Chris@17
|
1250 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1251 echo str_repeat("\t", $depth);
|
Chris@17
|
1252 echo '* ignoring curly brace inside condition *'.PHP_EOL;
|
Chris@17
|
1253 }
|
Chris@17
|
1254
|
Chris@17
|
1255 $ignore++;
|
Chris@17
|
1256 } else {
|
Chris@17
|
1257 // Make sure this is actually an opener and not a
|
Chris@17
|
1258 // string offset (e.g., $var{0}).
|
Chris@17
|
1259 for ($x = ($i - 1); $x > 0; $x--) {
|
Chris@17
|
1260 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
|
Chris@17
|
1261 continue;
|
Chris@17
|
1262 } else {
|
Chris@17
|
1263 // If the first non-whitespace/comment token looks like this
|
Chris@17
|
1264 // brace is a string offset, or this brace is mid-way through
|
Chris@17
|
1265 // a new statement, it isn't a scope opener.
|
Chris@17
|
1266 $disallowed = Util\Tokens::$assignmentTokens;
|
Chris@17
|
1267 $disallowed += [
|
Chris@17
|
1268 T_DOLLAR => true,
|
Chris@17
|
1269 T_VARIABLE => true,
|
Chris@17
|
1270 T_OBJECT_OPERATOR => true,
|
Chris@17
|
1271 T_COMMA => true,
|
Chris@17
|
1272 T_OPEN_PARENTHESIS => true,
|
Chris@17
|
1273 ];
|
Chris@17
|
1274
|
Chris@17
|
1275 if (isset($disallowed[$this->tokens[$x]['code']]) === true) {
|
Chris@17
|
1276 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1277 echo str_repeat("\t", $depth);
|
Chris@17
|
1278 echo '* ignoring curly brace *'.PHP_EOL;
|
Chris@17
|
1279 }
|
Chris@17
|
1280
|
Chris@17
|
1281 $ignore++;
|
Chris@17
|
1282 }
|
Chris@17
|
1283
|
Chris@17
|
1284 break;
|
Chris@17
|
1285 }//end if
|
Chris@17
|
1286 }//end for
|
Chris@17
|
1287 }//end if
|
Chris@17
|
1288 }//end if
|
Chris@17
|
1289
|
Chris@17
|
1290 if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) {
|
Chris@17
|
1291 // We found the opening scope token for $currType.
|
Chris@17
|
1292 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1293 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1294 echo str_repeat("\t", $depth);
|
Chris@17
|
1295 echo "=> Found scope opener for $stackPtr:$type".PHP_EOL;
|
Chris@17
|
1296 }
|
Chris@17
|
1297
|
Chris@17
|
1298 $opener = $i;
|
Chris@17
|
1299 }
|
Chris@17
|
1300 } else if ($tokenType === T_OPEN_PARENTHESIS) {
|
Chris@17
|
1301 if (isset($this->tokens[$i]['parenthesis_owner']) === true) {
|
Chris@17
|
1302 $owner = $this->tokens[$i]['parenthesis_owner'];
|
Chris@17
|
1303 if (isset(Util\Tokens::$scopeOpeners[$this->tokens[$owner]['code']]) === true
|
Chris@17
|
1304 && isset($this->tokens[$i]['parenthesis_closer']) === true
|
Chris@17
|
1305 ) {
|
Chris@17
|
1306 // If we get into here, then we opened a parenthesis for
|
Chris@17
|
1307 // a scope (eg. an if or else if) so we need to update the
|
Chris@17
|
1308 // start of the line so that when we check to see
|
Chris@17
|
1309 // if the closing parenthesis is more than 3 lines away from
|
Chris@17
|
1310 // the statement, we check from the closing parenthesis.
|
Chris@17
|
1311 $startLine = $this->tokens[$this->tokens[$i]['parenthesis_closer']]['line'];
|
Chris@17
|
1312 }
|
Chris@17
|
1313 }
|
Chris@17
|
1314 } else if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) {
|
Chris@17
|
1315 // We opened something that we don't have a scope opener for.
|
Chris@17
|
1316 // Examples of this are curly brackets for string offsets etc.
|
Chris@17
|
1317 // We want to ignore this so that we don't have an invalid scope
|
Chris@17
|
1318 // map.
|
Chris@17
|
1319 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1320 echo str_repeat("\t", $depth);
|
Chris@17
|
1321 echo '* ignoring curly brace *'.PHP_EOL;
|
Chris@17
|
1322 }
|
Chris@17
|
1323
|
Chris@17
|
1324 $ignore++;
|
Chris@17
|
1325 } else if ($tokenType === T_CLOSE_CURLY_BRACKET && $ignore > 0) {
|
Chris@17
|
1326 // We found the end token for the opener we were ignoring.
|
Chris@17
|
1327 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1328 echo str_repeat("\t", $depth);
|
Chris@17
|
1329 echo '* finished ignoring curly brace *'.PHP_EOL;
|
Chris@17
|
1330 }
|
Chris@17
|
1331
|
Chris@17
|
1332 $ignore--;
|
Chris@17
|
1333 } else if ($opener === null
|
Chris@17
|
1334 && isset($this->scopeOpeners[$currType]) === true
|
Chris@17
|
1335 ) {
|
Chris@17
|
1336 // If we still haven't found the opener after 30 lines,
|
Chris@17
|
1337 // we're not going to find it, unless we know it requires
|
Chris@17
|
1338 // an opener (in which case we better keep looking) or the last
|
Chris@17
|
1339 // token was empty (in which case we'll just confirm there is
|
Chris@17
|
1340 // more code in this file and not just a big comment).
|
Chris@17
|
1341 if ($this->tokens[$i]['line'] >= ($startLine + 30)
|
Chris@17
|
1342 && isset(Util\Tokens::$emptyTokens[$this->tokens[($i - 1)]['code']]) === false
|
Chris@17
|
1343 ) {
|
Chris@17
|
1344 if ($this->scopeOpeners[$currType]['strict'] === true) {
|
Chris@17
|
1345 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1346 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1347 $lines = ($this->tokens[$i]['line'] - $startLine);
|
Chris@17
|
1348 echo str_repeat("\t", $depth);
|
Chris@17
|
1349 echo "=> Still looking for $stackPtr:$type scope opener after $lines lines".PHP_EOL;
|
Chris@17
|
1350 }
|
Chris@17
|
1351 } else {
|
Chris@17
|
1352 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1353 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1354 echo str_repeat("\t", $depth);
|
Chris@17
|
1355 echo "=> Couldn't find scope opener for $stackPtr:$type, bailing".PHP_EOL;
|
Chris@17
|
1356 }
|
Chris@17
|
1357
|
Chris@17
|
1358 return $stackPtr;
|
Chris@17
|
1359 }
|
Chris@17
|
1360 }
|
Chris@17
|
1361 } else if ($opener !== null
|
Chris@17
|
1362 && $tokenType !== T_BREAK
|
Chris@17
|
1363 && isset($this->endScopeTokens[$tokenType]) === true
|
Chris@17
|
1364 ) {
|
Chris@17
|
1365 if (isset($this->tokens[$i]['scope_condition']) === false) {
|
Chris@17
|
1366 if ($ignore > 0) {
|
Chris@17
|
1367 // We found the end token for the opener we were ignoring.
|
Chris@17
|
1368 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1369 echo str_repeat("\t", $depth);
|
Chris@17
|
1370 echo '* finished ignoring curly brace *'.PHP_EOL;
|
Chris@17
|
1371 }
|
Chris@17
|
1372
|
Chris@17
|
1373 $ignore--;
|
Chris@17
|
1374 } else {
|
Chris@17
|
1375 // We found a token that closes the scope but it doesn't
|
Chris@17
|
1376 // have a condition, so it belongs to another token and
|
Chris@17
|
1377 // our token doesn't have a closer, so pretend this is
|
Chris@17
|
1378 // the closer.
|
Chris@17
|
1379 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1380 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1381 echo str_repeat("\t", $depth);
|
Chris@17
|
1382 echo "=> Found (unexpected) scope closer for $stackPtr:$type".PHP_EOL;
|
Chris@17
|
1383 }
|
Chris@17
|
1384
|
Chris@17
|
1385 foreach ([$stackPtr, $opener] as $token) {
|
Chris@17
|
1386 $this->tokens[$token]['scope_condition'] = $stackPtr;
|
Chris@17
|
1387 $this->tokens[$token]['scope_opener'] = $opener;
|
Chris@17
|
1388 $this->tokens[$token]['scope_closer'] = $i;
|
Chris@17
|
1389 }
|
Chris@17
|
1390
|
Chris@17
|
1391 return ($i - 1);
|
Chris@17
|
1392 }//end if
|
Chris@17
|
1393 }//end if
|
Chris@17
|
1394 }//end if
|
Chris@17
|
1395 }//end for
|
Chris@17
|
1396
|
Chris@17
|
1397 return $stackPtr;
|
Chris@17
|
1398
|
Chris@17
|
1399 }//end recurseScopeMap()
|
Chris@17
|
1400
|
Chris@17
|
1401
|
Chris@17
|
1402 /**
|
Chris@17
|
1403 * Constructs the level map.
|
Chris@17
|
1404 *
|
Chris@17
|
1405 * The level map adds a 'level' index to each token which indicates the
|
Chris@17
|
1406 * depth that a token within a set of scope blocks. It also adds a
|
Chris@17
|
1407 * 'conditions' index which is an array of the scope conditions that opened
|
Chris@17
|
1408 * each of the scopes - position 0 being the first scope opener.
|
Chris@17
|
1409 *
|
Chris@17
|
1410 * @return void
|
Chris@17
|
1411 */
|
Chris@17
|
1412 private function createLevelMap()
|
Chris@17
|
1413 {
|
Chris@17
|
1414 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1415 echo "\t*** START LEVEL MAP ***".PHP_EOL;
|
Chris@17
|
1416 }
|
Chris@17
|
1417
|
Chris@17
|
1418 $this->numTokens = count($this->tokens);
|
Chris@17
|
1419 $level = 0;
|
Chris@17
|
1420 $conditions = [];
|
Chris@17
|
1421 $lastOpener = null;
|
Chris@17
|
1422 $openers = [];
|
Chris@17
|
1423
|
Chris@17
|
1424 for ($i = 0; $i < $this->numTokens; $i++) {
|
Chris@17
|
1425 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1426 $type = $this->tokens[$i]['type'];
|
Chris@17
|
1427 $line = $this->tokens[$i]['line'];
|
Chris@17
|
1428 $len = $this->tokens[$i]['length'];
|
Chris@17
|
1429 $col = $this->tokens[$i]['column'];
|
Chris@17
|
1430
|
Chris@17
|
1431 $content = Util\Common::prepareForOutput($this->tokens[$i]['content']);
|
Chris@17
|
1432
|
Chris@17
|
1433 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1434 echo "Process token $i on line $line [col:$col;len:$len;lvl:$level;";
|
Chris@17
|
1435 if (empty($conditions) !== true) {
|
Chris@17
|
1436 $condString = 'conds;';
|
Chris@17
|
1437 foreach ($conditions as $condition) {
|
Chris@17
|
1438 $condString .= Util\Tokens::tokenName($condition).',';
|
Chris@17
|
1439 }
|
Chris@17
|
1440
|
Chris@17
|
1441 echo rtrim($condString, ',').';';
|
Chris@17
|
1442 }
|
Chris@17
|
1443
|
Chris@17
|
1444 echo "]: $type => $content".PHP_EOL;
|
Chris@17
|
1445 }//end if
|
Chris@17
|
1446
|
Chris@17
|
1447 $this->tokens[$i]['level'] = $level;
|
Chris@17
|
1448 $this->tokens[$i]['conditions'] = $conditions;
|
Chris@17
|
1449
|
Chris@17
|
1450 if (isset($this->tokens[$i]['scope_condition']) === true) {
|
Chris@17
|
1451 // Check to see if this token opened the scope.
|
Chris@17
|
1452 if ($this->tokens[$i]['scope_opener'] === $i) {
|
Chris@17
|
1453 $stackPtr = $this->tokens[$i]['scope_condition'];
|
Chris@17
|
1454 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1455 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1456 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1457 echo "=> Found scope opener for $stackPtr:$type".PHP_EOL;
|
Chris@17
|
1458 }
|
Chris@17
|
1459
|
Chris@17
|
1460 $stackPtr = $this->tokens[$i]['scope_condition'];
|
Chris@17
|
1461
|
Chris@17
|
1462 // If we find a scope opener that has a shared closer,
|
Chris@17
|
1463 // then we need to go back over the condition map that we
|
Chris@17
|
1464 // just created and fix ourselves as we just added some
|
Chris@17
|
1465 // conditions where there was none. This happens for T_CASE
|
Chris@17
|
1466 // statements that are using the same break statement.
|
Chris@17
|
1467 if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $this->tokens[$i]['scope_closer']) {
|
Chris@17
|
1468 // This opener shares its closer with the previous opener,
|
Chris@17
|
1469 // but we still need to check if the two openers share their
|
Chris@17
|
1470 // closer with each other directly (like CASE and DEFAULT)
|
Chris@17
|
1471 // or if they are just sharing because one doesn't have a
|
Chris@17
|
1472 // closer (like CASE with no BREAK using a SWITCHes closer).
|
Chris@17
|
1473 $thisType = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
|
Chris@17
|
1474 $opener = $this->tokens[$lastOpener]['scope_condition'];
|
Chris@17
|
1475
|
Chris@17
|
1476 $isShared = isset($this->scopeOpeners[$thisType]['with'][$this->tokens[$opener]['code']]);
|
Chris@17
|
1477
|
Chris@17
|
1478 reset($this->scopeOpeners[$thisType]['end']);
|
Chris@17
|
1479 reset($this->scopeOpeners[$this->tokens[$opener]['code']]['end']);
|
Chris@17
|
1480 $sameEnd = (current($this->scopeOpeners[$thisType]['end']) === current($this->scopeOpeners[$this->tokens[$opener]['code']]['end']));
|
Chris@17
|
1481
|
Chris@17
|
1482 if ($isShared === true && $sameEnd === true) {
|
Chris@17
|
1483 $badToken = $opener;
|
Chris@17
|
1484 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1485 $type = $this->tokens[$badToken]['type'];
|
Chris@17
|
1486 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1487 echo "* shared closer, cleaning up $badToken:$type *".PHP_EOL;
|
Chris@17
|
1488 }
|
Chris@17
|
1489
|
Chris@17
|
1490 for ($x = $this->tokens[$i]['scope_condition']; $x <= $i; $x++) {
|
Chris@17
|
1491 $oldConditions = $this->tokens[$x]['conditions'];
|
Chris@17
|
1492 $oldLevel = $this->tokens[$x]['level'];
|
Chris@17
|
1493 $this->tokens[$x]['level']--;
|
Chris@17
|
1494 unset($this->tokens[$x]['conditions'][$badToken]);
|
Chris@17
|
1495 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1496 $type = $this->tokens[$x]['type'];
|
Chris@17
|
1497 $oldConds = '';
|
Chris@17
|
1498 foreach ($oldConditions as $condition) {
|
Chris@17
|
1499 $oldConds .= Util\Tokens::tokenName($condition).',';
|
Chris@17
|
1500 }
|
Chris@17
|
1501
|
Chris@17
|
1502 $oldConds = rtrim($oldConds, ',');
|
Chris@17
|
1503
|
Chris@17
|
1504 $newConds = '';
|
Chris@17
|
1505 foreach ($this->tokens[$x]['conditions'] as $condition) {
|
Chris@17
|
1506 $newConds .= Util\Tokens::tokenName($condition).',';
|
Chris@17
|
1507 }
|
Chris@17
|
1508
|
Chris@17
|
1509 $newConds = rtrim($newConds, ',');
|
Chris@17
|
1510
|
Chris@17
|
1511 $newLevel = $this->tokens[$x]['level'];
|
Chris@17
|
1512 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1513 echo "* cleaned $x:$type *".PHP_EOL;
|
Chris@17
|
1514 echo str_repeat("\t", ($level + 2));
|
Chris@17
|
1515 echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
|
Chris@17
|
1516 echo str_repeat("\t", ($level + 2));
|
Chris@17
|
1517 echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
|
Chris@17
|
1518 }//end if
|
Chris@17
|
1519 }//end for
|
Chris@17
|
1520
|
Chris@17
|
1521 unset($conditions[$badToken]);
|
Chris@17
|
1522 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1523 $type = $this->tokens[$badToken]['type'];
|
Chris@17
|
1524 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1525 echo "* token $badToken:$type removed from conditions array *".PHP_EOL;
|
Chris@17
|
1526 }
|
Chris@17
|
1527
|
Chris@17
|
1528 unset($openers[$lastOpener]);
|
Chris@17
|
1529
|
Chris@17
|
1530 $level--;
|
Chris@17
|
1531 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1532 echo str_repeat("\t", ($level + 2));
|
Chris@17
|
1533 echo '* level decreased *'.PHP_EOL;
|
Chris@17
|
1534 }
|
Chris@17
|
1535 }//end if
|
Chris@17
|
1536 }//end if
|
Chris@17
|
1537
|
Chris@17
|
1538 $level++;
|
Chris@17
|
1539 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1540 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1541 echo '* level increased *'.PHP_EOL;
|
Chris@17
|
1542 }
|
Chris@17
|
1543
|
Chris@17
|
1544 $conditions[$stackPtr] = $this->tokens[$stackPtr]['code'];
|
Chris@17
|
1545 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1546 $type = $this->tokens[$stackPtr]['type'];
|
Chris@17
|
1547 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1548 echo "* token $stackPtr:$type added to conditions array *".PHP_EOL;
|
Chris@17
|
1549 }
|
Chris@17
|
1550
|
Chris@17
|
1551 $lastOpener = $this->tokens[$i]['scope_opener'];
|
Chris@17
|
1552 if ($lastOpener !== null) {
|
Chris@17
|
1553 $openers[$lastOpener] = $lastOpener;
|
Chris@17
|
1554 }
|
Chris@17
|
1555 } else if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $i) {
|
Chris@17
|
1556 foreach (array_reverse($openers) as $opener) {
|
Chris@17
|
1557 if ($this->tokens[$opener]['scope_closer'] === $i) {
|
Chris@17
|
1558 $oldOpener = array_pop($openers);
|
Chris@17
|
1559 if (empty($openers) === false) {
|
Chris@17
|
1560 $lastOpener = array_pop($openers);
|
Chris@17
|
1561 $openers[$lastOpener] = $lastOpener;
|
Chris@17
|
1562 } else {
|
Chris@17
|
1563 $lastOpener = null;
|
Chris@17
|
1564 }
|
Chris@17
|
1565
|
Chris@17
|
1566 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1567 $type = $this->tokens[$oldOpener]['type'];
|
Chris@17
|
1568 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1569 echo "=> Found scope closer for $oldOpener:$type".PHP_EOL;
|
Chris@17
|
1570 }
|
Chris@17
|
1571
|
Chris@17
|
1572 $oldCondition = array_pop($conditions);
|
Chris@17
|
1573 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1574 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1575 echo '* token '.Util\Tokens::tokenName($oldCondition).' removed from conditions array *'.PHP_EOL;
|
Chris@17
|
1576 }
|
Chris@17
|
1577
|
Chris@17
|
1578 // Make sure this closer actually belongs to us.
|
Chris@17
|
1579 // Either the condition also has to think this is the
|
Chris@17
|
1580 // closer, or it has to allow sharing with us.
|
Chris@17
|
1581 $condition = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
|
Chris@17
|
1582 if ($condition !== $oldCondition) {
|
Chris@17
|
1583 if (isset($this->scopeOpeners[$oldCondition]['with'][$condition]) === false) {
|
Chris@17
|
1584 $badToken = $this->tokens[$oldOpener]['scope_condition'];
|
Chris@17
|
1585
|
Chris@17
|
1586 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1587 $type = Util\Tokens::tokenName($oldCondition);
|
Chris@17
|
1588 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1589 echo "* scope closer was bad, cleaning up $badToken:$type *".PHP_EOL;
|
Chris@17
|
1590 }
|
Chris@17
|
1591
|
Chris@17
|
1592 for ($x = ($oldOpener + 1); $x <= $i; $x++) {
|
Chris@17
|
1593 $oldConditions = $this->tokens[$x]['conditions'];
|
Chris@17
|
1594 $oldLevel = $this->tokens[$x]['level'];
|
Chris@17
|
1595 $this->tokens[$x]['level']--;
|
Chris@17
|
1596 unset($this->tokens[$x]['conditions'][$badToken]);
|
Chris@17
|
1597 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1598 $type = $this->tokens[$x]['type'];
|
Chris@17
|
1599 $oldConds = '';
|
Chris@17
|
1600 foreach ($oldConditions as $condition) {
|
Chris@17
|
1601 $oldConds .= Util\Tokens::tokenName($condition).',';
|
Chris@17
|
1602 }
|
Chris@17
|
1603
|
Chris@17
|
1604 $oldConds = rtrim($oldConds, ',');
|
Chris@17
|
1605
|
Chris@17
|
1606 $newConds = '';
|
Chris@17
|
1607 foreach ($this->tokens[$x]['conditions'] as $condition) {
|
Chris@17
|
1608 $newConds .= Util\Tokens::tokenName($condition).',';
|
Chris@17
|
1609 }
|
Chris@17
|
1610
|
Chris@17
|
1611 $newConds = rtrim($newConds, ',');
|
Chris@17
|
1612
|
Chris@17
|
1613 $newLevel = $this->tokens[$x]['level'];
|
Chris@17
|
1614 echo str_repeat("\t", ($level + 1));
|
Chris@17
|
1615 echo "* cleaned $x:$type *".PHP_EOL;
|
Chris@17
|
1616 echo str_repeat("\t", ($level + 2));
|
Chris@17
|
1617 echo "=> level changed from $oldLevel to $newLevel".PHP_EOL;
|
Chris@17
|
1618 echo str_repeat("\t", ($level + 2));
|
Chris@17
|
1619 echo "=> conditions changed from $oldConds to $newConds".PHP_EOL;
|
Chris@17
|
1620 }//end if
|
Chris@17
|
1621 }//end for
|
Chris@17
|
1622 }//end if
|
Chris@17
|
1623 }//end if
|
Chris@17
|
1624
|
Chris@17
|
1625 $level--;
|
Chris@17
|
1626 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1627 echo str_repeat("\t", ($level + 2));
|
Chris@17
|
1628 echo '* level decreased *'.PHP_EOL;
|
Chris@17
|
1629 }
|
Chris@17
|
1630
|
Chris@17
|
1631 $this->tokens[$i]['level'] = $level;
|
Chris@17
|
1632 $this->tokens[$i]['conditions'] = $conditions;
|
Chris@17
|
1633 }//end if
|
Chris@17
|
1634 }//end foreach
|
Chris@17
|
1635 }//end if
|
Chris@17
|
1636 }//end if
|
Chris@17
|
1637 }//end for
|
Chris@17
|
1638
|
Chris@17
|
1639 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
1640 echo "\t*** END LEVEL MAP ***".PHP_EOL;
|
Chris@17
|
1641 }
|
Chris@17
|
1642
|
Chris@17
|
1643 }//end createLevelMap()
|
Chris@17
|
1644
|
Chris@17
|
1645
|
Chris@17
|
1646 }//end class
|