• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 15253296250

26 May 2025 11:55AM UTC coverage: 78.632% (+0.3%) from 78.375%
15253296250

Pull #1105

github

web-flow
Merge d9441d98f into caf806050
Pull Request #1105: Skip tests when 'git' command is not available

19665 of 25009 relevant lines covered (78.63%)

88.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.28
/src/Standards/Squiz/Sniffs/Formatting/OperatorBracketSniff.php
1
<?php
2
/**
3
 * Tests that all arithmetic operations are bracketed.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Standards\Squiz\Sniffs\Formatting;
11

12
use PHP_CodeSniffer\Files\File;
13
use PHP_CodeSniffer\Sniffs\Sniff;
14
use PHP_CodeSniffer\Util\Tokens;
15

16
class OperatorBracketSniff implements Sniff
17
{
18

19

20
    /**
21
     * Returns an array of tokens this test wants to listen for.
22
     *
23
     * @return array<int|string>
24
     */
25
    public function register()
3✔
26
    {
27
        return Tokens::OPERATORS;
3✔
28

29
    }//end register()
30

31

32
    /**
33
     * Processes this test, when one of its tokens is encountered.
34
     *
35
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
36
     * @param int                         $stackPtr  The position of the current token in the
37
     *                                               stack passed in $tokens.
38
     *
39
     * @return void
40
     */
41
    public function process(File $phpcsFile, $stackPtr)
3✔
42
    {
43
        $tokens = $phpcsFile->getTokens();
3✔
44

45
        // If the & is a reference, then we don't want to check for brackets.
46
        if ($tokens[$stackPtr]['code'] === T_BITWISE_AND && $phpcsFile->isReference($stackPtr) === true) {
3✔
47
            return;
3✔
48
        }
49

50
        // There is one instance where brackets aren't needed, which involves
51
        // the minus sign being used to assign a negative number to a variable.
52
        if ($tokens[$stackPtr]['code'] === T_MINUS) {
3✔
53
            // Check to see if we are trying to return -n.
54
            $prev = $phpcsFile->findPrevious(Tokens::EMPTY_TOKENS, ($stackPtr - 1), null, true);
3✔
55
            if ($tokens[$prev]['code'] === T_RETURN) {
3✔
56
                return;
3✔
57
            }
58

59
            $number = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
3✔
60
            if ($tokens[$number]['code'] === T_LNUMBER || $tokens[$number]['code'] === T_DNUMBER) {
3✔
61
                $previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
3✔
62
                if ($previous !== false) {
3✔
63
                    $isAssignment = isset(Tokens::ASSIGNMENT_TOKENS[$tokens[$previous]['code']]);
3✔
64
                    $isEquality   = isset(Tokens::EQUALITY_TOKENS[$tokens[$previous]['code']]);
3✔
65
                    $isComparison = isset(Tokens::COMPARISON_TOKENS[$tokens[$previous]['code']]);
3✔
66
                    $isUnary      = isset(Tokens::OPERATORS[$tokens[$previous]['code']]);
3✔
67
                    if ($isAssignment === true || $isEquality === true || $isComparison === true || $isUnary === true) {
3✔
68
                        // This is a negative assignment or comparison.
69
                        // We need to check that the minus and the number are
70
                        // adjacent.
71
                        if (($number - $stackPtr) !== 1) {
3✔
72
                            $error = 'No space allowed between minus sign and number';
3✔
73
                            $phpcsFile->addError($error, $stackPtr, 'SpacingAfterMinus');
3✔
74
                        }
75

76
                        return;
3✔
77
                    }
78
                }
79
            }
80
        }//end if
81

82
        $previousToken = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true, null, true);
3✔
83
        if ($previousToken !== false) {
3✔
84
            // A list of tokens that indicate that the token is not
85
            // part of an arithmetic operation.
86
            $invalidTokens = [
2✔
87
                T_COMMA               => true,
3✔
88
                T_COLON               => true,
3✔
89
                T_OPEN_PARENTHESIS    => true,
3✔
90
                T_OPEN_SQUARE_BRACKET => true,
3✔
91
                T_OPEN_CURLY_BRACKET  => true,
3✔
92
                T_OPEN_SHORT_ARRAY    => true,
3✔
93
                T_CASE                => true,
3✔
94
                T_EXIT                => true,
3✔
95
                T_MATCH_ARROW         => true,
3✔
96
            ];
2✔
97

98
            if (isset($invalidTokens[$tokens[$previousToken]['code']]) === true) {
3✔
99
                return;
3✔
100
            }
101
        }
