|
3 | 3 | namespace SlevomatCodingStandard\Helpers; |
4 | 4 |
|
5 | 5 | use PHP_CodeSniffer\Files\File; |
| 6 | +use function array_key_exists; |
6 | 7 | use function arsort; |
7 | | -use function end; |
8 | 8 | use function in_array; |
9 | 9 | use function key; |
10 | | -use function min; |
11 | | -use function strlen; |
12 | 10 | use function strnatcasecmp; |
13 | | -use function strpos; |
14 | 11 | use const T_COMMA; |
15 | 12 | use const T_OPEN_SHORT_ARRAY; |
16 | | -use const T_WHITESPACE; |
17 | 13 |
|
18 | 14 | /** |
19 | 15 | * @internal |
20 | 16 | */ |
21 | 17 | class ArrayHelper |
22 | 18 | { |
23 | 19 |
|
24 | | - /** @var array<int, array<string, array<int, int|string>|int|string>> */ |
25 | | - protected static $tokens; |
26 | | - |
27 | 20 | /** |
28 | 21 | * @return list<ArrayKeyValue> |
29 | 22 | */ |
30 | | - public static function parse(File $phpcsFile, int $pointer): array |
| 23 | + public static function parse(File $phpcsFile, int $arrayPointer): array |
31 | 24 | { |
32 | | - self::$tokens = $phpcsFile->getTokens(); |
33 | | - $token = self::$tokens[$pointer]; |
34 | | - [$pointerOpener, $pointerCloser] = self::openClosePointers($token); |
35 | | - $tokenOpener = self::$tokens[$pointerOpener]; |
36 | | - $lineIndents = ['']; |
| 25 | + $tokens = $phpcsFile->getTokens(); |
| 26 | + |
| 27 | + $arrayToken = $tokens[$arrayPointer]; |
| 28 | + [$arrayOpenerPointer, $arrayCloserPointer] = self::openClosePointers($arrayToken); |
| 29 | + |
37 | 30 | $keyValues = []; |
38 | | - $skipUntilPointer = null; |
39 | | - $tokenEnd = null; |
40 | | - $pointer = $pointerOpener + 1; |
41 | | - for (; $pointer < $pointerCloser; $pointer++) { |
42 | | - $token = self::$tokens[$pointer]; |
43 | | - if ($token['line'] > $tokenOpener['line'] || in_array($token['code'], TokenHelper::$ineffectiveTokenCodes, true) === false) { |
44 | | - break; |
45 | | - } |
46 | | - } |
47 | | - $pointerStart = $pointer; |
48 | 31 |
|
49 | | - for (; $pointer < $pointerCloser; $pointer++) { |
50 | | - if ($pointer < $skipUntilPointer) { |
51 | | - continue; |
52 | | - } |
| 32 | + $firstPointerOnNextLine = TokenHelper::findFirstTokenOnNextLine($phpcsFile, $arrayOpenerPointer + 1); |
| 33 | + $firstEffectivePointer = TokenHelper::findNextEffective($phpcsFile, $arrayOpenerPointer + 1); |
53 | 34 |
|
54 | | - $token = self::$tokens[$pointer]; |
| 35 | + $arrayKeyValueStartPointer = $firstPointerOnNextLine !== null && $firstPointerOnNextLine < $firstEffectivePointer |
| 36 | + ? $firstPointerOnNextLine |
| 37 | + : $firstEffectivePointer; |
| 38 | + $arrayKeyValueEndPointer = $arrayKeyValueStartPointer; |
| 39 | + |
| 40 | + $indentation = $tokens[$arrayOpenerPointer]['line'] < $tokens[$firstEffectivePointer]['line'] |
| 41 | + ? IndentationHelper::getIndentation($phpcsFile, $firstEffectivePointer) |
| 42 | + : ''; |
| 43 | + |
| 44 | + for ($i = $arrayKeyValueStartPointer; $i < $arrayCloserPointer; $i++) { |
| 45 | + $token = $tokens[$i]; |
55 | 46 |
|
56 | 47 | if (in_array($token['code'], TokenHelper::$arrayTokenCodes, true)) { |
57 | | - $pointerCloserWalk = self::openClosePointers($token)[1]; |
58 | | - $skipUntilPointer = $pointerCloserWalk; |
| 48 | + $i = self::openClosePointers($token)[1]; |
59 | 49 | continue; |
60 | 50 | } |
61 | | - if (isset($token['scope_closer']) && $token['scope_closer'] > $pointer) { |
62 | | - $skipUntilPointer = $token['scope_closer']; |
| 51 | + |
| 52 | + if (array_key_exists('scope_closer', $token) && $token['scope_closer'] > $i) { |
| 53 | + $i = $token['scope_closer'] - 1; |
63 | 54 | continue; |
64 | 55 | } |
65 | | - if (isset($token['parenthesis_closer'])) { |
66 | | - $skipUntilPointer = $token['parenthesis_closer']; |
| 56 | + |
| 57 | + if (array_key_exists('parenthesis_closer', $token) && $token['parenthesis_closer'] > $i) { |
| 58 | + $i = $token['parenthesis_closer'] - 1; |
67 | 59 | continue; |
68 | 60 | } |
69 | | - $nextEffective = in_array($token['code'], TokenHelper::$ineffectiveTokenCodes, true) |
70 | | - ? TokenHelper::findNextEffective($phpcsFile, $pointer) |
71 | | - : TokenHelper::findNextEffective($phpcsFile, $pointer + 1); |
72 | | - if ( |
73 | | - isset($lineIndents[$token['line']]) === false |
74 | | - && in_array($token['code'], TokenHelper::$ineffectiveTokenCodes, true) === false |
75 | | - ) { |
76 | | - $firstPointerOnLine = TokenHelper::findFirstTokenOnLine($phpcsFile, $pointer); |
77 | | - $firstEffective = TokenHelper::findNextEffective($phpcsFile, $firstPointerOnLine); |
78 | | - $lineIndents[$token['line']] = TokenHelper::getContent($phpcsFile, $firstPointerOnLine, $firstEffective - 1); |
79 | | - } |
80 | 61 |
|
81 | | - $startNewKeyValue = $tokenEnd !== null |
82 | | - ? self::parseTestStartKeyVal($pointer, $tokenEnd, end($lineIndents)) |
83 | | - : false; |
84 | | - if ($startNewKeyValue) { |
85 | | - if ($nextEffective === $pointerCloser && in_array($token['code'], TokenHelper::$ineffectiveTokenCodes, true)) { |
86 | | - // there are no more key/values |
87 | | - $firstPointerOnLine = TokenHelper::findFirstTokenOnLine($phpcsFile, $pointer); |
88 | | - if ($pointer === $firstPointerOnLine) { |
89 | | - // end last key/value on the prev token |
90 | | - $pointer--; |
91 | | - } |
92 | | - break; |
93 | | - } |
94 | | - $startNewKeyValue = false; |
95 | | - $tokenEnd = null; |
96 | | - $keyValues[] = new ArrayKeyValue($phpcsFile, $pointerStart, $pointer - 1); |
97 | | - $pointerStart = $pointer; |
| 62 | + $nextEffectivePointer = TokenHelper::findNextEffective($phpcsFile, $i + 1); |
| 63 | + |
| 64 | + if ($nextEffectivePointer === $arrayCloserPointer) { |
| 65 | + $arrayKeyValueEndPointer = $i; |
| 66 | + break; |
98 | 67 | } |
99 | | - if ($token['code'] === T_COMMA || $tokenEnd !== null) { |
100 | | - $tokenEnd = $token; |
| 68 | + |
| 69 | + if ($token['code'] !== T_COMMA || !ScopeHelper::isInSameScope($phpcsFile, $arrayOpenerPointer, $i)) { |
| 70 | + continue; |
101 | 71 | } |
| 72 | + |
| 73 | + $arrayKeyValueEndPointer = $tokens[$nextEffectivePointer]['line'] === $tokens[$i]['line'] |
| 74 | + ? $nextEffectivePointer - 1 |
| 75 | + : self::getValueEndPointer($phpcsFile, $i, $indentation); |
| 76 | + |
| 77 | + $keyValues[] = new ArrayKeyValue($phpcsFile, $arrayKeyValueStartPointer, $arrayKeyValueEndPointer); |
| 78 | + |
| 79 | + $arrayKeyValueStartPointer = $arrayKeyValueEndPointer + 1; |
| 80 | + $i = $arrayKeyValueEndPointer; |
102 | 81 | } |
103 | 82 |
|
104 | | - $pointer = min($pointer, $pointerCloser - 1); |
105 | | - $keyValues[] = new ArrayKeyValue($phpcsFile, $pointerStart, $pointer); |
| 83 | + $keyValues[] = new ArrayKeyValue( |
| 84 | + $phpcsFile, |
| 85 | + $arrayKeyValueStartPointer, |
| 86 | + self::getValueEndPointer($phpcsFile, $arrayKeyValueEndPointer, $indentation) |
| 87 | + ); |
106 | 88 |
|
107 | | - self::$tokens = []; |
108 | 89 | return $keyValues; |
109 | 90 | } |
110 | 91 |
|
@@ -219,45 +200,34 @@ public static function openClosePointers(array $token): array |
219 | 200 | return [(int) $pointerOpener, (int) $pointerCloser]; |
220 | 201 | } |
221 | 202 |
|
222 | | - /** |
223 | | - * Test whether we should begin collecting the next key/value tokens |
224 | | - * |
225 | | - * @param array<string, array<int, int|string>|int|string> $tokenPrev |
226 | | - */ |
227 | | - private static function parseTestStartKeyVal(int $pointer, array $tokenPrev, string $lastIndent): bool |
| 203 | + private static function getValueEndPointer(File $phpcsFile, int $endPointer, string $indentation): int |
228 | 204 | { |
229 | | - $token = self::$tokens[$pointer]; |
230 | | - |
231 | | - // token['column'] cannot be relied on if tabs are being used |
232 | | - // this simply checks if indent contains tab and is the same as the prev indent plus additional |
233 | | - $testTabIndent = static function ($indent, $indentPrev) { |
234 | | - return strpos($indent, "\t") !== false |
235 | | - && strpos($indent, $indentPrev) === 0 |
236 | | - && strlen($indent) > strlen($indentPrev); |
237 | | - }; |
238 | | - |
239 | | - $startNew = true; |
240 | | - |
241 | | - if ( |
242 | | - in_array($token['code'], TokenHelper::$ineffectiveTokenCodes, true) |
243 | | - && $token['line'] === $tokenPrev['line'] |
244 | | - ) { |
245 | | - // we're whitespace or comment after the value... |
246 | | - $startNew = false; |
247 | | - } elseif ( |
248 | | - $token['code'] === T_WHITESPACE |
249 | | - && in_array($tokenPrev['code'], TokenHelper::$inlineCommentTokenCodes, true) |
250 | | - && in_array(self::$tokens[$pointer + 1]['code'], TokenHelper::$inlineCommentTokenCodes, true) |
251 | | - && (self::$tokens[$pointer + 1]['column'] >= $tokenPrev['column'] |
252 | | - || $testTabIndent($token['content'], $lastIndent) |
253 | | - ) |
254 | | - ) { |
255 | | - // 'key' => 'value' // tokenPrev is this comment |
256 | | - // // we're in the preceding whitespace |
257 | | - $startNew = false; |
| 205 | + $tokens = $phpcsFile->getTokens(); |
| 206 | + |
| 207 | + $nextEffectivePointer = TokenHelper::findNextEffective($phpcsFile, $endPointer + 1); |
| 208 | + |
| 209 | + for ($i = $endPointer + 1; $i < $nextEffectivePointer; $i++) { |
| 210 | + if ($tokens[$i]['line'] === $tokens[$endPointer]['line']) { |
| 211 | + $endPointer = $i; |
| 212 | + continue; |
| 213 | + } |
| 214 | + |
| 215 | + $nextNonWhitespacePointer = TokenHelper::findNextNonWhitespace($phpcsFile, $i); |
| 216 | + |
| 217 | + if (!in_array($tokens[$nextNonWhitespacePointer]['code'], TokenHelper::$inlineCommentTokenCodes, true)) { |
| 218 | + break; |
| 219 | + } |
| 220 | + |
| 221 | + if ($indentation === IndentationHelper::getIndentation($phpcsFile, $nextNonWhitespacePointer)) { |
| 222 | + $endPointer = $i - 1; |
| 223 | + break; |
| 224 | + } |
| 225 | + |
| 226 | + $i = TokenHelper::findLastTokenOnLine($phpcsFile, $i); |
| 227 | + $endPointer = $i; |
258 | 228 | } |
259 | 229 |
|
260 | | - return $startNew; |
| 230 | + return $endPointer; |
261 | 231 | } |
262 | 232 |
|
263 | 233 | } |
0 commit comments