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

PHPCSStandards / PHP_CodeSniffer / 13863904400

14 Mar 2025 07:24PM UTC coverage: 78.718% (+0.03%) from 78.685%
13863904400

Pull #880

github

web-flow
Merge 2f44e1660 into 9feb4e58c
Pull Request #880: Generic/InlineControlStructure: bail early for control structures without body

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

1 existing line in 1 file now uncovered.

24834 of 31548 relevant lines covered (78.72%)

66.37 hits per line

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

97.35
/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
     * A list of tokenizers this sniff supports.
21
     *
22
     * @var array
23
     */
24
    public $supportedTokenizers = [
25
        'PHP',
26
        'JS',
27
    ];
28

29
    /**
30
     * If true, an error will be thrown; otherwise a warning.
31
     *
32
     * @var boolean
33
     */
34
    public $error = true;
35

36

37
    /**
38
     * Returns an array of tokens this test wants to listen for.
39
     *
40
     * @return array<int|string>
41
     */
42
    public function register()
3✔
43
    {
44
        return [
1✔
45
            T_IF,
3✔
46
            T_ELSE,
3✔
47
            T_ELSEIF,
3✔
48
            T_FOREACH,
3✔
49
            T_WHILE,
3✔
50
            T_DO,
3✔
51
            T_FOR,
3✔
52
        ];
2✔
53

54
    }//end register()
55

56

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

70
        if (isset($tokens[$stackPtr]['scope_opener']) === true) {
3✔
71
            $phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'no');
3✔
72
            return;
3✔
73
        }
74

75
        // Ignore the ELSE in ELSE IF. We'll process the IF part later.
76
        if ($tokens[$stackPtr]['code'] === T_ELSE) {
3✔
77
            $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
3✔
78
            if ($tokens[$next]['code'] === T_IF) {
3✔
79
                return;
3✔
80
            }
81
        }
1✔
82

83
        if ($tokens[$stackPtr]['code'] !== T_DO) {
3✔
84
            // This could be from a DO WHILE, which doesn't have an opening brace, or an
85
            // else/elseif/if/for/foreach/while without body.
86
            if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
3✔
87
                $nextTokenIndex = ($tokens[$stackPtr]['parenthesis_closer'] + 1);
3✔
88
            } else if ($tokens[$stackPtr]['code'] === T_ELSE) {
3✔
89
                $nextTokenIndex = ($stackPtr + 1);
3✔
90
            }
1✔
91

92
            if (isset($nextTokenIndex) === true) {
3✔
93
                $afterElseOrParensCloser = $phpcsFile->findNext(Tokens::$emptyTokens, $nextTokenIndex, null, true);
3✔
94
                if ($afterElseOrParensCloser === false) {
3✔
95
                    // Live coding.
96
                    return;
3✔
97
                }
98

99
                if ($tokens[$afterElseOrParensCloser]['code'] === T_SEMICOLON) {
3✔
100
                    $phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'no');
3✔
101
                    return;
3✔
102
                }
103
            }
1✔
104
        }//end if
1✔
105

106
        if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false
3✔
107
            && $tokens[$stackPtr]['code'] !== T_ELSE
3✔
108
        ) {
1✔
109
            if ($tokens[$stackPtr]['code'] !== T_DO) {
3✔
110
                // Live coding or parse error.
111
                return;
3✔
112
            }
113

114
            $nextWhile = $phpcsFile->findNext(T_WHILE, ($stackPtr + 1));
3✔
115
            if ($nextWhile !== false
2✔
116
                && isset($tokens[$nextWhile]['parenthesis_opener'], $tokens[$nextWhile]['parenthesis_closer']) === false
3✔
117
            ) {
1✔
118
                // Live coding or parse error.
119
                return;
3✔
120
            }
121

122
            unset($nextWhile);
3✔
123
        }
1✔
124

125
        $start = $stackPtr;
3✔
126
        if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
3✔
127
            $start = $tokens[$stackPtr]['parenthesis_closer'];
3✔
128
        }
1✔
129

130
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($start + 1), null, true);
3✔
131
        if ($nextNonEmpty === false) {
3✔
132
            // Live coding or parse error.
UNCOV
133
            return;
×
134
        }
135

136
        if ($tokens[$nextNonEmpty]['code'] === T_OPEN_CURLY_BRACKET
3✔
137
            || $tokens[$nextNonEmpty]['code'] === T_COLON
3✔
138
        ) {
1✔
139
            // T_CLOSE_CURLY_BRACKET missing, or alternative control structure with
140
            // T_END... missing. Either live coding, parse error or end
141
            // tag in short open tags and scan run with short_open_tag=Off.
142
            // Bow out completely as any further detection will be unreliable
143
            // and create incorrect fixes or cause fixer conflicts.
144
            return $phpcsFile->numTokens;
2✔
145
        }
146

147
        unset($nextNonEmpty, $start);
3✔
148

149
        // This is a control structure without an opening brace,
150
        // so it is an inline statement.
151
        if ($this->error === true) {
3✔
152
            $fix = $phpcsFile->addFixableError('Inline control structures are not allowed', $stackPtr, 'NotAllowed');
3✔
153
        } else {
1✔
154
            $fix = $phpcsFile->addFixableWarning('Inline control structures are discouraged', $stackPtr, 'Discouraged');
×
155
        }
156

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

159
        // Stop here if we are not fixing the error.
160
        if ($fix !== true) {
3✔
161
            return;
3✔
162
        }
163

164
        $phpcsFile->fixer->beginChangeset();
3✔
165
        if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
3✔
166
            $closer = $tokens[$stackPtr]['parenthesis_closer'];
3✔
167
        } else {
1✔
168
            $closer = $stackPtr;
3✔
169
        }
170

171
        if ($tokens[($closer + 1)]['code'] === T_WHITESPACE) {
3✔
172
            $phpcsFile->fixer->addContent($closer, ' {');
3✔
173
        } else {
1✔
174
            $phpcsFile->fixer->addContent($closer, ' { ');
×
175
        }
176

177
        $fixableScopeOpeners = $this->register();
3✔
178

179
        $lastNonEmpty = $closer;
3✔
180
        for ($end = ($closer + 1); $end < $phpcsFile->numTokens; $end++) {
3✔
181
            if ($tokens[$end]['code'] === T_SEMICOLON) {
3✔
182
                break;
3✔
183
            }
184

185
            if ($tokens[$end]['code'] === T_CLOSE_TAG) {
3✔
186
                $end = $lastNonEmpty;
3✔
187
                break;
3✔
188
            }
189

190
            if (in_array($tokens[$end]['code'], $fixableScopeOpeners, true) === true
3✔
191
                && isset($tokens[$end]['scope_opener']) === false
3✔
192
            ) {
1✔
193
                // The best way to fix nested inline scopes is middle-out.
194
                // So skip this one. It will be detected and fixed on a future loop.
195
                $phpcsFile->fixer->rollbackChangeset();
3✔
196
                return;
3✔
197
            }
198

199
            if (isset($tokens[$end]['scope_opener']) === true) {
3✔
200
                $type = $tokens[$end]['code'];
3✔
201
                $end  = $tokens[$end]['scope_closer'];
3✔
202
                if ($type === T_DO
2✔
203
                    || $type === T_IF || $type === T_ELSEIF
3✔
204
                    || $type === T_TRY || $type === T_CATCH || $type === T_FINALLY
3✔
205
                ) {
1✔
206
                    $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true);
3✔
207
                    if ($next === false) {
3✔
208
                        break;
×
209
                    }
210

211
                    $nextType = $tokens[$next]['code'];
3✔
212

213
                    // Let additional conditions loop and find their ending.
214
                    if (($type === T_IF
2✔
215
                        || $type === T_ELSEIF)
3✔
216
                        && ($nextType === T_ELSEIF
3✔
217
                        || $nextType === T_ELSE)
3✔
218
                    ) {
1✔
219
                        continue;
3✔
220
                    }
221

222
                    // Account for TRY... CATCH/FINALLY statements.
223
                    if (($type === T_TRY
2✔
224
                        || $type === T_CATCH
3✔
225
                        || $type === T_FINALLY)
3✔
226
                        && ($nextType === T_CATCH
3✔
227
                        || $nextType === T_FINALLY)
3✔
228
                    ) {
1✔
229
                        continue;
3✔
230
                    }
231

232
                    // Account for DO... WHILE conditions.
233
                    if ($type === T_DO && $nextType === T_WHILE) {
3✔
234
                        $end = $phpcsFile->findNext(T_SEMICOLON, ($next + 1));
3✔
235
                    }
1✔
236
                } else if ($type === T_CLOSURE) {
3✔
237
                    // There should be a semicolon after the closing brace.
238
                    $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true);
3✔
239
                    if ($next !== false && $tokens[$next]['code'] === T_SEMICOLON) {
3✔
240
                        $end = $next;
3✔
241
                    }
1✔
242
                }//end if
