• 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

99.26
/src/Standards/PSR2/Sniffs/Namespaces/UseDeclarationSniff.php
1
<?php
2
/**
3
 * Ensures USE blocks are declared correctly.
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\PSR2\Sniffs\Namespaces;
11

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

16
class UseDeclarationSniff 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 [T_USE];
3✔
28
    }
29

30

31
    /**
32
     * Processes this test, when one of its tokens is encountered.
33
     *
34
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
35
     * @param int                         $stackPtr  The position of the current token in
36
     *                                               the stack passed in $tokens.
37
     *
38
     * @return void
39
     */
40
    public function process(File $phpcsFile, int $stackPtr)
3✔
41
    {
42
        if ($this->shouldIgnoreUse($phpcsFile, $stackPtr) === true) {
3✔
43
            return;
3✔
44
        }
45

46
        $tokens = $phpcsFile->getTokens();
3✔
47

48
        // One space after the use keyword.
49
        if ($tokens[($stackPtr + 1)]['content'] !== ' ') {
3✔
50
            $error = 'There must be a single space after the USE keyword';
3✔
51
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterUse');
3✔
52
            if ($fix === true) {
3✔
53
                $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' ');
3✔
54
            }
55
        }
56

57
        // Only one USE declaration allowed per statement.
58
        $next = $phpcsFile->findNext([T_COMMA, T_SEMICOLON, T_OPEN_USE_GROUP, T_CLOSE_TAG], ($stackPtr + 1));
3✔
59
        if ($next !== false
3✔
60
            && $tokens[$next]['code'] !== T_SEMICOLON
3✔
61
            && $tokens[$next]['code'] !== T_CLOSE_TAG
3✔
62
        ) {
63
            $error = 'There must be one USE keyword per declaration';
3✔
64

65
            if ($tokens[$next]['code'] === T_COMMA) {
3✔
66
                $fix = $phpcsFile->addFixableError($error, $stackPtr, 'MultipleDeclarations');
3✔
67
                if ($fix === true) {
3✔
68
                    switch ($tokens[($stackPtr + 2)]['content']) {
3✔
69
                    case 'const':
3✔
70
                        $baseUse = 'use const';
3✔
71
                        break;
3✔
72
                    case 'function':
3✔
73
                        $baseUse = 'use function';
3✔
74
                        break;
3✔
75
                    default:
76
                        $baseUse = 'use';
3✔
77
                    }
78

79
                    if ($tokens[($next + 1)]['code'] !== T_WHITESPACE) {
3✔
80
                        $baseUse .= ' ';
3✔
81
                    }
82

83
                    $phpcsFile->fixer->replaceToken($next, ';' . $phpcsFile->eolChar . $baseUse);
3✔
84
                }
85
            } else {
86
                $closingCurly = $phpcsFile->findNext(T_CLOSE_USE_GROUP, ($next + 1));
3✔
87
                if ($closingCurly === false) {
3✔
88
                    // Parse error or live coding. Not auto-fixable.
89
                    $phpcsFile->addError($error, $stackPtr, 'MultipleDeclarations');
3✔
90
                } else {
91
                    $fix = $phpcsFile->addFixableError($error, $stackPtr, 'MultipleDeclarations');
3✔
92
                    if ($fix === true) {
3✔
93
                        $baseUse           = rtrim($phpcsFile->getTokensAsString($stackPtr, ($next - $stackPtr)));
3✔
94
                        $lastNonWhitespace = $phpcsFile->findPrevious(T_WHITESPACE, ($closingCurly - 1), null, true);
3✔
95

96
                        $phpcsFile->fixer->beginChangeset();
3✔
97

98
                        // Remove base use statement.
99
                        for ($i = $stackPtr; $i <= $next; $i++) {
3✔
100
                            $phpcsFile->fixer->replaceToken($i, '');
3✔
101
                        }
102

103
                        if (preg_match('`^[\r\n]+$`', $tokens[($next + 1)]['content']) === 1) {
3✔
104
                            $phpcsFile->fixer->replaceToken(($next + 1), '');
3✔
105
                        }
106

107
                        // Convert grouped use statements into full use statements.
108
                        do {
109
                            $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($next + 1), $closingCurly, true);
3✔
110
                            if ($next === false) {
3✔
111
                                // Group use statement with trailing comma after last item.
112
                                break;
3✔
113
                            }
114

115
                            $nonWhitespace = $phpcsFile->findPrevious(T_WHITESPACE, ($next - 1), null, true);
3✔
116
                            for ($i = ($nonWhitespace + 1); $i < $next; $i++) {
3✔
117
                                if (preg_match('`^[\r\n]+$`', $tokens[$i]['content']) === 1) {
3✔
118
                                    // Preserve new lines.
119
                                    continue;
3✔
120
                                }
121

122
                                $phpcsFile->fixer->replaceToken($i, '');
3✔
123
                            }
124

125
                            if ($tokens[$next]['content'] === 'const' || $tokens[$next]['content'] === 'function') {
3✔
126
                                $phpcsFile->fixer->addContentBefore($next, 'use ');
3✔
127
                                $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($next + 1), $closingCurly, true);
3✔
128
                                $phpcsFile->fixer->addContentBefore($next, str_replace('use ', '', $baseUse));
3✔
129
                            } else {
130
                                $phpcsFile->fixer->addContentBefore($next, $baseUse);
3✔
131
                            }
132

133
                            $next = $phpcsFile->findNext(T_COMMA, ($next + 1), $closingCurly);
3✔
134
                            if ($next !== false) {
3✔
135
                                $nextNonEmpty = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($next + 1), $closingCurly, true);
3✔
136
                                if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['line'] === $tokens[$next]['line']) {
3✔
137
                                    $prevNonWhitespace = $phpcsFile->findPrevious(T_WHITESPACE, ($nextNonEmpty - 1), $next, true);
3✔
138
                                    if ($prevNonWhitespace === $next) {
3✔
139
                                        $phpcsFile->fixer->replaceToken($next, ';' . $phpcsFile->eolChar);
3✔
140
                                    } else {
141
                                        $phpcsFile->fixer->replaceToken($next, ';');
3✔
142
                                        $phpcsFile->fixer->addNewline($prevNonWhitespace);
3✔
143
                                    }
144
                                } else {
145
                                    // Last item with trailing comma or next item already on new line.
146
                                    $phpcsFile->fixer->replaceToken($next, ';');
3✔
147
                                }
148
                            } else {
149
                                // Last item without trailing comma.
150
                                $phpcsFile->fixer->addContent($lastNonWhitespace, ';');
3✔
151
                            }
152
                        } while ($next !== false);
