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

PHPCSStandards / PHP_CodeSniffer / 17663621563

12 Sep 2025 03:25AM UTC coverage: 78.786%. Remained the same
17663621563

push

github

web-flow
Merge pull request #1243 from PHPCSStandards/phpcs-4.x/feature/155-normalize-some-code-style-rules-5

CS: normalize code style rules [5]

294 of 308 new or added lines in 191 files covered. (95.45%)

2354 existing lines in 130 files now uncovered.

19732 of 25045 relevant lines covered (78.79%)

96.47 hits per line

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

96.18
/src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php
1
<?php
2
/**
3
 * Warns about code that can never been executed.
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\PHP;
11

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

16
class NonExecutableCodeSniff implements Sniff
17
{
18

19
    /**
20
     * Tokens for terminating expressions, which can be used inline.
21
     *
22
     * This is in contrast to terminating statements, which cannot be used inline
23
     * and would result in a parse error (which is not the concern of this sniff).
24
     *
25
     * `throw` can be used as an expression since PHP 8.0.
26
     * {@link https://wiki.php.net/rfc/throw_expression}
27
     *
28
     * @var array<int, int>
29
     */
30
    private const EXPRESSION_TOKENS = [
31
        T_EXIT  => T_EXIT,
32
        T_THROW => T_THROW,
33
    ];
34

35

36
    /**
37
     * Returns an array of tokens this test wants to listen for.
38
     *
39
     * @return array<int|string>
40
     */
41
    public function register()
3✔
42
    {
43
        return [
2✔
44
            T_BREAK,
3✔
45
            T_CONTINUE,
3✔
46
            T_RETURN,
3✔
47
            T_THROW,
3✔
48
            T_EXIT,
3✔
49
            T_GOTO,
3✔
50
        ];
2✔
51
    }
52

53

54
    /**
55
     * Processes this test, when one of its tokens is encountered.
56
     *
57
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
58
     * @param int                         $stackPtr  The position of the current token in
59
     *                                               the stack passed in $tokens.
60
     *
61
     * @return void
62
     */
63
    public function process(File $phpcsFile, int $stackPtr)
3✔
64
    {
65
        $tokens = $phpcsFile->getTokens();
3✔
66

67
        $prev = $phpcsFile->findPrevious(Tokens::EMPTY_TOKENS, ($stackPtr - 1), null, true);
3✔
68

69
        // Tokens which can be used in inline expressions need special handling.
70
        if (isset(self::EXPRESSION_TOKENS[$tokens[$stackPtr]['code']]) === true) {
3✔
71
            // If this token is preceded by a logical operator, it only relates to one line
72
            // and should be ignored. For example: fopen() or die().
73
            // Note: There is one exception: throw expressions can not be used with xor.
74
            if (isset(Tokens::BOOLEAN_OPERATORS[$tokens[$prev]['code']]) === true
3✔
75
                && ($tokens[$stackPtr]['code'] === T_THROW && $tokens[$prev]['code'] === T_LOGICAL_XOR) === false
3✔
76
            ) {
77
                return;
3✔
78
            }
79

80
            // Expressions are allowed in the `else` clause of ternaries.
81
            if ($tokens[$prev]['code'] === T_INLINE_THEN || $tokens[$prev]['code'] === T_INLINE_ELSE) {
3✔
82
                return;
3✔
83
            }
84

85
            // Expressions are allowed with PHP 7.0+ null coalesce and PHP 7.4+ null coalesce equals.
86
            if ($tokens[$prev]['code'] === T_COALESCE || $tokens[$prev]['code'] === T_COALESCE_EQUAL) {
3✔
87
                return;
3✔
88
            }
89

90
            // Expressions are allowed in arrow functions.
91
            if ($tokens[$prev]['code'] === T_FN_ARROW) {
3✔
92
                return;
3✔
93
            }
94
        }//end if
95

96
        // This token may be part of an inline condition.
97
        // If we find a closing parenthesis that belongs to a condition,
98
        // or an "else", we should ignore this token.
99
        if ($tokens[$prev]['code'] === T_ELSE
3✔
100
            || (isset($tokens[$prev]['parenthesis_owner']) === true
3✔
101
            && ($tokens[$tokens[$prev]['parenthesis_owner']]['code'] === T_IF
3✔
102
            || $tokens[$tokens[$prev]['parenthesis_owner']]['code'] === T_ELSEIF))
3✔
103
        ) {
104
            return;
3✔
105
        }
106

107
        if ($tokens[$stackPtr]['code'] === T_RETURN) {
3✔
108
            $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($stackPtr + 1), null, true);
3✔
109
            if ($tokens[$next]['code'] === T_SEMICOLON) {
3✔
110
                $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($next + 1), null, true);
3✔
111
                if ($tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) {
3✔
112
                    // If this is the closing brace of a function
113
                    // then this return statement doesn't return anything
114
                    // and is not required anyway.
115
                    $owner = $tokens[$next]['scope_condition'];
3✔
116
                    if ($tokens[$owner]['code'] === T_FUNCTION
3✔
117
                        || $tokens[$owner]['code'] === T_CLOSURE
3✔
118
                    ) {
119
                        $warning = 'Empty return statement not required here';
3✔
120
                        $phpcsFile->addWarning($warning, $stackPtr, 'ReturnNotRequired');
3✔
121
                        return;
3✔
122
                    }
123
                }
124
            }
125
        }
126

127
        if (isset($tokens[$stackPtr]['scope_opener']) === true) {
3✔
128
            $owner = $tokens[$stackPtr]['scope_condition'];
3✔
129
            if ($tokens[$owner]['code'] === T_CASE || $tokens[$owner]['code'] === T_DEFAULT) {
3✔
130
                // This token closes the scope of a CASE or DEFAULT statement
131
                // so any code between this statement and the next CASE, DEFAULT or
132
                // end of SWITCH token will not be executable.
133
                $end  = $phpcsFile->findEndOfStatement($stackPtr);
3✔
134
                $next = $phpcsFile->findNext(
3✔
135
                    [
2✔
136
                        T_CASE,
3✔
137
                        T_DEFAULT,
3✔
138
                        T_CLOSE_CURLY_BRACKET,
3✔
139
                        T_ENDSWITCH,
3✔
140
                    ],
2✔
141
                    ($end + 1)
3✔
142
                );
2✔
143

144
                if ($next !== false) {
3✔
145
                    $lastLine = $tokens[$end]['line'];
3✔
146
                    for ($i = ($stackPtr + 1); $i < $next; $i++) {
3✔
147
                        if (isset(Tokens::EMPTY_TOKENS[$tokens[$i]['code']]) === true) {
3✔
148
                            continue;
3✔
149
                        }
150

151
                        $line = $tokens[$i]['line'];
3✔
152
                        if ($line > $lastLine) {
3✔
153
                            $type    = substr($tokens[$stackPtr]['type'], 2);
3✔
154
                            $warning = 'Code after the %s statement on line %s cannot be executed';
3✔
155
                            $data    = [
2✔
156
                                $type,
3✔
157
                                $tokens[$stackPtr]['line'],
3✔
158
                            ];
2✔
159
                            $phpcsFile->addWarning($warning, $i, 'Unreachable', $data);
3✔
160
                            $lastLine = $line;
3✔
161
                        }
162
                    }
163
                }//end if
164

165
                // That's all we have to check for these types of statements.
166
                return;
3✔
167
            }//end if
168
        }//end if
169

170
        $ourConditions = array_keys($tokens[$stackPtr]['conditions']);
3✔
171

172
        if (empty($ourConditions) === false) {
3✔
173
            $condition = array_pop($ourConditions);
3✔
174

175
            if (isset($tokens[$condition]['scope_closer']) === false) {
3✔
UNCOV
176
                return;
×
177
            }
178

179
            // Special case for BREAK statements sitting directly inside SWITCH
180
            // statements. If we get to this point, we know the BREAK is not being
181
            // used to close a CASE statement, so it is most likely non-executable
182
            // code itself (as is the case when you put return; break; at the end of
183
            // a case). So we need to ignore this token.
184
            if ($tokens[$condition]['code'] === T_SWITCH
3✔
185
                && $tokens[$stackPtr]['code'] === T_BREAK
3✔
186
            ) {
187
                return;
3✔
188
            }
189

190
            $closer = $tokens[$condition]['scope_closer'];
3✔
191

192
            // If the closer for our condition is shared with other openers,
193
            // we will need to throw errors from this token to the next
194
            // shared opener (if there is one), not to the scope closer.
195
            $nextOpener = null;
3✔
196
            for ($i = ($stackPtr + 1); $i < $closer; $i++) {
3✔
197
                if (isset($tokens[$i]['scope_closer']) === true) {
3✔
198
                    if ($tokens[$i]['scope_closer'] === $closer) {
3✔
199
                        // We found an opener that shares the same
200
                        // closing token as us.
UNCOV
201
                        $nextOpener = $i;
×
202
                        break;
×
203
                    }
204
                }
205
            }//end for
206

207
            if ($nextOpener === null) {
3✔
208
                $end = $closer;
3✔
209
            } else {
210
                $end = ($nextOpener - 1);
1✔
211
            }
212
        } else {
213
            // This token is in the global scope.
214
            if ($tokens[$stackPtr]['code'] === T_BREAK) {
3✔
UNCOV
215
                return;
×
216
            }
217

218
            // Throw an error for all lines until the end of the file.
219
            $end = ($phpcsFile->numTokens - 1);
3✔
220
        }//end if
221

222
        // Find the semicolon or closing PHP tag that ends this statement,
223
        // skipping nested statements like FOR loops and closures.
224
        for ($start = ($stackPtr + 1); $start < $phpcsFile->numTokens; $start++) {
3✔
225
            if ($start === $end) {
3✔
226
                break;
3✔
227
            }
228

229
            if (isset($tokens[$start]['parenthesis_closer']) === true
3✔
230
                && $tokens[$start]['code'] === T_OPEN_PARENTHESIS
3✔
231
            ) {
232
                $start = $tokens[$start]['parenthesis_closer'];
3✔
233
                continue;
3✔
234
            }
235

236
            if (isset($tokens[$start]['bracket_closer']) === true
3✔
237
                && $tokens[$start]['code'] === T_OPEN_CURLY_BRACKET
3✔
238
            ) {
239
                $start = $tokens[$start]['bracket_closer'];
3✔
240
                continue;
3✔
241
            }
242

243
            if ($tokens[$start]['code'] === T_SEMICOLON || $tokens[$start]['code'] === T_CLOSE_TAG) {
3✔
244
                break;
3✔
245
            }
246
        }//end for
247

248
        if (isset($tokens[$start]) === false) {
3✔
UNCOV
249
            return;
×
250
        }
251

252
        $lastLine = $tokens[$start]['line'];
3✔
253
        for ($i = ($start + 1); $i < $end; $i++) {
3✔
254
            if (isset(Tokens::EMPTY_TOKENS[$tokens[$i]['code']]) === true
3✔
255
                || isset(Tokens::BRACKET_TOKENS[$tokens[$i]['code']]) === true
3✔
256
                || $tokens[$i]['code'] === T_SEMICOLON
3✔
257
            ) {
258
                continue;
3✔
259
            }
260

261
            // Skip whole functions and classes/interfaces because they are not
262
            // technically executed code, but rather declarations that may be used.
263
            if (isset(Tokens::OO_SCOPE_TOKENS[$tokens[$i]['code']]) === true
3✔
264
                || $tokens[$i]['code'] === T_FUNCTION
3✔
265
                || $tokens[$i]['code'] === T_CLOSURE
3✔
266
            ) {
267
                if (isset($tokens[$i]['scope_closer']) === false) {
3✔
268
                    // Parse error/Live coding.
269
                    return;
3✔
270
                }
271

272
                $i = $tokens[$i]['scope_closer'];
3✔
273
                continue;
3✔
274
            }
275

276
            // Skip HTML whitespace.
277
            if ($tokens[$i]['code'] === T_INLINE_HTML && trim($tokens[$i]['content']) === '') {
3✔
278
                continue;
3✔
279
            }
280

281
            // Skip PHP re-open tag (eg, after inline HTML).
282
            if ($tokens[$i]['code'] === T_OPEN_TAG) {
3✔
283
                continue;
3✔
284
            }
285

286
            $line = $tokens[$i]['line'];
3✔
287
            if ($line > $lastLine) {
3✔
288
                $type    = substr($tokens[$stackPtr]['type'], 2);
3✔
289
                $warning = 'Code after the %s statement on line %s cannot be executed';
3✔
290
                $data    = [
2✔
291
                    $type,
3✔
292
                    $tokens[$stackPtr]['line'],
3✔
293
                ];
2✔
294
                $phpcsFile->addWarning($warning, $i, 'Unreachable', $data);
3✔
295
                $lastLine = $line;
3✔
296
            }
297
        }//end for
298
    }
1✔
299
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc