Mercurial > hg > isophonics-drupal-site
comparison vendor/squizlabs/php_codesniffer/src/Tokenizers/CSS.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | |
children | af1871eacc83 |
comparison
equal
deleted
inserted
replaced
16:c2387f117808 | 17:129ea1e6d783 |
---|---|
1 <?php | |
2 /** | |
3 * Tokenizes CSS code. | |
4 * | |
5 * @author Greg Sherwood <gsherwood@squiz.net> | |
6 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) | |
7 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence | |
8 */ | |
9 | |
10 namespace PHP_CodeSniffer\Tokenizers; | |
11 | |
12 use PHP_CodeSniffer\Util; | |
13 use PHP_CodeSniffer\Config; | |
14 use PHP_CodeSniffer\Exceptions\TokenizerException; | |
15 | |
16 class CSS extends PHP | |
17 { | |
18 | |
19 | |
20 /** | |
21 * Initialise the tokenizer. | |
22 * | |
23 * Pre-checks the content to see if it looks minified. | |
24 * | |
25 * @param string $content The content to tokenize, | |
26 * @param \PHP_CodeSniffer\Config $config The config data for the run. | |
27 * @param string $eolChar The EOL char used in the content. | |
28 * | |
29 * @return void | |
30 * @throws TokenizerException If the file appears to be minified. | |
31 */ | |
32 public function __construct($content, Config $config, $eolChar='\n') | |
33 { | |
34 if ($this->isMinifiedContent($content, $eolChar) === true) { | |
35 throw new TokenizerException('File appears to be minified and cannot be processed'); | |
36 } | |
37 | |
38 return parent::__construct($content, $config, $eolChar); | |
39 | |
40 }//end __construct() | |
41 | |
42 | |
43 /** | |
44 * Creates an array of tokens when given some CSS code. | |
45 * | |
46 * Uses the PHP tokenizer to do all the tricky work | |
47 * | |
48 * @param string $string The string to tokenize. | |
49 * | |
50 * @return array | |
51 */ | |
52 public function tokenize($string) | |
53 { | |
54 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
55 echo "\t*** START CSS TOKENIZING 1ST PASS ***".PHP_EOL; | |
56 } | |
57 | |
58 // If the content doesn't have an EOL char on the end, add one so | |
59 // the open and close tags we add are parsed correctly. | |
60 $eolAdded = false; | |
61 if (substr($string, (strlen($this->eolChar) * -1)) !== $this->eolChar) { | |
62 $string .= $this->eolChar; | |
63 $eolAdded = true; | |
64 } | |
65 | |
66 $string = str_replace('<?php', '^PHPCS_CSS_T_OPEN_TAG^', $string); | |
67 $string = str_replace('?>', '^PHPCS_CSS_T_CLOSE_TAG^', $string); | |
68 $tokens = parent::tokenize('<?php '.$string.'?>'); | |
69 | |
70 $finalTokens = []; | |
71 $finalTokens[0] = [ | |
72 'code' => T_OPEN_TAG, | |
73 'type' => 'T_OPEN_TAG', | |
74 'content' => '', | |
75 ]; | |
76 | |
77 $newStackPtr = 1; | |
78 $numTokens = count($tokens); | |
79 $multiLineComment = false; | |
80 for ($stackPtr = 1; $stackPtr < $numTokens; $stackPtr++) { | |
81 $token = $tokens[$stackPtr]; | |
82 | |
83 // CSS files don't have lists, breaks etc, so convert these to | |
84 // standard strings early so they can be converted into T_STYLE | |
85 // tokens and joined with other strings if needed. | |
86 if ($token['code'] === T_BREAK | |
87 || $token['code'] === T_LIST | |
88 || $token['code'] === T_DEFAULT | |
89 || $token['code'] === T_SWITCH | |
90 || $token['code'] === T_FOR | |
91 || $token['code'] === T_FOREACH | |
92 || $token['code'] === T_WHILE | |
93 || $token['code'] === T_DEC | |
94 || $token['code'] === T_NEW | |
95 ) { | |
96 $token['type'] = 'T_STRING'; | |
97 $token['code'] = T_STRING; | |
98 } | |
99 | |
100 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
101 $type = $token['type']; | |
102 $content = Util\Common::prepareForOutput($token['content']); | |
103 echo "\tProcess token $stackPtr: $type => $content".PHP_EOL; | |
104 } | |
105 | |
106 if ($token['code'] === T_BITWISE_XOR | |
107 && $tokens[($stackPtr + 1)]['content'] === 'PHPCS_CSS_T_OPEN_TAG' | |
108 ) { | |
109 $content = '<?php'; | |
110 for ($stackPtr += 3; $stackPtr < $numTokens; $stackPtr++) { | |
111 if ($tokens[$stackPtr]['code'] === T_BITWISE_XOR | |
112 && $tokens[($stackPtr + 1)]['content'] === 'PHPCS_CSS_T_CLOSE_TAG' | |
113 ) { | |
114 // Add the end tag and ignore the * we put at the end. | |
115 $content .= '?>'; | |
116 $stackPtr += 2; | |
117 break; | |
118 } else { | |
119 $content .= $tokens[$stackPtr]['content']; | |
120 } | |
121 } | |
122 | |
123 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
124 echo "\t\t=> Found embedded PHP code: "; | |
125 $cleanContent = Util\Common::prepareForOutput($content); | |
126 echo $cleanContent.PHP_EOL; | |
127 } | |
128 | |
129 $finalTokens[$newStackPtr] = [ | |
130 'type' => 'T_EMBEDDED_PHP', | |
131 'code' => T_EMBEDDED_PHP, | |
132 'content' => $content, | |
133 ]; | |
134 | |
135 $newStackPtr++; | |
136 continue; | |
137 }//end if | |
138 | |
139 if ($token['code'] === T_GOTO_LABEL) { | |
140 // Convert these back to T_STRING followed by T_COLON so we can | |
141 // more easily process style definitions. | |
142 $finalTokens[$newStackPtr] = [ | |
143 'type' => 'T_STRING', | |
144 'code' => T_STRING, | |
145 'content' => substr($token['content'], 0, -1), | |
146 ]; | |
147 $newStackPtr++; | |
148 $finalTokens[$newStackPtr] = [ | |
149 'type' => 'T_COLON', | |
150 'code' => T_COLON, | |
151 'content' => ':', | |
152 ]; | |
153 $newStackPtr++; | |
154 continue; | |
155 } | |
156 | |
157 if ($token['code'] === T_FUNCTION) { | |
158 // There are no functions in CSS, so convert this to a string. | |
159 $finalTokens[$newStackPtr] = [ | |
160 'type' => 'T_STRING', | |
161 'code' => T_STRING, | |
162 'content' => $token['content'], | |
163 ]; | |
164 | |
165 $newStackPtr++; | |
166 continue; | |
167 } | |
168 | |
169 if ($token['code'] === T_COMMENT | |
170 && substr($token['content'], 0, 2) === '/*' | |
171 ) { | |
172 // Multi-line comment. Record it so we can ignore other | |
173 // comment tags until we get out of this one. | |
174 $multiLineComment = true; | |
175 } | |
176 | |
177 if ($token['code'] === T_COMMENT | |
178 && $multiLineComment === false | |
179 && (substr($token['content'], 0, 2) === '//' | |
180 || $token['content']{0} === '#') | |
181 ) { | |
182 $content = ltrim($token['content'], '#/'); | |
183 | |
184 // Guard against PHP7+ syntax errors by stripping | |
185 // leading zeros so the content doesn't look like an invalid int. | |
186 $leadingZero = false; | |
187 if ($content{0} === '0') { | |
188 $content = '1'.$content; | |
189 $leadingZero = true; | |
190 } | |
191 | |
192 $commentTokens = parent::tokenize('<?php '.$content.'?>'); | |
193 | |
194 // The first and last tokens are the open/close tags. | |
195 array_shift($commentTokens); | |
196 array_pop($commentTokens); | |
197 | |
198 if ($leadingZero === true) { | |
199 $commentTokens[0]['content'] = substr($commentTokens[0]['content'], 1); | |
200 $content = substr($content, 1); | |
201 } | |
202 | |
203 if ($token['content']{0} === '#') { | |
204 // The # character is not a comment in CSS files, so | |
205 // determine what it means in this context. | |
206 $firstContent = $commentTokens[0]['content']; | |
207 | |
208 // If the first content is just a number, it is probably a | |
209 // colour like 8FB7DB, which PHP splits into 8 and FB7DB. | |
210 if (($commentTokens[0]['code'] === T_LNUMBER | |
211 || $commentTokens[0]['code'] === T_DNUMBER) | |
212 && $commentTokens[1]['code'] === T_STRING | |
213 ) { | |
214 $firstContent .= $commentTokens[1]['content']; | |
215 array_shift($commentTokens); | |
216 } | |
217 | |
218 // If the first content looks like a colour and not a class | |
219 // definition, join the tokens together. | |
220 if (preg_match('/^[ABCDEF0-9]+$/i', $firstContent) === 1 | |
221 && $commentTokens[1]['content'] !== '-' | |
222 ) { | |
223 array_shift($commentTokens); | |
224 // Work out what we trimmed off above and remember to re-add it. | |
225 $trimmed = substr($token['content'], 0, (strlen($token['content']) - strlen($content))); | |
226 $finalTokens[$newStackPtr] = [ | |
227 'type' => 'T_COLOUR', | |
228 'code' => T_COLOUR, | |
229 'content' => $trimmed.$firstContent, | |
230 ]; | |
231 } else { | |
232 $finalTokens[$newStackPtr] = [ | |
233 'type' => 'T_HASH', | |
234 'code' => T_HASH, | |
235 'content' => '#', | |
236 ]; | |
237 } | |
238 } else { | |
239 $finalTokens[$newStackPtr] = [ | |
240 'type' => 'T_STRING', | |
241 'code' => T_STRING, | |
242 'content' => '//', | |
243 ]; | |
244 }//end if | |
245 | |
246 $newStackPtr++; | |
247 | |
248 array_splice($tokens, $stackPtr, 1, $commentTokens); | |
249 $numTokens = count($tokens); | |
250 $stackPtr--; | |
251 continue; | |
252 }//end if | |
253 | |
254 if ($token['code'] === T_COMMENT | |
255 && substr($token['content'], -2) === '*/' | |
256 ) { | |
257 // Multi-line comment is done. | |
258 $multiLineComment = false; | |
259 } | |
260 | |
261 $finalTokens[$newStackPtr] = $token; | |
262 $newStackPtr++; | |
263 }//end for | |
264 | |
265 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
266 echo "\t*** END CSS TOKENIZING 1ST PASS ***".PHP_EOL; | |
267 echo "\t*** START CSS TOKENIZING 2ND PASS ***".PHP_EOL; | |
268 } | |
269 | |
270 // A flag to indicate if we are inside a style definition, | |
271 // which is defined using curly braces. | |
272 $inStyleDef = false; | |
273 | |
274 // A flag to indicate if an At-rule like "@media" is used, which will result | |
275 // in nested curly brackets. | |
276 $asperandStart = false; | |
277 | |
278 $numTokens = count($finalTokens); | |
279 for ($stackPtr = 0; $stackPtr < $numTokens; $stackPtr++) { | |
280 $token = $finalTokens[$stackPtr]; | |
281 | |
282 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
283 $type = $token['type']; | |
284 $content = Util\Common::prepareForOutput($token['content']); | |
285 echo "\tProcess token $stackPtr: $type => $content".PHP_EOL; | |
286 } | |
287 | |
288 switch ($token['code']) { | |
289 case T_OPEN_CURLY_BRACKET: | |
290 // Opening curly brackets for an At-rule do not start a style | |
291 // definition. We also reset the asperand flag here because the next | |
292 // opening curly bracket could be indeed the start of a style | |
293 // definition. | |
294 if ($asperandStart === true) { | |
295 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
296 if ($inStyleDef === true) { | |
297 echo "\t\t* style definition closed *".PHP_EOL; | |
298 } | |
299 | |
300 if ($asperandStart === true) { | |
301 echo "\t\t* at-rule definition closed *".PHP_EOL; | |
302 } | |
303 } | |
304 | |
305 $inStyleDef = false; | |
306 $asperandStart = false; | |
307 } else { | |
308 $inStyleDef = true; | |
309 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
310 echo "\t\t* style definition opened *".PHP_EOL; | |
311 } | |
312 } | |
313 break; | |
314 case T_CLOSE_CURLY_BRACKET: | |
315 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
316 if ($inStyleDef === true) { | |
317 echo "\t\t* style definition closed *".PHP_EOL; | |
318 } | |
319 | |
320 if ($asperandStart === true) { | |
321 echo "\t\t* at-rule definition closed *".PHP_EOL; | |
322 } | |
323 } | |
324 | |
325 $inStyleDef = false; | |
326 $asperandStart = false; | |
327 break; | |
328 case T_MINUS: | |
329 // Minus signs are often used instead of spaces inside | |
330 // class names, IDs and styles. | |
331 if ($finalTokens[($stackPtr + 1)]['code'] === T_STRING) { | |
332 if ($finalTokens[($stackPtr - 1)]['code'] === T_STRING) { | |
333 $newContent = $finalTokens[($stackPtr - 1)]['content'].'-'.$finalTokens[($stackPtr + 1)]['content']; | |
334 | |
335 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
336 echo "\t\t* token is a string joiner; ignoring this and previous token".PHP_EOL; | |
337 $old = Util\Common::prepareForOutput($finalTokens[($stackPtr + 1)]['content']); | |
338 $new = Util\Common::prepareForOutput($newContent); | |
339 echo "\t\t=> token ".($stackPtr + 1)." content changed from \"$old\" to \"$new\"".PHP_EOL; | |
340 } | |
341 | |
342 $finalTokens[($stackPtr + 1)]['content'] = $newContent; | |
343 unset($finalTokens[$stackPtr]); | |
344 unset($finalTokens[($stackPtr - 1)]); | |
345 } else { | |
346 $newContent = '-'.$finalTokens[($stackPtr + 1)]['content']; | |
347 | |
348 $finalTokens[($stackPtr + 1)]['content'] = $newContent; | |
349 unset($finalTokens[$stackPtr]); | |
350 } | |
351 } else if ($finalTokens[($stackPtr + 1)]['code'] === T_LNUMBER) { | |
352 // They can also be used to provide negative numbers. | |
353 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
354 echo "\t\t* token is part of a negative number; adding content to next token and ignoring *".PHP_EOL; | |
355 $content = Util\Common::prepareForOutput($finalTokens[($stackPtr + 1)]['content']); | |
356 echo "\t\t=> token ".($stackPtr + 1)." content changed from \"$content\" to \"-$content\"".PHP_EOL; | |
357 } | |
358 | |
359 $finalTokens[($stackPtr + 1)]['content'] = '-'.$finalTokens[($stackPtr + 1)]['content']; | |
360 unset($finalTokens[$stackPtr]); | |
361 }//end if | |
362 break; | |
363 case T_COLON: | |
364 // Only interested in colons that are defining styles. | |
365 if ($inStyleDef === false) { | |
366 break; | |
367 } | |
368 | |
369 for ($x = ($stackPtr - 1); $x >= 0; $x--) { | |
370 if (isset(Util\Tokens::$emptyTokens[$finalTokens[$x]['code']]) === false) { | |
371 break; | |
372 } | |
373 } | |
374 | |
375 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
376 $type = $finalTokens[$x]['type']; | |
377 echo "\t\t=> token $x changed from $type to T_STYLE".PHP_EOL; | |
378 } | |
379 | |
380 $finalTokens[$x]['type'] = 'T_STYLE'; | |
381 $finalTokens[$x]['code'] = T_STYLE; | |
382 break; | |
383 case T_STRING: | |
384 if (strtolower($token['content']) === 'url') { | |
385 // Find the next content. | |
386 for ($x = ($stackPtr + 1); $x < $numTokens; $x++) { | |
387 if (isset(Util\Tokens::$emptyTokens[$finalTokens[$x]['code']]) === false) { | |
388 break; | |
389 } | |
390 } | |
391 | |
392 // Needs to be in the format "url(" for it to be a URL. | |
393 if ($finalTokens[$x]['code'] !== T_OPEN_PARENTHESIS) { | |
394 continue 2; | |
395 } | |
396 | |
397 // Make sure the content isn't empty. | |
398 for ($y = ($x + 1); $y < $numTokens; $y++) { | |
399 if (isset(Util\Tokens::$emptyTokens[$finalTokens[$y]['code']]) === false) { | |
400 break; | |
401 } | |
402 } | |
403 | |
404 if ($finalTokens[$y]['code'] === T_CLOSE_PARENTHESIS) { | |
405 continue 2; | |
406 } | |
407 | |
408 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
409 for ($i = ($stackPtr + 1); $i <= $y; $i++) { | |
410 $type = $finalTokens[$i]['type']; | |
411 $content = Util\Common::prepareForOutput($finalTokens[$i]['content']); | |
412 echo "\tProcess token $i: $type => $content".PHP_EOL; | |
413 } | |
414 | |
415 echo "\t\t* token starts a URL *".PHP_EOL; | |
416 } | |
417 | |
418 // Join all the content together inside the url() statement. | |
419 $newContent = ''; | |
420 for ($i = ($x + 2); $i < $numTokens; $i++) { | |
421 if ($finalTokens[$i]['code'] === T_CLOSE_PARENTHESIS) { | |
422 break; | |
423 } | |
424 | |
425 $newContent .= $finalTokens[$i]['content']; | |
426 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
427 $content = Util\Common::prepareForOutput($finalTokens[$i]['content']); | |
428 echo "\t\t=> token $i added to URL string and ignored: $content".PHP_EOL; | |
429 } | |
430 | |
431 unset($finalTokens[$i]); | |
432 } | |
433 | |
434 $stackPtr = $i; | |
435 | |
436 // If the content inside the "url()" is in double quotes | |
437 // there will only be one token and so we don't have to do | |
438 // anything except change its type. If it is not empty, | |
439 // we need to do some token merging. | |
440 $finalTokens[($x + 1)]['type'] = 'T_URL'; | |
441 $finalTokens[($x + 1)]['code'] = T_URL; | |
442 | |
443 if ($newContent !== '') { | |
444 $finalTokens[($x + 1)]['content'] .= $newContent; | |
445 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
446 $content = Util\Common::prepareForOutput($finalTokens[($x + 1)]['content']); | |
447 echo "\t\t=> token content changed to: $content".PHP_EOL; | |
448 } | |
449 } | |
450 } else if ($finalTokens[$stackPtr]['content'][0] === '-' | |
451 && $finalTokens[($stackPtr + 1)]['code'] === T_STRING | |
452 ) { | |
453 if (isset($finalTokens[($stackPtr - 1)]) === true | |
454 && $finalTokens[($stackPtr - 1)]['code'] === T_STRING | |
455 ) { | |
456 $newContent = $finalTokens[($stackPtr - 1)]['content'].$finalTokens[$stackPtr]['content'].$finalTokens[($stackPtr + 1)]['content']; | |
457 | |
458 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
459 echo "\t\t* token is a string joiner; ignoring this and previous token".PHP_EOL; | |
460 $old = Util\Common::prepareForOutput($finalTokens[($stackPtr + 1)]['content']); | |
461 $new = Util\Common::prepareForOutput($newContent); | |
462 echo "\t\t=> token ".($stackPtr + 1)." content changed from \"$old\" to \"$new\"".PHP_EOL; | |
463 } | |
464 | |
465 $finalTokens[($stackPtr + 1)]['content'] = $newContent; | |
466 unset($finalTokens[$stackPtr]); | |
467 unset($finalTokens[($stackPtr - 1)]); | |
468 } else { | |
469 $newContent = $finalTokens[$stackPtr]['content'].$finalTokens[($stackPtr + 1)]['content']; | |
470 | |
471 $finalTokens[($stackPtr + 1)]['content'] = $newContent; | |
472 unset($finalTokens[$stackPtr]); | |
473 } | |
474 }//end if | |
475 break; | |
476 case T_ASPERAND: | |
477 $asperandStart = true; | |
478 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
479 echo "\t\t* at-rule definition opened *".PHP_EOL; | |
480 } | |
481 break; | |
482 default: | |
483 // Nothing special to be done with this token. | |
484 break; | |
485 }//end switch | |
486 }//end for | |
487 | |
488 // Reset the array keys to avoid gaps. | |
489 $finalTokens = array_values($finalTokens); | |
490 $numTokens = count($finalTokens); | |
491 | |
492 // Blank out the content of the end tag. | |
493 $finalTokens[($numTokens - 1)]['content'] = ''; | |
494 | |
495 if ($eolAdded === true) { | |
496 // Strip off the extra EOL char we added for tokenizing. | |
497 $finalTokens[($numTokens - 2)]['content'] = substr( | |
498 $finalTokens[($numTokens - 2)]['content'], | |
499 0, | |
500 (strlen($this->eolChar) * -1) | |
501 ); | |
502 | |
503 if ($finalTokens[($numTokens - 2)]['content'] === '') { | |
504 unset($finalTokens[($numTokens - 2)]); | |
505 $finalTokens = array_values($finalTokens); | |
506 $numTokens = count($finalTokens); | |
507 } | |
508 } | |
509 | |
510 if (PHP_CODESNIFFER_VERBOSITY > 1) { | |
511 echo "\t*** END CSS TOKENIZING 2ND PASS ***".PHP_EOL; | |
512 } | |
513 | |
514 return $finalTokens; | |
515 | |
516 }//end tokenize() | |
517 | |
518 | |
519 /** | |
520 * Performs additional processing after main tokenizing. | |
521 * | |
522 * @return void | |
523 */ | |
524 public function processAdditional() | |
525 { | |
526 /* | |
527 We override this method because we don't want the PHP version to | |
528 run during CSS processing because it is wasted processing time. | |
529 */ | |
530 | |
531 }//end processAdditional() | |
532 | |
533 | |
534 }//end class |