1111namespace PHPCSUtils \Utils ;
1212
1313use PHP_CodeSniffer \Files \File ;
14+ use PHP_CodeSniffer \Util \Tokens ;
15+ use PHPCSUtils \BackCompat \Helper ;
1416use PHPCSUtils \Tokens \Collections ;
1517use PHPCSUtils \Utils \Lists ;
1618
@@ -26,25 +28,129 @@ class Arrays
2628 * Determine whether a `T_OPEN/CLOSE_SHORT_ARRAY` token is a short array() construct
2729 * and not a short list.
2830 *
31+ * This method also accepts `T_OPEN/CLOSE_SQUARE_BRACKET` tokens to allow it to be
32+ * PHPCS cross-version compatible as the short array tokenizing has been plagued by
33+ * a number of bugs over time.
34+ *
2935 * @since 1.0.0
3036 *
3137 * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
3238 * @param int $stackPtr The position of the short array bracket token.
3339 *
3440 * @return bool True if the token passed is the open/close bracket of a short array.
35- * False if the token is a short list bracket or not one of the accepted tokens.
41+ * False if the token is a short list bracket, a plain square bracket
42+ * or not one of the accepted tokens.
3643 */
3744 public static function isShortArray (File $ phpcsFile , $ stackPtr )
3845 {
3946 $ tokens = $ phpcsFile ->getTokens ();
4047
4148 // Is this one of the tokens this function handles ?
4249 if (isset ($ tokens [$ stackPtr ]) === false
43- || isset (Collections::$ shortArrayTokens [$ tokens [$ stackPtr ]['code ' ]]) === false
50+ || isset (Collections::$ shortArrayTokensBC [$ tokens [$ stackPtr ]['code ' ]]) === false
4451 ) {
4552 return false ;
4653 }
4754
55+ // All known tokenizer bugs are in PHPCS versions before 3.3.0.
56+ $ phpcsVersion = Helper::getVersion ();
57+
58+ /*
59+ * Deal with square brackets which may be incorrectly tokenized short arrays.
60+ */
61+ if (isset (Collections::$ shortArrayTokens [$ tokens [$ stackPtr ]['code ' ]]) === false ) {
62+ if (\version_compare ($ phpcsVersion , '3.3.0 ' , '>= ' )) {
63+ // These will just be properly tokenized, plain square brackets. No need for further checks.
64+ return false ;
65+ }
66+
67+ $ opener = $ stackPtr ;
68+ if ($ tokens [$ stackPtr ]['code ' ] === \T_CLOSE_SQUARE_BRACKET ) {
69+ $ opener = $ tokens [$ stackPtr ]['bracket_opener ' ];
70+ }
71+
72+ if (isset ($ tokens [$ opener ]['bracket_closer ' ]) === false ) {
73+ return false ;
74+ }
75+
76+ $ prevNonEmpty = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ opener - 1 ), null , true );
77+
78+ if (\version_compare ($ phpcsVersion , '2.8.0 ' , '>= ' )) {
79+ /*
80+ * BC: Work around a bug in the tokenizer of PHPCS 2.8.0 - 3.2.3 where a `[` would be
81+ * tokenized as T_OPEN_SQUARE_BRACKET instead of T_OPEN_SHORT_ARRAY if it was
82+ * preceded by a PHP open tag at the very start of the file.
83+ *
84+ * If we have square brackets which are not that specific situation, they are just plain
85+ * square brackets.
86+ *
87+ * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1971
88+ */
89+ if ($ prevNonEmpty !== 0 || $ tokens [$ prevNonEmpty ]['code ' ] !== \T_OPEN_TAG ) {
90+ return false ;
91+ }
92+ }
93+
94+ if (\version_compare ($ phpcsVersion , '2.8.0 ' , '< ' )) {
95+ /*
96+ * BC: Work around a bug in the tokenizer of PHPCS < 2.8.0 where a `[` would be
97+ * tokenized as T_OPEN_SQUARE_BRACKET instead of T_OPEN_SHORT_ARRAY if it was
98+ * preceded by a close curly of a control structure.
99+ *
100+ * If we have square brackets which are not that specific situation, they are just plain
101+ * square brackets.
102+ *
103+ * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1284
104+ */
105+ if ($ tokens [$ prevNonEmpty ]['code ' ] !== \T_CLOSE_CURLY_BRACKET
106+ || isset ($ tokens [$ prevNonEmpty ]['scope_condition ' ]) === false
107+ ) {
108+ return false ;
109+ }
110+ }
111+ } else {
112+ /*
113+ * Deal with short array brackets which may be incorrectly tokenized plain square brackets.
114+ */
115+ if (\version_compare ($ phpcsVersion , '2.9.0 ' , '< ' )) {
116+ $ opener = $ stackPtr ;
117+ if ($ tokens [$ stackPtr ]['code ' ] === \T_CLOSE_SHORT_ARRAY ) {
118+ $ opener = $ tokens [$ stackPtr ]['bracket_opener ' ];
119+ }
120+
121+ /*
122+ * BC: Work around a bug in the tokenizer of PHPCS < 2.9.0 where array dereferencing
123+ * of short array and string literals would be incorrectly tokenized as short array.
124+ * I.e. the square brackets in `'PHP'[0]` would be tokenized as short array.
125+ *
126+ * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1381
127+ */
128+ $ prevNonEmpty = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ opener - 1 ), null , true );
129+ if ($ tokens [$ prevNonEmpty ]['code ' ] === \T_CLOSE_SHORT_ARRAY
130+ || $ tokens [$ prevNonEmpty ]['code ' ] === \T_CONSTANT_ENCAPSED_STRING
131+ ) {
132+ return false ;
133+ }
134+
135+ /*
136+ * BC: Work around a bug in the tokenizer of PHPCS 2.8.0 and 2.8.1 where array dereferencing
137+ * of a variable variable would be incorrectly tokenized as short array.
138+ *
139+ * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/1284
140+ */
141+ if (\version_compare ($ phpcsVersion , '2.8.0 ' , '>= ' )
142+ && $ tokens [$ prevNonEmpty ]['code ' ] === \T_CLOSE_CURLY_BRACKET
143+ ) {
144+ $ openCurly = $ tokens [$ prevNonEmpty ]['bracket_opener ' ];
145+ $ beforeCurlies = $ phpcsFile ->findPrevious (Tokens::$ emptyTokens , ($ openCurly - 1 ), null , true );
146+ if ($ tokens [$ beforeCurlies ]['code ' ] === \T_DOLLAR ) {
147+ return false ;
148+ }
149+ }
150+ }
151+ }
152+
153+ // In all other circumstances, make sure this isn't a short list instead of a short array.
48154 return (Lists::isShortList ($ phpcsFile , $ stackPtr ) === false );
49155 }
50156}
0 commit comments