• 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

98.96
/src/Standards/Squiz/Sniffs/WhiteSpace/ControlStructureSpacingSniff.php
1
<?php
2
/**
3
 * Checks that control structures have the correct spacing around brackets.
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\WhiteSpace;
11

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

16
class ControlStructureSpacingSniff 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 [
2✔
28
            T_IF,
3✔
29
            T_WHILE,
3✔
30
            T_FOREACH,
3✔
31
            T_FOR,
3✔
32
            T_SWITCH,
3✔
33
            T_DO,
3✔
34
            T_ELSE,
3✔
35
            T_ELSEIF,
3✔
36
            T_TRY,
3✔
37
            T_CATCH,
3✔
38
            T_FINALLY,
3✔
39
            T_MATCH,
3✔
40
        ];
2✔
41

42
    }//end register()
43

44

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

58
        if (isset($tokens[$stackPtr]['parenthesis_opener']) === true
3✔
59
            && isset($tokens[$stackPtr]['parenthesis_closer']) === true
3✔
60
        ) {
61
            $parenOpener = $tokens[$stackPtr]['parenthesis_opener'];
3✔
62
            $parenCloser = $tokens[$stackPtr]['parenthesis_closer'];
3✔
63
            if ($tokens[($parenOpener + 1)]['code'] === T_WHITESPACE) {
3✔
64
                $gap = $tokens[($parenOpener + 1)]['length'];
3✔
65

66
                if ($gap === 0) {
3✔
67
                    $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', 'newline');
3✔
68
                    $gap = 'newline';
3✔
69
                } else {
70
                    $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', $gap);
3✔
71
                }
72

73
                $error = 'Expected 0 spaces after opening bracket; %s found';
3✔
74
                $data  = [$gap];
3✔
75
                $fix   = $phpcsFile->addFixableError($error, ($parenOpener + 1), 'SpacingAfterOpenBrace', $data);
3✔
76
                if ($fix === true) {
3✔
77
                    $phpcsFile->fixer->replaceToken(($parenOpener + 1), '');
3✔
78
                }
79
            } else {
80
                $phpcsFile->recordMetric($stackPtr, 'Spaces after control structure open parenthesis', 0);
3✔
81
            }
82

83
            if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']
3✔
84
                && $tokens[($parenCloser - 1)]['code'] === T_WHITESPACE
3✔
85
            ) {
86
                $gap   = $tokens[($parenCloser - 1)]['length'];
3✔
87
                $error = 'Expected 0 spaces before closing bracket; %s found';
3✔
88
                $data  = [$gap];
3✔
89
                $fix   = $phpcsFile->addFixableError($error, ($parenCloser - 1), 'SpaceBeforeCloseBrace', $data);
3✔
90
                if ($fix === true) {
3✔
91
                    $phpcsFile->fixer->replaceToken(($parenCloser - 1), '');
3✔
92
                }
93

94
                if ($gap === 0) {
3✔
95
                    $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', 'newline');
×
96
                } else {
97
                    $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', $gap);
3✔
98
                }
99
            } else {
100
                $phpcsFile->recordMetric($stackPtr, 'Spaces before control structure close parenthesis', 0);
3✔
101
            }
102
        }//end if
103

104
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
3✔
105
            return;
3✔
106
        }
107

108
        $scopeOpener = $tokens[$stackPtr]['scope_opener'];
3✔
109
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
3✔
110

111
        for ($firstContent = ($scopeOpener + 1); $firstContent < $phpcsFile->numTokens; $firstContent++) {
3✔
112
            $code = $tokens[$firstContent]['code'];
3✔
113

114
            if ($code === T_WHITESPACE
3✔
115
                || ($code === T_INLINE_HTML
3✔
116
                && trim($tokens[$firstContent]['content']) === '')
3✔
117
            ) {
118
                continue;
3✔
119
            }
120

121
            // Skip all empty tokens on the same line as the opener.
122
            if ($tokens[$firstContent]['line'] === $tokens[$scopeOpener]['line']
3✔
123
                && (isset(Tokens::EMPTY_TOKENS[$code]) === true
3✔
124
                || $code === T_CLOSE_TAG)
3✔
125
            ) {
126
                continue;
3✔
127
            }
128

129
            break;
3✔
130
        }
131

132
        // We ignore spacing for some structures that tend to have their own rules.
133
        $ignore = [
2✔
134
            T_FUNCTION             => true,
3✔
135
            T_CLASS                => true,
3✔
136
            T_INTERFACE            => true,
3✔
137
            T_TRAIT                => true,
3✔
138
            T_ENUM                 => true,
3✔
139
            T_DOC_COMMENT_OPEN_TAG => true,
3✔
140
        ];
2✔
141

142
        if (isset($ignore[$tokens[$firstContent]['code']]) === false
3✔
143
            && $tokens[$firstContent]['line'] >= ($tokens[$scopeOpener]['line'] + 2)
3✔
144
        ) {
145
            $gap = ($tokens[$firstContent]['line'] - $tokens[$scopeOpener]['line'] - 1);
3✔
146
            $phpcsFile->recordMetric($stackPtr, 'Blank lines at start of control structure', $gap);
3✔
147

148
            $error = 'Blank line found at start of control structure';
3✔
149
            $fix   = $phpcsFile->addFixableError($error, $scopeOpener, 'SpacingAfterOpen');
3✔
150

151
            if ($fix === true) {
3✔
152
                $phpcsFile->fixer->beginChangeset();
3✔
153
                $i = ($scopeOpener + 1);
3✔
154
                while ($tokens[$i]['line'] !== $tokens[$firstContent]['line']) {
3✔
155
                    // Start removing content from the line after the opener.
156
                    if ($tokens[$i]['line'] !== $tokens[$scopeOpener]['line']) {
3✔
157
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
158
                    }
159

160
                    $i++;
3✔
161
                }
162

163
                $phpcsFile->fixer->endChangeset();
3✔
164
            }
165
        } else {
166
            $phpcsFile->recordMetric($stackPtr, 'Blank lines at start of control structure', 0);
3✔
167
        }//end if
168

169
        if ($firstContent !== $scopeCloser) {
3✔
170
            $lastContent = $phpcsFile->findPrevious(
3✔
171
                T_WHITESPACE,
3✔
172
                ($scopeCloser - 1),
3✔
173
                null,
3✔
174
                true
3✔
175
            );
2✔
176

177
            $lastNonEmptyContent = $phpcsFile->findPrevious(
3✔
178
                Tokens::EMPTY_TOKENS,
3✔
179
                ($scopeCloser - 1),
3✔
180
                null,
3✔
181
                true
3✔
182
            );
2✔
183

184
            $checkToken = $lastContent;
3✔
185
            if (isset($tokens[$lastNonEmptyContent]['scope_condition']) === true) {
3✔
186
                $checkToken = $tokens[$lastNonEmptyContent]['scope_condition'];
3✔
187
            }
188

189
            if (isset($ignore[$tokens[$checkToken]['code']]) === false
3✔
190
                && $tokens[$lastContent]['line'] <= ($tokens[$scopeCloser]['line'] - 2)
3✔
191
            ) {
192
                $errorToken = $scopeCloser;
3✔
193
                for ($i = ($scopeCloser - 1); $i > $lastContent; $i--) {
3✔
194
                    if ($tokens[$i]['line'] < $tokens[$scopeCloser]['line']) {
3✔
195
                        $errorToken = $i;
3✔
196
                        break;
3✔
197
                    }
198
                }
199

200
                $gap = ($tokens[$scopeCloser]['line'] - $tokens[$lastContent]['line'] - 1);
3✔
201
                $phpcsFile->recordMetric($stackPtr, 'Blank lines at end of control structure', $gap);
3✔
202

203
                $error = 'Blank line found at end of control structure';
3✔
204
                $fix   = $phpcsFile->addFixableError($error, $errorToken, 'SpacingBeforeClose');
3✔
205

206
                if ($fix === true) {
3✔
207
                    $phpcsFile->fixer->beginChangeset();
3✔
208
                    for ($i = ($scopeCloser - 1); $i > $lastContent; $i--) {
3✔
209
                        if ($tokens[$i]['line'] === $tokens[$scopeCloser]['line']) {
3✔
210
                            continue;
3✔
211
                        }
212

213
                        if ($tokens[$i]['line'] === $tokens[$lastContent]['line']) {
3✔
214
                            break;
3✔
215
                        }
216

217
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
218
                    }
219

220
                    $phpcsFile->fixer->endChangeset();
3✔
221
                }
222
            } else {
223
                $phpcsFile->recordMetric($stackPtr, 'Blank lines at end of control structure', 0);
3✔
224
            }//end if
225
        }//end if
226

227
        if ($tokens[$stackPtr]['code'] === T_MATCH) {
3✔
228
            // Move the scope closer to the semicolon/comma.
229
            $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($scopeCloser + 1), null, true);
3✔
230
            if ($next !== false
3✔
231
                && ($tokens[$next]['code'] === T_SEMICOLON || $tokens[$next]['code'] === T_COMMA)
3✔
232
            ) {
233
                $scopeCloser = $next;
3✔
234
            }
235
        }
236

237
        $trailingContent = $phpcsFile->findNext(
3✔
238
            T_WHITESPACE,
3✔
239
            ($scopeCloser + 1),
3✔
240
            null,
3✔
241
            true
3✔
242
        );
2✔
243

244
        if ($tokens[$trailingContent]['code'] === T_COMMENT
3✔
245
            || isset(Tokens::PHPCS_ANNOTATION_TOKENS[$tokens[$trailingContent]['code']]) === true
3✔
246
        ) {
247
            // Special exception for code where the comment about
248
            // an ELSE or ELSEIF is written between the control structures.
249
            $nextCode = $phpcsFile->findNext(
3✔
250
                Tokens::EMPTY_TOKENS,
3✔
251
                ($scopeCloser + 1),
3✔
252
                null,
3✔
253
                true
3✔
254
            );
2✔
255

256
            if ($tokens[$nextCode]['code'] === T_ELSE
3✔
257
                || $tokens[$nextCode]['code'] === T_ELSEIF
3✔
258
                || $tokens[$trailingContent]['line'] === $tokens[$scopeCloser]['line']
3✔
259
            ) {
260
                $trailingContent = $nextCode;
3✔
261
            }
262
        }//end if
263

264
        if ($tokens[$trailingContent]['code'] === T_ELSE) {
3✔
265
            if ($tokens[$stackPtr]['code'] === T_IF) {
3✔
266
                // IF with ELSE.
267
                return;
3✔
268
            }
269
        }
270

271
        if ($tokens[$trailingContent]['code'] === T_WHILE
3✔
272
            && $tokens[$stackPtr]['code'] === T_DO
3✔
273
        ) {
274
            // DO with WHILE.
275
            return;
3✔
276
        }
277

278
        if ($tokens[$trailingContent]['code'] === T_CLOSE_TAG) {
3✔
279
            // At the end of the script or embedded code.
280
            return;
3✔
281
        }
282

283
        if (isset($tokens[$trailingContent]['scope_condition']) === true
3✔
284
            && $tokens[$trailingContent]['scope_condition'] !== $trailingContent
3✔
285
            && isset($tokens[$trailingContent]['scope_opener']) === true
3✔
286
            && $tokens[$trailingContent]['scope_opener'] !== $trailingContent
3✔
287
        ) {
288
            // Another control structure's closing brace.
289
            $owner = $tokens[$trailingContent]['scope_condition'];
3✔
290
            if ($tokens[$owner]['code'] === T_FUNCTION) {
3✔
291
                // The next content is the closing brace of a function
292
                // so normal function rules apply and we can ignore it.
293
                return;
3✔
294
            }
295

296
            if ($tokens[$owner]['code'] === T_CLOSURE
3✔
297
                && ($phpcsFile->hasCondition($stackPtr, [T_FUNCTION, T_CLOSURE]) === true
2✔
298
                || isset($tokens[$stackPtr]['nested_parenthesis']) === true)
3✔
299
            ) {
300
                return;
×
301
            }
302

303
            if ($tokens[$trailingContent]['line'] !== ($tokens[$scopeCloser]['line'] + 1)) {
3✔
304
                $error = 'Blank line found after control structure';
3✔
305
                $fix   = $phpcsFile->addFixableError($error, $scopeCloser, 'LineAfterClose');
3✔
306

307
                if ($fix === true) {
3✔
308
                    $phpcsFile->fixer->beginChangeset();
3✔
309
                    $i = ($scopeCloser + 1);
3✔
310
                    while ($tokens[$i]['line'] !== $tokens[$trailingContent]['line']) {
3✔
311
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
312
                        $i++;
3✔
313
                    }
314

315
                    $phpcsFile->fixer->addNewline($scopeCloser);
3✔
316
                    $phpcsFile->fixer->endChangeset();
3✔
317
                }
318
            }
319
        } else if ($tokens[$trailingContent]['code'] !== T_ELSE
3✔
320
            && $tokens[$trailingContent]['code'] !== T_ELSEIF
3✔
321
            && $tokens[$trailingContent]['code'] !== T_CATCH
3✔
322
            && $tokens[$trailingContent]['code'] !== T_FINALLY
3✔
323
            && $tokens[$trailingContent]['line'] === ($tokens[$scopeCloser]['line'] + 1)
3✔
324
        ) {
325
            $error = 'No blank line found after control structure';
3✔
326
            $fix   = $phpcsFile->addFixableError($error, $scopeCloser, 'NoLineAfterClose');
3✔
327
            if ($fix === true) {
3✔
328
                $trailingContent = $phpcsFile->findNext(
3✔
329
                    T_WHITESPACE,
3✔
330
                    ($scopeCloser + 1),
3✔
331
                    null,
3✔
332
                    true
3✔
333
                );
2✔
334

335
                if (($tokens[$trailingContent]['code'] === T_COMMENT
3✔
336
                    || isset(Tokens::PHPCS_ANNOTATION_TOKENS[$tokens[$trailingContent]['code']]) === true)
3✔
337
                    && $tokens[$trailingContent]['line'] === $tokens[$scopeCloser]['line']
3✔
338
                ) {
339
                    $phpcsFile->fixer->addNewline($trailingContent);
3✔
340
                } else {
341
                    $phpcsFile->fixer->addNewline($scopeCloser);
3✔
342
                }
343
            }
344
        }//end if
345

346
    }//end process()
1✔
347

348

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