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

PHPCSStandards / PHP_CodeSniffer / 15114979259

19 May 2025 01:58PM UTC coverage: 78.674% (+0.04%) from 78.632%
15114979259

Pull #880

github

web-flow
Merge 17601e27d into ad852ee16
Pull Request #880: Generic/InlineControlStructure: bail early for control structures without body

48 of 48 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

19663 of 24993 relevant lines covered (78.67%)

88.73 hits per line

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

96.03
/src/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php
1
<?php
2
/**
3
 * Verifies that inline control statements are not present.
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\Generic\Sniffs\ControlStructures;
11

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

16
class InlineControlStructureSniff implements Sniff
17
{
18

19
    /**
20
     * If true, an error will be thrown; otherwise a warning.
21
     *
22
     * @var boolean
23
     */
24
    public $error = true;
25

26

27
    /**
28
     * Returns an array of tokens this test wants to listen for.
29
     *
30
     * @return array<int|string>
31
     */
32
    public function register()
3✔
33
    {
34
        return [
2✔
35
            T_IF,
3✔
36
            T_ELSE,
3✔
37
            T_ELSEIF,
3✔
38
            T_FOREACH,
3✔
39
            T_WHILE,
3✔
40
            T_DO,
3✔
41
            T_FOR,
3✔
42
        ];
2✔
43

44
    }//end register()
45

46

47
    /**
48
     * Processes this test, when one of its tokens is encountered.
49
     *
50
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
51
     * @param int                         $stackPtr  The position of the current token in the
52
     *                                               stack passed in $tokens.
53
     *
54
     * @return void|int
55
     */
56
    public function process(File $phpcsFile, $stackPtr)
3✔
57
    {
58
        $tokens = $phpcsFile->getTokens();
3✔
59

60
        if (isset($tokens[$stackPtr]['scope_opener']) === true) {
3✔
61
            $phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'no');
3✔
62
            return;
3✔
63
        }
64

65
        // Ignore the ELSE in ELSE IF. We'll process the IF part later.
66
        if ($tokens[$stackPtr]['code'] === T_ELSE) {
3✔
67
            $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($stackPtr + 1), null, true);
3✔
68
            if ($tokens[$next]['code'] === T_IF) {
3✔
69
                return;
3✔
70
            }
71
        }
72

73
        if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
3✔
74
            $nextTokenIndex = ($tokens[$stackPtr]['parenthesis_closer'] + 1);
3✔
75
        } else if (in_array($tokens[$stackPtr]['code'], [T_ELSE, T_DO], true) === true) {
3✔
76
            $nextTokenIndex = ($stackPtr + 1);
3✔
77
        }
78

79
        if (isset($nextTokenIndex) === true) {
3✔
80
            $firstNonEmptyToken = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, $nextTokenIndex, null, true);
3✔
81
            if ($firstNonEmptyToken === false) {
3✔
82
                // Live coding.
83
                return;
3✔
84
            }
85

86
            if ($tokens[$firstNonEmptyToken]['code'] === T_SEMICOLON) {
3✔
87
                // This is a control structure without a body. Bow out.
88
                $phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'no');
3✔
89
                return;
3✔
90
            }
91
        }
92

93
        if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false
3✔
94
            && $tokens[$stackPtr]['code'] !== T_ELSE
3✔
95
        ) {
96
            if ($tokens[$stackPtr]['code'] !== T_DO) {
3✔
97
                // Live coding or parse error.
98
                return;
3✔
99
            }
100

101
            $nextWhile = $phpcsFile->findNext(T_WHILE, ($stackPtr + 1));
3✔
102
            if ($nextWhile !== false
3✔
103
                && isset($tokens[$nextWhile]['parenthesis_opener'], $tokens[$nextWhile]['parenthesis_closer']) === false
3✔
104
            ) {
105
                // Live coding or parse error.
106
                return;
3✔
107
            }
108

109
            unset($nextWhile);
×
110
        }
111

112
        $start = $stackPtr;
3✔
113
        if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
3✔
114
            $start = $tokens[$stackPtr]['parenthesis_closer'];
3✔
115
        }
116

117
        $nextNonEmpty = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($start + 1), null, true);
3✔
118
        if ($nextNonEmpty === false) {
3✔
119
            // Live coding or parse error.
UNCOV
120
            return;
×
121
        }
122

123
        if ($tokens[$nextNonEmpty]['code'] === T_OPEN_CURLY_BRACKET
3✔
124
            || $tokens[$nextNonEmpty]['code'] === T_COLON
3✔
125
        ) {
126
            // T_CLOSE_CURLY_BRACKET missing, or alternative control structure with
127
            // T_END... missing. Either live coding, parse error or end
128
            // tag in short open tags and scan run with short_open_tag=Off.
129
            // Bow out completely as any further detection will be unreliable
130
            // and create incorrect fixes or cause fixer conflicts.
131
            return $phpcsFile->numTokens;
2✔
132
        }
133

134
        unset($nextNonEmpty, $start);
3✔
135

136
        // This is a control structure without an opening brace,
137
        // so it is an inline statement.
138
        if ($this->error === true) {
3✔
139
            $fix = $phpcsFile->addFixableError('Inline control structures are not allowed', $stackPtr, 'NotAllowed');
3✔
140
        } else {
141
            $fix = $phpcsFile->addFixableWarning('Inline control structures are discouraged', $stackPtr, 'Discouraged');
×
142
        }
143

144
        $phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'yes');
3✔
145

146
        // Stop here if we are not fixing the error.
147
        if ($fix !== true) {
3✔
148
            return;
3✔
149
        }
150

151
        $phpcsFile->fixer->beginChangeset();
3✔
152
        if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
3✔
153
            $closer = $tokens[$stackPtr]['parenthesis_closer'];
3✔
154
        } else {
155
            $closer = $stackPtr;
3✔
156
        }
157

158
        if ($tokens[($closer + 1)]['code'] === T_WHITESPACE) {
3✔
159
            $phpcsFile->fixer->addContent($closer, ' {');
3✔
160
        } else {
161
            $phpcsFile->fixer->addContent($closer, ' { ');
×
162
        }
163

164
        $fixableScopeOpeners = $this->register();
3✔
165

166
        $lastNonEmpty = $closer;
3✔
167
        for ($end = ($closer + 1); $end < $phpcsFile->numTokens; $end++) {
3✔
168
            if ($tokens[$end]['code'] === T_SEMICOLON) {
3✔
169
                break;
3✔
170
            }
171

172
            if ($tokens[$end]['code'] === T_CLOSE_TAG) {
3✔
173
                $end = $lastNonEmpty;
3✔
174
                break;
3✔
175
            }
176

177
            if (in_array($tokens[$end]['code'], $fixableScopeOpeners, true) === true
3✔
178
                && isset($tokens[$end]['scope_opener']) === false
3✔
179
            ) {
180
                // The best way to fix nested inline scopes is middle-out.
181
                // So skip this one. It will be detected and fixed on a future loop.
182
                $phpcsFile->fixer->rollbackChangeset();
3✔
183
                return;
3✔
184
            }
185

186
            if (isset($tokens[$end]['scope_opener']) === true) {
3✔
187
                $type = $tokens[$end]['code'];
3✔
188
                $end  = $tokens[$end]['scope_closer'];
3✔
189
                if ($type === T_DO
3✔
190
                    || $type === T_IF || $type === T_ELSEIF
3✔
191
                    || $type === T_TRY || $type === T_CATCH || $type === T_FINALLY
3✔
192
                ) {
193
                    $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($end + 1), null, true);
3✔
194
                    if ($next === false) {
3✔
195
                        break;
×
196
                    }
197

198
                    $nextType = $tokens[$next]['code'];
3✔
199

200
                    // Let additional conditions loop and find their ending.
201
                    if (($type === T_IF
3✔
202
                        || $type === T_ELSEIF)
3✔
203
                        && ($nextType === T_ELSEIF
3✔
204
                        || $nextType === T_ELSE)
3✔
205
                    ) {
206
                        continue;
3✔
207
                    }
208

209
                    // Account for TRY... CATCH/FINALLY statements.
210
                    if (($type === T_TRY
3✔
211
                        || $type === T_CATCH
3✔
212
                        || $type === T_FINALLY)
3✔
213
                        && ($nextType === T_CATCH
3✔
214
                        || $nextType === T_FINALLY)
3✔
215
                    ) {
216
                        continue;
3✔
217
                    }
218

219
                    // Account for DO... WHILE conditions.
220
                    if ($type === T_DO && $nextType === T_WHILE) {
3✔
221
                        $end = $phpcsFile->findNext(T_SEMICOLON, ($next + 1));
3✔
222
                    }
223
                } else if ($type === T_CLOSURE) {
3✔
224
                    // There should be a semicolon after the closing brace.
225
                    $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($end + 1), null, true);
3✔
226
                    if ($next !== false && $tokens[$next]['code'] === T_SEMICOLON) {
3✔
227
                        $end = $next;
3✔
228
                    }
229
                }//end if
