PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 5.8.4
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v5.8.4
7.2.2 7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 All 37 releases
mlsimport / vendor / phpcsstandards / phpcsutils / PHPCSUtils / AbstractSniffs / AbstractArrayDeclarationSniff.php

AbstractArrayDeclarationSniff.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 5.8.4, at vendor/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php

552 lines 20.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4 *
5 * @package PHPCSUtils
6 * @copyright 2019-2020 PHPCSUtils Contributors
7 * @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8 * @link https://github.com/PHPCSStandards/PHPCSUtils
9 */
10
11 namespace PHPCSUtils\AbstractSniffs;
12
13 use PHP_CodeSniffer\Exceptions\RuntimeException;
14 use PHP_CodeSniffer\Files\File;
15 use PHP_CodeSniffer\Sniffs\Sniff;
16 use PHP_CodeSniffer\Util\Tokens;
17 use PHPCSUtils\Tokens\Collections;
18 use PHPCSUtils\Utils\Arrays;
19 use PHPCSUtils\Utils\Numbers;
20 use PHPCSUtils\Utils\PassedParameters;
21 use PHPCSUtils\Utils\TextStrings;
22
23 /**
24 * Abstract sniff to easily examine all parts of an array declaration.
25 *
26 * @since 1.0.0
27 */
28 abstract class AbstractArrayDeclarationSniff implements Sniff
29 {
30
31 /**
32 * The stack pointer to the array keyword or the short array open token.
33 *
34 * @since 1.0.0
35 *
36 * @var int
37 */
38 protected $stackPtr;
39
40 /**
41 * The token stack for the current file being examined.
42 *
43 * @since 1.0.0
44 *
45 * @var array<int, array<string, mixed>>
46 */
47 protected $tokens;
48
49 /**
50 * The stack pointer to the array opener.
51 *
52 * @since 1.0.0
53 *
54 * @var int
55 */
56 protected $arrayOpener;
57
58 /**
59 * The stack pointer to the array closer.
60 *
61 * @since 1.0.0
62 *
63 * @var int
64 */
65 protected $arrayCloser;
66
67 /**
68 * A multi-dimentional array with information on each array item.
69 *
70 * The array index is 1-based and contains the following information on each array item:
71 * ```php
72 * 1 => array(
73 * 'start' => int, // The stack pointer to the first token in the array item.
74 * 'end' => int, // The stack pointer to the last token in the array item.
75 * 'raw' => string, // A string with the contents of all tokens between `start` and `end`.
76 * 'clean' => string, // Same as `raw`, but all comment tokens have been stripped out.
77 * )
78 * ```
79 *
80 * @since 1.0.0
81 *
82 * @var array<int, array<string, int|string>>
83 */
84 protected $arrayItems;
85
86 /**
87 * How many items are in the array.
88 *
89 * @since 1.0.0
90 *
91 * @var int
92 */
93 protected $itemCount = 0;
94
95 /**
96 * Whether or not the array is single line.
97 *
98 * @since 1.0.0
99 *
100 * @var bool
101 */
102 protected $singleLine;
103
104 /**
105 * List of tokens which can safely be used with an eval() expression.
106 *
107 * This list gets enhanced with additional token groups in the constructor.
108 *
109 * @since 1.0.0
110 *
111 * @var array<int|string, int|string>
112 */
113 private $acceptedTokens = [
114 \T_NULL => \T_NULL,
115 \T_TRUE => \T_TRUE,
116 \T_FALSE => \T_FALSE,
117 \T_LNUMBER => \T_LNUMBER,
118 \T_DNUMBER => \T_DNUMBER,
119 \T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
120 \T_STRING_CONCAT => \T_STRING_CONCAT,
121 \T_INLINE_THEN => \T_INLINE_THEN,
122 \T_INLINE_ELSE => \T_INLINE_ELSE,
123 \T_BOOLEAN_NOT => \T_BOOLEAN_NOT,
124 ];
125
126 /**
127 * Set up this class.
128 *
129 * @since 1.0.0
130 *
131 * @codeCoverageIgnore
132 *
133 * @return void
134 */
135 final public function __construct()
136 {
137 // Enhance the list of accepted tokens.
138 $this->acceptedTokens += Tokens::$assignmentTokens;
139 $this->acceptedTokens += Tokens::$comparisonTokens;
140 $this->acceptedTokens += Tokens::$arithmeticTokens;
141 $this->acceptedTokens += Tokens::$operators;
142 $this->acceptedTokens += Tokens::$booleanOperators;
143 $this->acceptedTokens += Tokens::$castTokens;
144 $this->acceptedTokens += Tokens::$bracketTokens;
145 $this->acceptedTokens += Tokens::$heredocTokens;
146 }
147
148 /**
149 * Returns an array of tokens this test wants to listen for.
150 *
151 * @since 1.0.0
152 *
153 * @codeCoverageIgnore
154 *
155 * @return array<int|string>
156 */
157 public function register()
158 {
159 return Collections::arrayOpenTokensBC();
160 }
161
162 /**
163 * Processes this test when one of its tokens is encountered.
164 *
165 * This method fills the properties with relevant information for examining the array
166 * and then passes off to the {@see AbstractArrayDeclarationSniff::processArray()} method.
167 *
168 * @since 1.0.0
169 *
170 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
171 * token was found.
172 * @param int $stackPtr The position in the PHP_CodeSniffer
173 * file's token stack where the token
174 * was found.
175 *
176 * @return void
177 */
178 final public function process(File $phpcsFile, $stackPtr)
179 {
180 try {
181 $this->arrayItems = PassedParameters::getParameters($phpcsFile, $stackPtr);
182 } catch (RuntimeException $e) {
183 // Parse error, short list, real square open bracket or incorrectly tokenized short array token.
184 return;
185 }
186
187 $openClose = Arrays::getOpenClose($phpcsFile, $stackPtr, true);
188 if ($openClose === false) {
189 // Parse error or live coding.
190 return;
191 }
192
193 $this->stackPtr = $stackPtr;
194 $this->tokens = $phpcsFile->getTokens();
195 $this->arrayOpener = $openClose['opener'];
196 $this->arrayCloser = $openClose['closer'];
197 $this->itemCount = \count($this->arrayItems);
198
199 $this->singleLine = true;
200 if ($this->tokens[$openClose['opener']]['line'] !== $this->tokens[$openClose['closer']]['line']) {
201 $this->singleLine = false;
202 }
203
204 $this->processArray($phpcsFile);
205
206 // Reset select properties between calls to this sniff to lower memory usage.
207 $this->tokens = [];
208 $this->arrayItems = [];
209 }
210
211 /**
212 * Process every part of the array declaration.
213 *
214 * Controller which calls the individual `process...()` methods for each part of the array.
215 *
216 * The method starts by calling the {@see AbstractArrayDeclarationSniff::processOpenClose()} method
217 * and subsequently calls the following methods for each array item:
218 *
219 * Unkeyed arrays | Keyed arrays
220 * -------------- | ------------
221 * processNoKey() | processKey()
222 * - | processArrow()
223 * processValue() | processValue()
224 * processComma() | processComma()
225 *
226 * This is the default logic for the sniff, but can be overloaded in a concrete child class
227 * if needed.
228 *
229 * @since 1.0.0
230 *
231 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
232 * token was found.
233 *
234 * @return void
235 */
236 public function processArray(File $phpcsFile)
237 {
238 if ($this->processOpenClose($phpcsFile, $this->arrayOpener, $this->arrayCloser) === true) {
239 return;
240 }
241
242 if ($this->itemCount === 0) {
243 return;
244 }
245
246 foreach ($this->arrayItems as $itemNr => $arrayItem) {
247 try {
248 $arrowPtr = Arrays::getDoubleArrowPtr($phpcsFile, $arrayItem['start'], $arrayItem['end']);
249 } catch (RuntimeException $e) {
250 // Parse error: empty array item. Ignore.
251 continue;
252 }
253
254 if ($arrowPtr !== false) {
255 if ($this->processKey($phpcsFile, $arrayItem['start'], ($arrowPtr - 1), $itemNr) === true) {
256 return;
257 }
258
259 if ($this->processArrow($phpcsFile, $arrowPtr, $itemNr) === true) {
260 return;
261 }
262
263 if ($this->processValue($phpcsFile, ($arrowPtr + 1), $arrayItem['end'], $itemNr) === true) {
264 return;
265 }
266 } else {
267 if ($this->processNoKey($phpcsFile, $arrayItem['start'], $itemNr) === true) {
268 return;
269 }
270
271 if ($this->processValue($phpcsFile, $arrayItem['start'], $arrayItem['end'], $itemNr) === true) {
272 return;
273 }
274 }
275
276 $commaPtr = ($arrayItem['end'] + 1);
277 if ($itemNr < $this->itemCount || $this->tokens[$commaPtr]['code'] === \T_COMMA) {
278 if ($this->processComma($phpcsFile, $commaPtr, $itemNr) === true) {
279 return;
280 }
281 }
282 }
283 }
284
285 /**
286 * Process the array opener and closer.
287 *
288 * Optional method to be implemented in concrete child classes. By default, this method does nothing.
289 *
290 * @since 1.0.0
291 *
292 * @codeCoverageIgnore
293 *
294 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
295 * token was found.
296 * @param int $openPtr The position of the array opener token in the token stack.
297 * @param int $closePtr The position of the array closer token in the token stack.
298 *
299 * @return true|void Returning `TRUE` will short-circuit the sniff and stop processing.
300 * In effect, this means that the sniff will not examine the individual
301 * array items if `TRUE` is returned.
302 */
303 public function processOpenClose(File $phpcsFile, $openPtr, $closePtr)
304 {
305 }
306
307 /**
308 * Process the tokens in an array key.
309 *
310 * Optional method to be implemented in concrete child classes. By default, this method does nothing.
311 *
312 * Note: The `$startPtr` and `$endPtr` do not discount whitespace or comments, but are all inclusive
313 * to allow for examining all tokens in an array key.
314 *
315 * @since 1.0.0
316 *
317 * @codeCoverageIgnore
318 *
319 * @see \PHPCSUtils\AbstractSniffs\AbstractArrayDeclarationSniff::getActualArrayKey() Optional helper function.
320 *
321 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
322 * token was found.
323 * @param int $startPtr The stack pointer to the first token in the "key" part of
324 * an array item.
325 * @param int $endPtr The stack pointer to the last token in the "key" part of
326 * an array item.
327 * @param int $itemNr Which item in the array is being handled.
328 * 1-based, i.e. the first item is item 1, the second 2 etc.
329 *
330 * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
331 * In effect, this means that the sniff will not examine the double arrow, the array
332 * value or comma for this array item and will not process any array items after this one.
333 */
334 public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr)
335 {
336 }
337
338 /**
339 * Process an array item without an array key.
340 *
341 * Optional method to be implemented in concrete child classes. By default, this method does nothing.
342 *
343 * Note: This method is _not_ intended for processing the array _value_. Use the
344 * {@see AbstractArrayDeclarationSniff::processValue()} method to implement processing of the array value.
345 *
346 * @since 1.0.0
347 *
348 * @codeCoverageIgnore
349 *
350 * @see \PHPCSUtils\AbstractSniffs\AbstractArrayDeclarationSniff::processValue() Method to process the array value.
351 *
352 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
353 * token was found.
354 * @param int $startPtr The stack pointer to the first token in the array item,
355 * which in this case will be the first token of the array
356 * value part of the array item.
357 * @param int $itemNr Which item in the array is being handled.
358 * 1-based, i.e. the first item is item 1, the second 2 etc.
359 *
360 * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
361 * In effect, this means that the sniff will not examine the array value or
362 * comma for this array item and will not process any array items after this one.
363 */
364 public function processNoKey(File $phpcsFile, $startPtr, $itemNr)
365 {
366 }
367
368 /**
369 * Process the double arrow.
370 *
371 * Optional method to be implemented in concrete child classes. By default, this method does nothing.
372 *
373 * @since 1.0.0
374 *
375 * @codeCoverageIgnore
376 *
377 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
378 * token was found.
379 * @param int $arrowPtr The stack pointer to the double arrow for the array item.
380 * @param int $itemNr Which item in the array is being handled.
381 * 1-based, i.e. the first item is item 1, the second 2 etc.
382 *
383 * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
384 * In effect, this means that the sniff will not examine the array value or
385 * comma for this array item and will not process any array items after this one.
386 */
387 public function processArrow(File $phpcsFile, $arrowPtr, $itemNr)
388 {
389 }
390
391 /**
392 * Process the tokens in an array value.
393 *
394 * Optional method to be implemented in concrete child classes. By default, this method does nothing.
395 *
396 * Note: The `$startPtr` and `$endPtr` do not discount whitespace or comments, but are all inclusive
397 * to allow for examining all tokens in an array value.
398 *
399 * @since 1.0.0
400 *
401 * @codeCoverageIgnore
402 *
403 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
404 * token was found.
405 * @param int $startPtr The stack pointer to the first token in the "value" part of
406 * an array item.
407 * @param int $endPtr The stack pointer to the last token in the "value" part of
408 * an array item.
409 * @param int $itemNr Which item in the array is being handled.
410 * 1-based, i.e. the first item is item 1, the second 2 etc.
411 *
412 * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
413 * In effect, this means that the sniff will not examine the comma for this
414 * array item and will not process any array items after this one.
415 */
416 public function processValue(File $phpcsFile, $startPtr, $endPtr, $itemNr)
417 {
418 }
419
420 /**
421 * Process the comma after an array item.
422 *
423 * Optional method to be implemented in concrete child classes. By default, this method does nothing.
424 *
425 * @since 1.0.0
426 *
427 * @codeCoverageIgnore
428 *
429 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
430 * token was found.
431 * @param int $commaPtr The stack pointer to the comma.
432 * @param int $itemNr Which item in the array is being handled.
433 * 1-based, i.e. the first item is item 1, the second 2 etc.
434 *
435 * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing.
436 * In effect, this means that the sniff will not process any array items
437 * after this one.
438 */
439 public function processComma(File $phpcsFile, $commaPtr, $itemNr)
440 {
441 }
442
443 /**
444 * Determine what the actual array key would be.
445 *
446 * Helper function for processsing array keys in the processKey() function.
447 * Using this method is up to the sniff implementation in the child class.
448 *
449 * @since 1.0.0
450 *
451 * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
452 * token was found.
453 * @param int $startPtr The stack pointer to the first token in the "key" part of
454 * an array item.
455 * @param int $endPtr The stack pointer to the last token in the "key" part of
456 * an array item.
457 *
458 * @return string|int|void The string or integer array key or void if the array key could not
459 * reliably be determined.
460 */
461 public function getActualArrayKey(File $phpcsFile, $startPtr, $endPtr)
462 {
463 /*
464 * Determine the value of the key.
465 */
466 $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true);
467 $lastNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, $endPtr, null, true);
468
469 $content = '';
470
471 for ($i = $firstNonEmpty; $i <= $lastNonEmpty; $i++) {
472 if (isset(Tokens::$commentTokens[$this->tokens[$i]['code']]) === true) {
473 continue;
474 }
475
476 if ($this->tokens[$i]['code'] === \T_WHITESPACE) {
477 $content .= ' ';
478 continue;
479 }
480
481 if (isset($this->acceptedTokens[$this->tokens[$i]['code']]) === false) {
482 // This is not a key we can evaluate. Might be a variable or constant.
483 return;
484 }
485
486 // Take PHP 7.4 numeric literal separators into account.
487 if ($this->tokens[$i]['code'] === \T_LNUMBER || $this->tokens[$i]['code'] === \T_DNUMBER) {
488 $number = Numbers::getCompleteNumber($phpcsFile, $i);
489 $content .= $number['content'];
490 $i = $number['last_token'];
491 continue;
492 }
493
494 // Account for heredoc with vars.
495 if ($this->tokens[$i]['code'] === \T_START_HEREDOC) {
496 $text = TextStrings::getCompleteTextString($phpcsFile, $i);
497
498 // Check if there's a variable in the heredoc.
499 if ($text !== TextStrings::stripEmbeds($text)) {
500 return;
501 }
502
503 for ($j = $i; $j <= $this->tokens[$i]['scope_closer']; $j++) {
504 $content .= $this->tokens[$j]['content'];
505 }
506
507 $i = $this->tokens[$i]['scope_closer'];
508 continue;
509 }
510
511 $content .= $this->tokens[$i]['content'];
512 }
513
514 // The PHP_EOL is to prevent getting parse errors when the key is a heredoc/nowdoc.
515 $key = eval('return ' . $content . ';' . \PHP_EOL);
516
517 /*
518 * Ok, so now we know the base value of the key, let's determine whether it is
519 * an acceptable index key for an array and if not, what it would turn into.
520 */
521
522 switch (\gettype($key)) {
523 case 'NULL':
524 // An array key of `null` will become an empty string.
525 return '';
526
527 case 'boolean':
528 return ($key === true) ? 1 : 0;
529
530 case 'integer':
531 return $key;
532
533 case 'double':
534 return (int) $key; // Will automatically cut off the decimal part.
535
536 case 'string':
537 if (Numbers::isDecimalInt($key) === true) {
538 return (int) $key;
539 }
540
541 return $key;
542
543 default:
544 /*
545 * Shouldn't be possible. Either way, if it's not one of the above types,
546 * this is not a key we can handle.
547 */
548 return; // @codeCoverageIgnore
549 }
550 }
551 }
552