102

103
        if ($tokens[$stackPtr]['code'] === T_BITWISE_OR
3✔
104
            && isset($tokens[$stackPtr]['nested_parenthesis']) === true
3✔
105
        ) {
106
            $brackets    = $tokens[$stackPtr]['nested_parenthesis'];
3✔
107
            $lastBracket = array_pop($brackets);
3✔
108
            if (isset($tokens[$lastBracket]['parenthesis_owner']) === true
3✔
109
                && $tokens[$tokens[$lastBracket]['parenthesis_owner']]['code'] === T_CATCH
3✔
110
            ) {
111
                // This is a pipe character inside a catch statement, so it is acting
112
                // as an exception type separator and not an arithmetic operation.
113
                return;
3✔
114
            }
115
        }
116

117
        // Tokens that are allowed inside a bracketed operation.
118
        $allowed  = Tokens::NAME_TOKENS;
3✔
119
        $allowed += Tokens::OPERATORS;
3✔
120
        $allowed += [
2✔
121
            T_VARIABLE                 => T_VARIABLE,
3✔
122
            T_LNUMBER                  => T_LNUMBER,
3✔
123
            T_DNUMBER                  => T_DNUMBER,
3✔
124
            T_WHITESPACE               => T_WHITESPACE,
3✔
125
            T_SELF                     => T_SELF,
3✔
126
            T_STATIC                   => T_STATIC,
3✔
127
            T_PARENT                   => T_PARENT,
3✔
128
            T_OBJECT_OPERATOR          => T_OBJECT_OPERATOR,
3✔
129
            T_NULLSAFE_OBJECT_OPERATOR => T_NULLSAFE_OBJECT_OPERATOR,
3✔
130
            T_DOUBLE_COLON             => T_DOUBLE_COLON,
3✔
131
            T_OPEN_SQUARE_BRACKET      => T_OPEN_SQUARE_BRACKET,
3✔
132
            T_CLOSE_SQUARE_BRACKET     => T_CLOSE_SQUARE_BRACKET,
3✔
133
            T_NONE                     => T_NONE,
3✔
134
            T_BITWISE_NOT              => T_BITWISE_NOT,
3✔
135
        ];
2✔
136

137
        $lastBracket = false;
3✔
138
        if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
3✔
139
            $parenthesis = array_reverse($tokens[$stackPtr]['nested_parenthesis'], true);
3✔
140
            foreach ($parenthesis as $bracket => $endBracket) {
3✔
141
                $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($bracket - 1), null, true);
3✔
142
                $prevCode  = $tokens[$prevToken]['code'];
3✔
143

144
                if ($prevCode === T_ISSET) {
3✔
145
                    // This operation is inside an isset() call, but has
146
                    // no bracket of it's own.
147
                    break;
3✔
148
                }
149

150
                if (isset(Tokens::NAME_TOKENS[$prevCode]) === true
3✔
151
                    || $prevCode === T_SWITCH
3✔
152
                    || $prevCode === T_MATCH
3✔
153
                ) {
154
                    // We allow simple operations to not be bracketed.
155
                    // For example, ceil($one / $two).
156
                    for ($prev = ($stackPtr - 1); $prev > $bracket; $prev--) {
3✔
157
                        if (isset($allowed[$tokens[$prev]['code']]) === true) {
3✔
158
                            continue;
3✔
159
                        }
160

161
                        if ($tokens[$prev]['code'] === T_CLOSE_PARENTHESIS) {
3✔
162
                            $prev = $tokens[$prev]['parenthesis_opener'];
3✔
163
                        } else {
164
                            break;
3✔
165
                        }
166
                    }
167

168
                    if ($prev !== $bracket) {
3✔
169
                        break;
3✔
170
                    }
171

172
                    for ($next = ($stackPtr + 1); $next < $endBracket; $next++) {
3✔
173
                        if (isset($allowed[$tokens[$next]['code']]) === true) {
3✔
174
                            continue;
3✔
175
                        }
176

177
                        if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS) {
3✔
178
                            $next = $tokens[$next]['parenthesis_closer'];
3✔
179
                        } else {
180
                            break;
×
181
                        }
182
                    }
183

184
                    if ($next !== $endBracket) {
3✔
185
                        break;
×
186
                    }
