Skip to content

Commit e438508

Browse files
authored
Merge pull request #57 from PHPCSStandards/feature/arrays-new-getopenclose-utility
Utils\Arrays: add new getOpenClose() utility method
2 parents 01bc6e0 + 8d49d1b commit e438508

File tree

3 files changed

+258
-0
lines changed

3 files changed

+258
-0
lines changed

PHPCSUtils/Utils/Arrays.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,68 @@ public static function isShortArray(File $phpcsFile, $stackPtr)
153153
// In all other circumstances, make sure this isn't a short list instead of a short array.
154154
return (Lists::isShortList($phpcsFile, $stackPtr) === false);
155155
}
156+
157+
/**
158+
* Find the array opener & closer based on a T_ARRAY or T_OPEN_SHORT_ARRAY token.
159+
*
160+
* This method also accepts `T_OPEN_SQUARE_BRACKET` tokens to allow it to be
161+
* PHPCS cross-version compatible as the short array tokenizing has been plagued by
162+
* a number of bugs over time, which affects the short array determination.
163+
*
164+
* @since 1.0.0
165+
*
166+
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
167+
* @param int $stackPtr The position of the T_ARRAY or T_OPEN_SHORT_ARRAY
168+
* token in the stack.
169+
* @param true|null $isShortArray Short-circuit the short array check for T_OPEN_SHORT_ARRAY
170+
* tokens if it isn't necessary.
171+
* Efficiency tweak for when this has already been established,
172+
* i.e. when encountering a nested array while walking the
173+
* tokens in an array.
174+
* Use with care.
175+
*
176+
* @return array|false Array with two keys `opener`, `closer` or false if
177+
* not a (short) array token or if the opener/closer
178+
* could not be determined.
179+
*/
180+
public static function getOpenClose(File $phpcsFile, $stackPtr, $isShortArray = null)
181+
{
182+
$tokens = $phpcsFile->getTokens();
183+
184+
// Is this one of the tokens this function handles ?
185+
if (isset($tokens[$stackPtr]) === false
186+
|| isset(Collections::$arrayTokensBC[$tokens[$stackPtr]['code']]) === false
187+
) {
188+
return false;
189+
}
190+
191+
switch ($tokens[$stackPtr]['code']) {
192+
case \T_ARRAY:
193+
if (isset($tokens[$stackPtr]['parenthesis_opener'])) {
194+
$opener = $tokens[$stackPtr]['parenthesis_opener'];
195+
196+
if (isset($tokens[$opener]['parenthesis_closer'])) {
197+
$closer = $tokens[$opener]['parenthesis_closer'];
198+
}
199+
}
200+
break;
201+
202+
case \T_OPEN_SHORT_ARRAY:
203+
case \T_OPEN_SQUARE_BRACKET:
204+
if ($isShortArray === true || self::isShortArray($phpcsFile, $stackPtr) === true) {
205+
$opener = $stackPtr;
206+
$closer = $tokens[$stackPtr]['bracket_closer'];
207+
}
208+
break;
209+
}
210+
211+
if (isset($opener, $closer)) {
212+
return [
213+
'opener' => $opener,
214+
'closer' => $closer,
215+
];
216+
}
217+
218+
return false;
219+
}
156220
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/* testShortList */
4+
['foo' => $bar] = $array;
5+
6+
/* testArrayAccess */
7+
echo $array['index'];
8+
9+
/* testLongArray */
10+
$array = array($a, /* testNestedLongArray */ array ( $b ));
11+
12+
/* testShortArray */
13+
$array = [$a, /* testNestedShortArray */ [$b]];
14+
15+
/* testArrayWithCommentsAndAnnotations */
16+
$array = array // Comment.
17+
(
18+
0 => $a,
19+
/* phpcs:ignore Stnd.Cat.Sniff -- For reasons. */
20+
1 => $b,
21+
);
22+
23+
/* testParseError */
24+
// Intentional parse error. This has to be the last test in the file.
25+
array( $a
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<?php
2+
/**
3+
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4+
*
5+
* @package PHPCSUtils
6+
* @copyright 2019 PHPCSUtils Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSUtils
9+
*/
10+
11+
namespace PHPCSUtils\Tests\Utils\Arrays;
12+
13+
use PHPCSUtils\TestUtils\UtilityMethodTestCase;
14+
use PHPCSUtils\Utils\Arrays;
15+
16+
/**
17+
* Tests for the \PHPCSUtils\Utils\Arrays::getOpenClose() method.
18+
*
19+
* @covers \PHPCSUtils\Utils\Arrays::getOpenClose
20+
*
21+
* @group arrays
22+
*
23+
* @since 1.0.0
24+
*/
25+
class GetOpenCloseTest extends UtilityMethodTestCase
26+
{
27+
28+
/**
29+
* Test passing a non-existent token pointer.
30+
*
31+
* @return void
32+
*/
33+
public function testNonExistentToken()
34+
{
35+
$this->assertFalse(Arrays::getOpenClose(self::$phpcsFile, 100000));
36+
}
37+
38+
/**
39+
* Test that false is returned when a non-(short) array token is passed.
40+
*
41+
* @dataProvider dataNotArrayToken
42+
*
43+
* @param string $testMarker The comment which prefaces the target token in the test file.
44+
*
45+
* @return void
46+
*/
47+
public function testNotArrayToken($testMarker)
48+
{
49+
$target = $this->getTargetToken($testMarker, [\T_OPEN_SHORT_ARRAY, \T_OPEN_SQUARE_BRACKET]);
50+
$this->assertFalse(Arrays::getOpenClose(self::$phpcsFile, $target));
51+
}
52+
53+
/**
54+
* Data provider.
55+
*
56+
* @see testNotArrayToken() For the array format.
57+
*
58+
* @return array
59+
*/
60+
public function dataNotArrayToken()
61+
{
62+
return [
63+
'short-list' => ['/* testShortList */'],
64+
'array-access-square-bracket' => ['/* testArrayAccess */'],
65+
];
66+
}
67+
68+
/**
69+
* Test retrieving the open/close tokens for an array.
70+
*
71+
* @dataProvider dataGetOpenClose
72+
*
73+
* @param string $testMarker The comment which prefaces the target token in the test file.
74+
* @param int|string|array $targetToken The token type(s) to look for.
75+
* @param array|false $expected The expected function return value.
76+
*
77+
* @return void
78+
*/
79+
public function testGetOpenClose($testMarker, $targetToken, $expected)
80+
{
81+
$stackPtr = $this->getTargetToken($testMarker, $targetToken);
82+
83+
// Convert offsets to absolute positions.
84+
if (isset($expected['opener'], $expected['closer'])) {
85+
$expected['opener'] += $stackPtr;
86+
$expected['closer'] += $stackPtr;
87+
}
88+
89+
$result = Arrays::getOpenClose(self::$phpcsFile, $stackPtr);
90+
$this->assertSame($expected, $result);
91+
}
92+
93+
/**
94+
* Data provider.
95+
*
96+
* The opener/closer positions are provided as offsets from the target stackPtr.
97+
*
98+
* @see testGetOpenClose() For the array format.
99+
*
100+
* @return array
101+
*/
102+
public function dataGetOpenClose()
103+
{
104+
return [
105+
'long-array' => [
106+
'/* testLongArray */',
107+
\T_ARRAY,
108+
[
109+
'opener' => 1,
110+
'closer' => 14,
111+
],
112+
],
113+
'long-array-nested' => [
114+
'/* testNestedLongArray */',
115+
\T_ARRAY,
116+
[
117+
'opener' => 2,
118+
'closer' => 6,
119+
],
120+
],
121+
'short-array' => [
122+
'/* testShortArray */',
123+
\T_OPEN_SHORT_ARRAY,
124+
[
125+
'opener' => 0,
126+
'closer' => 9,
127+
],
128+
],
129+
'short-array-nested' => [
130+
'/* testNestedShortArray */',
131+
\T_OPEN_SHORT_ARRAY,
132+
[
133+
'opener' => 0,
134+
'closer' => 2,
135+
],
136+
],
137+
'long-array-with-comments-and-annotations' => [
138+
'/* testArrayWithCommentsAndAnnotations */',
139+
\T_ARRAY,
140+
[
141+
'opener' => 4,
142+
'closer' => 26,
143+
],
144+
],
145+
'parse-error' => [
146+
'/* testParseError */',
147+
\T_ARRAY,
148+
false,
149+
],
150+
];
151+
}
152+
153+
/**
154+
* Test retrieving the open/close tokens for a nested array, skipping the short array check.
155+
*
156+
* @return void
157+
*/
158+
public function testGetOpenCloseThirdParam()
159+
{
160+
$stackPtr = $this->getTargetToken('/* testNestedShortArray */', \T_OPEN_SHORT_ARRAY);
161+
$expected = [
162+
'opener' => $stackPtr,
163+
'closer' => ($stackPtr + 2),
164+
];
165+
166+
$result = Arrays::getOpenClose(self::$phpcsFile, $stackPtr, true);
167+
$this->assertSame($expected, $result);
168+
}
169+
}

0 commit comments

Comments
 (0)