3✔
153

154
                        // Remove closing curly, semicolon and any whitespace between last child and closing curly.
155
                        $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($closingCurly + 1), null, true);
3✔
156
                        if ($next === false || $tokens[$next]['code'] !== T_SEMICOLON) {
3✔
157
                            // Parse error, forgotten semicolon.
158
                            $next = $closingCurly;
3✔
159
                        }
160

161
                        for ($i = ($lastNonWhitespace + 1); $i <= $next; $i++) {
3✔
162
                            $phpcsFile->fixer->replaceToken($i, '');
3✔
163
                        }
164

165
                        $phpcsFile->fixer->endChangeset();
3✔
166
                    }//end if
167
                }//end if
168
            }//end if
169
        }//end if
170

171
        // Make sure this USE comes after the first namespace declaration.
172
        $prev = $phpcsFile->findPrevious(T_NAMESPACE, ($stackPtr - 1));
3✔
173
        if ($prev === false) {
3✔
174
            $next = $phpcsFile->findNext(T_NAMESPACE, ($stackPtr + 1));
3✔
175
            if ($next !== false) {
3✔
176
                $error = 'USE declarations must go after the namespace declaration';
3✔
177
                $phpcsFile->addError($error, $stackPtr, 'UseBeforeNamespace');
3✔
178
            }
179
        }
180

181
        // Only interested in the last USE statement from here onwards.
182
        $nextUse = $phpcsFile->findNext(T_USE, ($stackPtr + 1));
3✔
183
        while ($this->shouldIgnoreUse($phpcsFile, $nextUse) === true) {
3✔
184
            $nextUse = $phpcsFile->findNext(T_USE, ($nextUse + 1));
3✔
185
            if ($nextUse === false) {
3✔
186
                break;
3✔
187
            }
188
        }
189