187
                }//end if
188

189
                if (in_array($prevCode, Tokens::SCOPE_OPENERS, true) === true) {
3✔
190
                    // This operation is inside a control structure like FOREACH
191
                    // or IF, but has no bracket of it's own.
192
                    // The only control structures allowed to do this are SWITCH and MATCH.
193
                    if ($prevCode !== T_SWITCH && $prevCode !== T_MATCH) {
3✔
194
                        break;
3✔
195
                    }
196
                }
197

198
                if ($prevCode === T_OPEN_PARENTHESIS) {
3✔
199
                    // These are two open parenthesis in a row. If the current
200
                    // one doesn't enclose the operator, go to the previous one.
201
                    if ($endBracket < $stackPtr) {
3✔
202
                        continue;
×
203
                    }
204
                }
205

206
                $lastBracket = $bracket;
3✔
207
                break;
3✔
208
            }//end foreach
209
        }//end if
210

211
        if ($lastBracket === false) {
3✔
212
            // It is not in a bracketed statement at all.
213
            $this->addMissingBracketsError($phpcsFile, $stackPtr);
3✔
214
            return;
3✔
215
        } else if ($tokens[$lastBracket]['parenthesis_closer'] < $stackPtr) {
3✔
216
            // There are a set of brackets in front of it that don't include it.
217
            $this->addMissingBracketsError($phpcsFile, $stackPtr);
×
218
            return;
×
219
        } else {
220
            // We are enclosed in a set of bracket, so the last thing to
221
            // check is that we are not also enclosed in square brackets
222
            // like this: ($array[$index + 1]), which is invalid.
223
            $brackets = [
2✔
224
                T_OPEN_SQUARE_BRACKET,
3✔
225
                T_CLOSE_SQUARE_BRACKET,
3✔
226
            ];
2✔
227

228
            $squareBracket = $phpcsFile->findPrevious($brackets, ($stackPtr - 1), $lastBracket);
3✔
229
            if ($squareBracket !== false && $tokens[$squareBracket]['code'] === T_OPEN_SQUARE_BRACKET) {
3✔
230
                $closeSquareBracket = $phpcsFile->findNext($brackets, ($stackPtr + 1));
3✔
231
                if ($closeSquareBracket !== false && $tokens[$closeSquareBracket]['code'] === T_CLOSE_SQUARE_BRACKET) {
3✔
232
                    $this->addMissingBracketsError($phpcsFile, $stackPtr);
3✔
233
                }
234
            }
235

236
            return;
3✔
237
        }//end if
238

239
        $lastAssignment = $phpcsFile->findPrevious(Tokens::ASSIGNMENT_TOKENS, $stackPtr, null, false, null, true);
240
        if ($lastAssignment !== false && $lastAssignment > $lastBracket) {
241
            $this->addMissingBracketsError($phpcsFile, $stackPtr);
242
        }
243

244
    }//end process()
245

246

247
    /**
248
     * Add and fix the missing brackets error.
249
     *
250
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
251
     * @param int                         $stackPtr  The position of the current token in the
252
     *                                               stack passed in $tokens.
253
     *
254
     * @return void
255
     */
256
    public function addMissingBracketsError($phpcsFile, $stackPtr)