1✔
243

244
                if ($tokens[$end]['code'] !== T_END_HEREDOC
3✔
245
                    && $tokens[$end]['code'] !== T_END_NOWDOC
3✔
246
                ) {
1✔
247
                    break;
3✔
248
                }
249
            }//end if
1✔
250

251
            if (isset($tokens[$end]['parenthesis_closer']) === true) {
3✔
252
                $end          = $tokens[$end]['parenthesis_closer'];
3✔
253
                $lastNonEmpty = $end;
3✔
254
                continue;
3✔
255
            }
256

257
            if ($tokens[$end]['code'] !== T_WHITESPACE) {
3✔
258
                $lastNonEmpty = $end;
3✔
259
            }
1✔
260
        }//end for
1✔
261

262
        if ($end === $phpcsFile->numTokens) {
3✔
263
            $end = $lastNonEmpty;
×
264
        }
265

266
        $nextContent = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true);
3✔
267

268
        if ($nextContent === false || $tokens[$nextContent]['line'] !== $tokens[$end]['line']) {
3✔
269
            // Account for a comment on the end of the line.
270
            for ($endLine = $end; $endLine < $phpcsFile->numTokens; $endLine++) {
3✔
271
                if (isset($tokens[($endLine + 1)]) === false
3✔
272
                    || $tokens[$endLine]['line'] !== $tokens[($endLine + 1)]['line']
3✔
273
                ) {
1✔
274
                    break;
3✔
275
                }
276
            }
1✔
277

278
            if (isset(Tokens::$commentTokens[$tokens[$endLine]['code']]) === false
3✔
279
                && ($tokens[$endLine]['code'] !== T_WHITESPACE
3✔
280
                || isset(Tokens::$commentTokens[$tokens[($endLine - 1)]['code']]) === false)
3✔
281
            ) {
1✔
282
                $endLine = $end;
3✔
283
            }
1✔
284
        } else {
1✔
285
            $endLine = $end;
3✔
286
        }
287

288
        if ($endLine !== $end) {
3✔
289
            $endToken     = $endLine;
3✔
290
            $addedContent = '';
3✔
291
        } else {
1✔
292
            $endToken     = $end;
3✔
293
            $addedContent = $phpcsFile->eolChar;
3✔
294

295
            if ($tokens[$end]['code'] !== T_SEMICOLON
3✔
296
                && $tokens[$end]['code'] !== T_CLOSE_CURLY_BRACKET
3✔
297
            ) {
1✔
298
                $phpcsFile->fixer->addContent($end, '; ');
3✔
299
            }
1✔
300
        }
301

302
        $next = $phpcsFile->findNext(T_WHITESPACE, ($endToken + 1), null, true);
3✔
303
        if ($next !== false
2✔
304
            && ($tokens[$next]['code'] === T_ELSE
3✔
305
            || $tokens[$next]['code'] === T_ELSEIF)
3✔
306
        ) {
1✔
307
            $phpcsFile->fixer->addContentBefore($next, '} ');
3✔
308
        } else {
1✔
309
            $indent = '';
3✔
310
            for ($first = $stackPtr; $first > 0; $first--) {
3✔
311
                if ($tokens[$first]['column'] === 1) {
3✔
312
                    break;
3✔
313
                }
314
            }
1✔
315

316
            if ($tokens[$first]['code'] === T_WHITESPACE) {
3✔
317
                $indent = $tokens[$first]['content'];
3✔
318
            } else if ($tokens[$first]['code'] === T_INLINE_HTML
3✔
319
                || $tokens[$first]['code'] === T_OPEN_TAG
3✔
320
            ) {
1✔
321
                $addedContent = '';
3✔
322
            }
1✔
323

324
            $addedContent .= $indent.'}';
3✔
325
            if ($next !== false && $tokens[$endToken]['code'] === T_COMMENT) {
3✔
326
                $addedContent .= $phpcsFile->eolChar;
3✔
327
            }
1✔
328

329
            $phpcsFile->fixer->addContent($endToken, $addedContent);
3✔
330
        }//end if
331

332
        $phpcsFile->fixer->endChangeset();
3✔
333

334
    }//end process()
2✔
335

336

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