190
        if ($nextUse !== false) {
3✔
191
            return;
3✔
192
        }
193

194
        $end = $phpcsFile->findNext([T_SEMICOLON, T_CLOSE_USE_GROUP, T_CLOSE_TAG], ($stackPtr + 1));
3✔
195
        if ($end === false) {
3✔
196
            return;
3✔
197
        }
198

199
        if ($tokens[$end]['code'] === T_CLOSE_USE_GROUP) {
3✔
200
            $nextNonEmpty = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($end + 1), null, true);
3✔
201
            if ($tokens[$nextNonEmpty]['code'] === T_SEMICOLON) {
3✔
202
                $end = $nextNonEmpty;
3✔
203
            }
204
        }
205

206
        // Find either the start of the next line or the beginning of the next statement,
207
        // whichever comes first.
208
        for ($end = ++$end; $end < $phpcsFile->numTokens; $end++) {
3✔
209
            if (isset(Tokens::EMPTY_TOKENS[$tokens[$end]['code']]) === false) {
3✔
210
                break;
3✔
211
            }
212

213
            if ($tokens[$end]['column'] === 1) {
3✔
214
                // Reached the next line.
215
                break;
3✔
216
            }
217
        }
218

219
        --$end;
3✔
220

221
        if (($tokens[$end]['code'] === T_COMMENT
3✔
222
            || isset(Tokens::PHPCS_ANNOTATION_TOKENS[$tokens[$end]['code']]) === true)
3✔
223
            && substr($tokens[$end]['content'], 0, 2) === '/*'
3✔
224
            && substr($tokens[$end]['content'], -2) !== '*/'
3✔
225
        ) {
226
            // Multi-line block comments are not allowed as trailing comment after a use statement.
227
            --$end;
3✔
228
        }
229

230
        $next = $phpcsFile->findNext(T_WHITESPACE, ($end + 1), null, true);
3✔
231

232
        if ($next === false || $tokens[$next]['code'] === T_CLOSE_TAG) {
3✔
233
            return;
3✔
234
        }
235

236
        $diff = ($tokens[$next]['line'] - $tokens[$end]['line'] - 1);
3✔
237
        if ($diff !== 1) {
3✔
238
            if ($diff < 0) {
3✔
239
                $diff = 0;
3✔
240
            }
241

242
            $error = 'There must be one blank line after the last USE statement; %s found;';
3✔
243
            $data  = [$diff];
3✔
244
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterLastUse', $data);
3✔
245
            if ($fix === true) {
3✔
246
                if ($diff === 0) {
3✔
247
                    $phpcsFile->fixer->addNewline($end);
3✔
248
                } else {
249
                    $phpcsFile->fixer->beginChangeset();
3✔
250
                    for ($i = ($end + 1); $i < $next; $i++) {
3✔
251
                        if ($tokens[$i]['line'] === $tokens[$next]['line']) {
3✔
UNCOV
252
                            break;
×
253
                        }
254

255
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
256
                    }
257

258
                    $phpcsFile->fixer->addNewline($end);
3✔
259
                    $phpcsFile->fixer->endChangeset();
3✔
260
                }
261
            }
262
        }//end if
263
    }
1✔
264

265

266
    /**
267
     * Check if this use statement is part of the namespace block.
268
     *
269
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
270
     * @param int                         $stackPtr  The position of the current token in
271
     *                                               the stack passed in $tokens.
272
     *
273
     * @return bool
274
     */
275
    private function shouldIgnoreUse(File $phpcsFile, int $stackPtr)
3✔
276
    {
277
        $tokens = $phpcsFile->getTokens();
3✔
278

279
        // Ignore USE keywords for closures.
280
        if (isset($tokens[$stackPtr]['parenthesis_owner']) === true) {
3✔
281
            return true;
3✔
282
        }
283

284
        // Ignore USE keywords during live coding.
285
        $next = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($stackPtr + 1), null, true);
3✔
286
        if ($next === false) {
3✔
287
            return true;
3✔
288
        }
289

290
        // Ignore USE keywords for traits.
291
        if ($phpcsFile->hasCondition($stackPtr, [T_CLASS, T_TRAIT, T_ENUM]) === true) {
3✔
292
            return true;
3✔
293
        }
294

295
        return false;
3✔
296
    }
297
}
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