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