230

231
                if ($tokens[$end]['code'] !== T_END_HEREDOC
3✔
232
                    && $tokens[$end]['code'] !== T_END_NOWDOC
3✔
233
                ) {
234
                    break;
3✔
235
                }
236
            }//end if
237

238
            if (isset($tokens[$end]['parenthesis_closer']) === true) {
3✔
239
                $end          = $tokens[$end]['parenthesis_closer'];
3✔
240
                $lastNonEmpty = $end;
3✔
241
                continue;
3✔
242
            }
243

244
            if ($tokens[$end]['code'] !== T_WHITESPACE) {
3✔
245
                $lastNonEmpty = $end;
3✔
246
            }
247
        }//end for
248

249
        if ($end === $phpcsFile->numTokens) {
3✔
250
            $end = $lastNonEmpty;
×
251
        }
252

253
        $nextContent = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($end + 1), null, true);
3✔
254

255
        if ($nextContent === false || $tokens[$nextContent]['line'] !== $tokens[$end]['line']) {
3✔
256
            // Account for a comment on the end of the line.
257
            for ($endLine = $end; $endLine < $phpcsFile->numTokens; $endLine++) {
3✔
258
                if (isset($tokens[($endLine + 1)]) === false
3✔
259
                    || $tokens[$endLine]['line'] !== $tokens[($endLine + 1)]['line']
3✔
260
                ) {
261
                    break;
3✔
262
                }
263
            }
264

265
            if (isset(Tokens::COMMENT_TOKENS[$tokens[$endLine]['code']]) === false
3✔
266
                && ($tokens[$endLine]['code'] !== T_WHITESPACE
3✔
267
                || isset(Tokens::COMMENT_TOKENS[$tokens[($endLine - 1)]['code']]) === false)
3✔
268
            ) {
269
                $endLine = $end;
3✔
270
            }
271
        } else {
272
            $endLine = $end;
3✔
273
        }
274

275
        if ($endLine !== $end) {
3✔
276
            $endToken     = $endLine;
3✔
277
            $addedContent = '';
3✔
278
        } else {
279
            $endToken     = $end;
3✔
280
            $addedContent = $phpcsFile->eolChar;
3✔
281

282
            if ($tokens[$end]['code'] !== T_SEMICOLON
3✔
283
                && $tokens[$end]['code'] !== T_CLOSE_CURLY_BRACKET
3✔
284
            ) {
285
                $phpcsFile->fixer->addContent($end, '; ');
3✔
286
            }
287
        }
288

289
        $next = $phpcsFile->findNext(T_WHITESPACE, ($endToken + 1), null, true);
3✔
290
        if ($next !== false
3✔
291
            && ($tokens[$next]['code'] === T_ELSE
3✔
292
            || $tokens[$next]['code'] === T_ELSEIF)
3✔
293
        ) {
294
            $phpcsFile->fixer->addContentBefore($next, '} ');
3✔
295
        } else {
296
            $indent = '';
3✔
297
            for ($first = $stackPtr; $first > 0; $first--) {
3✔
298
                if ($tokens[$first]['column'] === 1) {
3✔
299
                    break;
3✔
300
                }
301
            }
302

303
            if ($tokens[$first]['code'] === T_WHITESPACE) {
3✔
304
                $indent = $tokens[$first]['content'];
3✔
305
            } else if ($tokens[$first]['code'] === T_INLINE_HTML
3✔
306
                || $tokens[$first]['code'] === T_OPEN_TAG
3✔
307
            ) {
308
                $addedContent = '';
3✔
309
            }
310

311
            $addedContent .= $indent.'}';
3✔
312
            if ($next !== false && $tokens[$endToken]['code'] === T_COMMENT) {
3✔
313
                $addedContent .= $phpcsFile->eolChar;
3✔
314
            }
315

316
            $phpcsFile->fixer->addContent($endToken, $addedContent);
3✔
317
        }//end if
318

319
        $phpcsFile->fixer->endChangeset();
3✔
320

321
    }//end process()
1✔
322

323

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