Skip to content

Commit 8dcca5e

Browse files
committed
✨ New Abstracts\AbstractArrayDeclarationSniff
New abstract sniff to examine array declarations. This abstract sets up a number of helpful properties for the array and then calls the following methods from the `processArray()` method: * `processOpenClose()` to examine the array opener and closer. This method will be called independently of whether the array has items or not. * And then for each individual array item: - `processKey()` and `processArrow()` for array items with an index key; or `processNoKey()` for array items without one. - `processValue()` - `processComma()` (this method _may_ not be called for the last array item if it is not followed by a comma. Aside from the `process()` method which sets up the properties, all other methods can be overloaded and/or ignored in concrete sniff implementations. Returning `true` from any of the methods will short-circuit the loop and exit the sniff early. The properties available to the methods in the child class are: * `$stackPtr` containing the stack pointer to the array keyword/short array open bracket. * `$tokens` containing the token stack of the current file. * `$arrayOpener` containing the stack pointer to the array open parenthesis/short array open bracket. * `$arrayCloser` containing the stack pointer to the array closer parenthesis/short array close bracket. * `$arrayItems` containing a multi-dimentional array with information on each array item. * `$itemCount` containing the number of items found in the array. * `$singleLine` containing whether or not the array single line or multi-line. Includes extensive unit tests for the abstract sniff behaviour.
1 parent d5ae800 commit 8dcca5e

File tree

3 files changed

+992
-0
lines changed

3 files changed

+992
-0
lines changed
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
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\AbstractSniffs;
12+
13+
use PHP_CodeSniffer\Exceptions\RuntimeException;
14+
use PHP_CodeSniffer\Files\File;
15+
use PHP_CodeSniffer\Sniffs\Sniff;
16+
use PHPCSUtils\Utils\Arrays;
17+
use PHPCSUtils\Utils\PassedParameters;
18+
19+
/**
20+
* Abstract sniff to easily examine all parts of an array declaration.
21+
*
22+
* @since 1.0.0
23+
*/
24+
abstract class AbstractArrayDeclarationSniff implements Sniff
25+
{
26+
27+
/**
28+
* The stack pointer to the array keyword or the short array open token.
29+
*
30+
* @since 1.0.0
31+
*
32+
* @var int
33+
*/
34+
protected $stackPtr;
35+
36+
/**
37+
* The token stack for the current file being examined.
38+
*
39+
* @since 1.0.0
40+
*
41+
* @var array
42+
*/
43+
protected $tokens;
44+
45+
/**
46+
* The stack pointer to the array opener.
47+
*
48+
* @since 1.0.0
49+
*
50+
* @var int
51+
*/
52+
protected $arrayOpener;
53+
54+
/**
55+
* The stack pointer to the array closer.
56+
*
57+
* @since 1.0.0
58+
*
59+
* @var int
60+
*/
61+
protected $arrayCloser;
62+
63+
/**
64+
* A multi-dimentional array with information on each array item.
65+
*
66+
* The array index is 1-based and contains the following information on each array item:
67+
* - 'start' : The stack pointer to the first token in the array item.
68+
* - 'end' : The stack pointer to the first token in the array item.
69+
* - 'raw' : A string with the contents of all tokens between `start` and `end`.
70+
* - 'clean' : Same as `raw`, but all comment tokens have been stripped out.
71+
*
72+
* @since 1.0.0
73+
*
74+
* @var array
75+
*/
76+
protected $arrayItems;
77+
78+
/**
79+
* How many items are in the array.
80+
*
81+
* @since 1.0.0
82+
*
83+
* @var int
84+
*/
85+
protected $itemCount = 0;
86+
87+
/**
88+
* Whether or not the array is single line.
89+
*
90+
* @since 1.0.0
91+
*
92+
* @var bool
93+
*/
94+
protected $singleLine;
95+
96+
/**
97+
* Returns an array of tokens this test wants to listen for.
98+
*
99+
* @since 1.0.0
100+
*
101+
* @codeCoverageIgnore
102+
*
103+
* @return array
104+
*/
105+
public function register()
106+
{
107+
return [
108+
\T_ARRAY,
109+
\T_OPEN_SHORT_ARRAY,
110+
\T_OPEN_SQUARE_BRACKET,
111+
];
112+
}
113+
114+
/**
115+
* Processes this test when one of its tokens is encountered.
116+
*
117+
* This method fills the properties with relevant information for examining the array
118+
* and then passes off to the `processArray()` method.
119+
*
120+
* @since 1.0.0
121+
*
122+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
123+
* token was found.
124+
* @param int $stackPtr The position in the PHP_CodeSniffer
125+
* file's token stack where the token
126+
* was found.
127+
*
128+
* @return void
129+
*/
130+
final public function process(File $phpcsFile, $stackPtr)
131+
{
132+
try {
133+
$this->arrayItems = PassedParameters::getParameters($phpcsFile, $stackPtr);
134+
} catch (RuntimeException $e) {
135+
// Parse error, short list, real square open bracket or incorrectly tokenized short array token.
136+
return;
137+
}
138+
139+
$this->stackPtr = $stackPtr;
140+
$this->tokens = $phpcsFile->getTokens();
141+
$openClose = Arrays::getOpenClose($phpcsFile, $stackPtr, true);
142+
$this->arrayOpener = $openClose['opener'];
143+
$this->arrayCloser = $openClose['closer'];
144+
$this->itemCount = \count($this->arrayItems);
145+
146+
$this->singleLine = true;
147+
if ($this->tokens[$openClose['opener']]['line'] !== $this->tokens[$openClose['closer']]['line']) {
148+
$this->singleLine = false;
149+
}
150+
151+
$this->processArray($phpcsFile);
152+
153+
// Reset select properties between calls to this sniff to lower memory usage.
154+
unset($this->tokens, $this->arrayItems);
155+
}
156+
157+
/**
158+
* Process every part of the array declaration.
159+
*
160+
* This contains the default logic for the sniff, but can be overloaded in a concrete child class
161+
* if needed.
162+
*
163+
* @since 1.0.0
164+
*
165+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
166+
* token was found.
167+
*
168+
* @return void
169+
*/
170+
public function processArray(File $phpcsFile)
171+
{
172+
if ($this->processOpenClose($phpcsFile, $this->arrayOpener, $this->arrayCloser) === true) {
173+
return;
174+
}
175+
176+
if ($this->itemCount === 0) {
177+
return;
178+
}
179+
180+
foreach ($this->arrayItems as $itemNr => $arrayItem) {
181+
$arrowPtr = Arrays::getDoubleArrowPtr($phpcsFile, $arrayItem['start'], $arrayItem['end']);
182+
183+
if ($arrowPtr !== false) {
184+
if ($this->processKey($phpcsFile, $arrayItem['start'], ($arrowPtr - 1), $itemNr) === true) {
185+
return;
186+
}
187+
188+
if ($this->processArrow($phpcsFile, $arrowPtr, $itemNr) === true) {
189+
return;
190+
}
191+
192+
if ($this->processValue($phpcsFile, ($arrowPtr + 1), $arrayItem['end'], $itemNr) === true) {
193+
return;
194+
}
195+
} else {
196+
if ($this->processNoKey($phpcsFile, $arrayItem['start'], $itemNr) === true) {
197+
return;
198+
}
199+
200+
if ($this->processValue($phpcsFile, $arrayItem['start'], $arrayItem['end'], $itemNr) === true) {
201+
return;
202+
}
203+
}
204+
205+
$commaPtr = ($arrayItem['end'] + 1);
206+
if ($itemNr < $this->itemCount || $this->tokens[$commaPtr]['code'] === \T_COMMA) {
207+
if ($this->processComma($phpcsFile, $commaPtr, $itemNr) === true) {
208+
return;
209+
}
210+
}
211+
}
212+
}
213+
214+
/**
215+
* Process the array opener and closer.
216+
*
217+
* Optional method to be implemented in concrete child classes.
218+
*
219+
* @since 1.0.0
220+
*
221+
* @codeCoverageIgnore
222+
*
223+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
224+
* token was found.
225+
* @param int $openPtr The position of the array opener token in the token stack.
226+
* @param int $closePtr The position of the array closer token in the token stack.
227+
*
228+
* @return true|void Returning `true` will short-circuit the sniff and stop processing.
229+
*/
230+
public function processOpenClose(File $phpcsFile, $openPtr, $closePtr)
231+
{
232+
}
233+
234+
/**
235+
* Process the tokens in an array key.
236+
*
237+
* Optional method to be implemented in concrete child classes.
238+
*
239+
* The $startPtr and $endPtr do not discount whitespace or comments, but are all inclusive to
240+
* allow examining all tokens in an array key.
241+
*
242+
* @since 1.0.0
243+
*
244+
* @codeCoverageIgnore
245+
*
246+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
247+
* token was found.
248+
* @param int $startPtr The stack pointer to the first token in the "key" part of
249+
* an array item.
250+
* @param int $endPtr The stack pointer to the last token in the "key" part of
251+
* an array item.
252+
* @param int $itemNr Which item in the array is being handled.
253+
* 1-based, i.e. the first item is item 1, the second 2 etc.
254+
*
255+
* @return true|void Returning `true` will short-circuit the array item loop and stop processing.
256+
*/
257+
public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr)
258+
{
259+
}
260+
261+
/**
262+
* Process an array item without an array key.
263+
*
264+
* Optional method to be implemented in concrete child classes.
265+
*
266+
* @since 1.0.0
267+
*
268+
* @codeCoverageIgnore
269+
*
270+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
271+
* token was found.
272+
* @param int $startPtr The stack pointer to the first token in the array item,
273+
* which in this case will be the first token of the array
274+
* value part of the array item.
275+
* @param int $itemNr Which item in the array is being handled.
276+
* 1-based, i.e. the first item is item 1, the second 2 etc.
277+
*
278+
* @return true|void Returning `true` will short-circuit the array item loop and stop processing.
279+
*/
280+
public function processNoKey(File $phpcsFile, $startPtr, $itemNr)
281+
{
282+
}
283+
284+
/**
285+
* Process the double arrow.
286+
*
287+
* Optional method to be implemented in concrete child classes.
288+
*
289+
* @since 1.0.0
290+
*
291+
* @codeCoverageIgnore
292+
*
293+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
294+
* token was found.
295+
* @param int $arrowPtr The stack pointer to the double arrow for the array item.
296+
* @param int $itemNr Which item in the array is being handled.
297+
* 1-based, i.e. the first item is item 1, the second 2 etc.
298+
*
299+
* @return true|void Returning `true` will short-circuit the array item loop and stop processing.
300+
*/
301+
public function processArrow(File $phpcsFile, $arrowPtr, $itemNr)
302+
{
303+
}
304+
305+
/**
306+
* Process the tokens in an array value.
307+
*
308+
* Optional method to be implemented in concrete child classes.
309+
*
310+
* The $startPtr and $endPtr do not discount whitespace or comments, but are all inclusive to
311+
* allow examining all tokens in an array value.
312+
*
313+
* @since 1.0.0
314+
*
315+
* @codeCoverageIgnore
316+
*
317+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
318+
* token was found.
319+
* @param int $startPtr The stack pointer to the first token in the "value" part of
320+
* an array item.
321+
* @param int $endPtr The stack pointer to the last token in the "value" part of
322+
* an array item.
323+
* @param int $itemNr Which item in the array is being handled.
324+
* 1-based, i.e. the first item is item 1, the second 2 etc.
325+
*
326+
* @return true|void Returning `true` will short-circuit the array item loop and stop processing.
327+
*/
328+
public function processValue(File $phpcsFile, $startPtr, $endPtr, $itemNr)
329+
{
330+
}
331+
332+
/**
333+
* Process the comma after an array item.
334+
*
335+
* Optional method to be implemented in concrete child classes.
336+
*
337+
* @since 1.0.0
338+
*
339+
* @codeCoverageIgnore
340+
*
341+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
342+
* token was found.
343+
* @param int $commaPtr The stack pointer to the comma.
344+
* @param int $itemNr Which item in the array is being handled.
345+
* 1-based, i.e. the first item is item 1, the second 2 etc.
346+
*
347+
* @return true|void Returning `true` will short-circuit the array item loop and stop processing.
348+
*/
349+
public function processComma(File $phpcsFile, $commaPtr, $itemNr)
350+
{
351+
}
352+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/* testEmptyArray */
4+
$array = array( );
5+
6+
/* testSingleLineShortArrayNoKeysNoTrailingComma */
7+
$array = [1, 2];
8+
9+
/* testMultiLineLongArrayKeysTrailingComma */
10+
$array = array(
11+
1 => 'a',
12+
2 => 'b',
13+
3 => 'c',
14+
4 => 'd',
15+
);
16+
17+
/* testMultiLineShortArrayMixedKeysNoKeys */
18+
$array = [
19+
12 => 'numeric key',
20+
'value',
21+
'string' => 'string key',
22+
];
23+
24+
/* testShortCircuit */
25+
$array = [1, 'a' => 2, ];
26+
27+
/* testShortList */
28+
[$a, $b] = $array;

0 commit comments

Comments
 (0)