3✔
257
    {
258
        $tokens = $phpcsFile->getTokens();
3✔
259

260
        $allowed = [
2✔
261
            T_VARIABLE                 => true,
3✔
262
            T_LNUMBER                  => true,
3✔
263
            T_DNUMBER                  => true,
3✔
264
            T_CONSTANT_ENCAPSED_STRING => true,
3✔
265
            T_DOUBLE_QUOTED_STRING     => true,
3✔
266
            T_WHITESPACE               => true,
3✔
267
            T_SELF                     => true,
3✔
268
            T_STATIC                   => true,
3✔
269
            T_OBJECT_OPERATOR          => true,
3✔
270
            T_NULLSAFE_OBJECT_OPERATOR => true,
3✔
271
            T_DOUBLE_COLON             => true,
3✔
272
            T_ISSET                    => true,
3✔
273
            T_ARRAY                    => true,
3✔
274
            T_NONE                     => true,
3✔
275
            T_BITWISE_NOT              => true,
3✔
276
        ];
2✔
277

278
        // Find the first token in the expression.
279
        for ($before = ($stackPtr - 1); $before > 0; $before--) {
3✔
280
            if (isset(Tokens::EMPTY_TOKENS[$tokens[$before]['code']]) === true
3✔
281
                || isset(Tokens::OPERATORS[$tokens[$before]['code']]) === true
3✔
282
                || isset(Tokens::CAST_TOKENS[$tokens[$before]['code']]) === true
3✔
283
                || isset(Tokens::NAME_TOKENS[$tokens[$before]['code']]) === true
3✔
284
                || isset($allowed[$tokens[$before]['code']]) === true
3✔
285
            ) {
286
                continue;
3✔
287
            }
288

289
            if ($tokens[$before]['code'] === T_CLOSE_PARENTHESIS) {
3✔
290
                $before = $tokens[$before]['parenthesis_opener'];
3✔
291
                continue;
3✔
292
            }
293

294
            if ($tokens[$before]['code'] === T_CLOSE_SQUARE_BRACKET) {
3✔
295
                $before = $tokens[$before]['bracket_opener'];
3✔
296
                continue;
3✔
297
            }
298

299
            if ($tokens[$before]['code'] === T_CLOSE_SHORT_ARRAY) {
3✔
300
                $before = $tokens[$before]['bracket_opener'];
3✔
301
                continue;
3✔
302
            }
303

304
            break;
3✔
305
        }//end for
306

307
        $before = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($before + 1), null, true);
3✔
308

309
        // A few extra tokens are allowed to be on the right side of the expression.
310
        $allowed[T_EQUAL] = true;
3✔
311
        $allowed[T_NEW]   = true;
3✔
312

313
        // Find the last token in the expression.
314
        for ($after = ($stackPtr + 1); $after < $phpcsFile->numTokens; $after++) {
3✔
315
            if (isset(Tokens::EMPTY_TOKENS[$tokens[$after]['code']]) === true
3✔
316
                || isset(Tokens::OPERATORS[$tokens[$after]['code']]) === true
3✔
317
                || isset(Tokens::CAST_TOKENS[$tokens[$after]['code']]) === true
3✔
318
                || isset(Tokens::NAME_TOKENS[$tokens[$after]['code']]) === true
3✔
319
                || isset($allowed[$tokens[$after]['code']]) === true
3✔
320
            ) {
321
                continue;
3✔
322
            }
323

324
            if ($tokens[$after]['code'] === T_OPEN_PARENTHESIS) {
3✔
325
                if (isset($tokens[$after]['parenthesis_closer']) === false) {
3✔
326
                    // Live coding/parse error. Ignore.
327
                    return;
3✔
328
                }
329

330
                $after = $tokens[$after]['parenthesis_closer'];
3✔
331
                continue;
3✔
332
            }
333

334
            if (($tokens[$after]['code'] === T_OPEN_SQUARE_BRACKET
3✔
335
                || $tokens[$after]['code'] === T_OPEN_SHORT_ARRAY)
3✔
336
            ) {
337
                if (isset($tokens[$after]['bracket_closer']) === false) {
3✔
338
                    // Live coding/parse error. Ignore.
339
                    return;
3✔
340
                }
341

342
                $after = $tokens[$after]['bracket_closer'];
3✔
343
                continue;
3✔
344
            }
345

346
            break;
3✔
347
        }//end for
348

349
        $after = $phpcsFile->findPrevious(Tokens::EMPTY_TOKENS, ($after - 1), null, true);
3✔
350

351
        $error = 'Operation must be bracketed';
3✔
352
        if ($before === $after || $before === $stackPtr || $after === $stackPtr) {
3✔
353
            $phpcsFile->addError($error, $stackPtr, 'MissingBrackets');
3✔
354
            return;
3✔
355
        }
356

357
        $fix = $phpcsFile->addFixableError($error, $stackPtr, 'MissingBrackets');
3✔
358
        if ($fix === true) {
3✔
359
            // Can only fix this error if both tokens are available for fixing.
360
            // Adding one bracket without the other will create parse errors.
361
            $phpcsFile->fixer->beginChangeset();
3✔
362
            $phpcsFile->fixer->replaceToken($before, '('.$tokens[$before]['content']);
3✔
363
            $phpcsFile->fixer->replaceToken($after, $tokens[$after]['content'].')');
3✔
364
            $phpcsFile->fixer->endChangeset();
3✔
365
        }
366

367
    }//end addMissingBracketsError()
1✔
368

369

370
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc