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

PHPCSStandards / PHP_CodeSniffer / 17174734458

23 Aug 2025 11:06AM UTC coverage: 76.88% (-2.1%) from 78.934%
17174734458

push

github

jrfnl
TEMP/TESTING PHPUnit 6331

19187 of 24957 relevant lines covered (76.88%)

60.25 hits per line

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

95.38
/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
29
     */
30
    private $expressionTokens = [
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()
2✔
42
    {
43
        return [
2✔
44
            T_BREAK,
2✔
45
            T_CONTINUE,
2✔
46
            T_RETURN,
2✔
47
            T_THROW,
2✔
48
            T_EXIT,
2✔
49
            T_GOTO,
2✔
50
        ];
2✔
51

52
    }//end register()
53

54

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

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

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

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

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

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

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

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

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

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

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

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

171
        $ourConditions = array_keys($tokens[$stackPtr]['conditions']);
2✔
172

173
        if (empty($ourConditions) === false) {
2✔
174
            $condition = array_pop($ourConditions);
2✔
175

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

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

191
            $closer = $tokens[$condition]['scope_closer'];
2✔
192

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

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

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

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

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

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

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

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

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

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

273
                $i = $tokens[$i]['scope_closer'];
2✔
274
                continue;
2✔
275
            }
276

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

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

287
            $line = $tokens[$i]['line'];
2✔
288
            if ($line > $lastLine) {
2✔
289
                $type    = substr($tokens[$stackPtr]['type'], 2);
2✔
290
                $warning = 'Code after the %s statement on line %s cannot be executed';
2✔
291
                $data    = [
2✔
292
                    $type,
2✔
293
                    $tokens[$stackPtr]['line'],
2✔
294
                ];
2✔
295
                $phpcsFile->addWarning($warning, $i, 'Unreachable', $data);
2✔
296
                $lastLine = $line;
2✔
297
            }
298
        }//end for
299

300
    }//end process()
301

302

303
}//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

© 2026 Coveralls, Inc