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

PHPCSStandards / PHPCSExtra / 15639493787

13 Jun 2025 04:35PM UTC coverage: 97.39% (-2.5%) from 99.85%
15639493787

Pull #367

github

web-flow
Merge 844746ea8 into 10343591c
Pull Request #367: Update for compatibility with PHPCS 4.0

55 of 140 new or added lines in 8 files covered. (39.29%)

1 existing line in 1 file now uncovered.

3358 of 3448 relevant lines covered (97.39%)

3.58 hits per line

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

74.75
/Universal/Sniffs/UseStatements/DisallowUseClassSniff.php
1
<?php
2
/**
3
 * PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4
 *
5
 * @package   PHPCSExtra
6
 * @copyright 2020 PHPCSExtra Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCSStandards/PHPCSExtra
9
 */
10

11
namespace PHPCSExtra\Universal\Sniffs\UseStatements;
12

13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Sniffs\Sniff;
15
use PHP_CodeSniffer\Util\Tokens;
16
use PHPCSUtils\Exceptions\ValueError;
17
use PHPCSUtils\Utils\Namespaces;
18
use PHPCSUtils\Utils\UseStatements;
19

20
/**
21
 * Disallow class/trait/interface/enum import `use` statements.
22
 *
23
 * Related sniffs:
24
 * - `Universal.UseStatements.DisallowUseFunction`
25
 * - `Universal.UseStatements.DisallowUseConst`
26
 *
27
 * @since 1.0.0
28
 */
29
final class DisallowUseClassSniff implements Sniff
30
{
31

32
    /**
33
     * Name of the "Use import source" metric.
34
     *
35
     * @since 1.0.0
36
     *
37
     * @var string
38
     */
39
    const METRIC_NAME_SRC = 'Use import statement source for class/interface/trait/enum';
40

41
    /**
42
     * Name of the "Use import with/without alias" metric.
43
     *
44
     * @since 1.0.0
45
     *
46
     * @var string
47
     */
48
    const METRIC_NAME_ALIAS = 'Use import statement for class/interface/trait/enum';
49

50
    /**
51
     * Keep track of which file is being scanned.
52
     *
53
     * @since 1.0.0
54
     *
55
     * @var string
56
     */
57
    private $currentFile = '';
58

59
    /**
60
     * Keep track of the current namespace.
61
     *
62
     * @since 1.0.0
63
     *
64
     * @var string
65
     */
66
    private $currentNamespace = '';
67

68
    /**
69
     * Returns an array of tokens this test wants to listen for.
70
     *
71
     * @since 1.0.0
72
     *
73
     * @return array<int|string>
74
     */
75
    public function register()
4✔
76
    {
77
        return [
2✔
78
            \T_USE,
4✔
79
            \T_NAMESPACE,
4✔
80
        ];
4✔
81
    }
82

83
    /**
84
     * Processes this test, when one of its tokens is encountered.
85
     *
86
     * @since 1.0.0
87
     *
88
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
89
     * @param int                         $stackPtr  The position of the current token
90
     *                                               in the stack passed in $tokens.
91
     *
92
     * @return void
93
     */
94
    public function process(File $phpcsFile, $stackPtr)
4✔
95
    {
96
        $file = $phpcsFile->getFilename();
4✔
97
        if ($file !== $this->currentFile) {
4✔
98
            // Reset the current namespace for each new file.
99
            $this->currentFile      = $file;
4✔
100
            $this->currentNamespace = '';
4✔
101
        }
2✔
102

103
        $tokens = $phpcsFile->getTokens();
4✔
104

105
        // Get the name of the current namespace.
106
        if ($tokens[$stackPtr]['code'] === \T_NAMESPACE) {
4✔
107
            $namespaceName = Namespaces::getDeclaredName($phpcsFile, $stackPtr);
4✔
108
            if ($namespaceName !== false) {
4✔
109
                $this->currentNamespace = $namespaceName;
4✔
110
            }
2✔
111

112
            return;
4✔
113
        }
114

115
        // Ok, so this is a T_USE token.
116
        try {
117
            $statements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr);
4✔
118
        } catch (ValueError $e) {
4✔
119
            // Not an import use statement. Bow out.
120
            return;
4✔
121
        }
122

123
        if (empty($statements['name'])) {
4✔
124
            // No class/trait/interface/enum import statements found.
125
            return;
4✔
126
        }
127

128
        $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1));
4✔
129

130
        foreach ($statements['name'] as $alias => $qualifiedName) {
4✔
131
            /*
132
             * Determine which token to flag.
133
             *
134
             * - If there is an alias, the alias should be flagged.
135
             * - If there isn't an alias, the class name at the end of the qualified name should be flagged
136
             *   on PHPCS 3.x and the complete qualified class name on PHPCS 4.x.
137
             */
138
            $reportPtr = $stackPtr;
4✔
139
            do {
140
                $reportPtr = $phpcsFile->findNext(\T_STRING, ($reportPtr + 1), $endOfStatement, false, $alias);
4✔
141
                if ($reportPtr === false) {
4✔
142
                    // Shouldn't be possible on 3.x, but perfectly possible on 4.x when the class is not aliased.
NEW
143
                    break;
×
144
                }
145

146
                $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($reportPtr + 1), $endOfStatement, true);
4✔
147
                if ($next !== false && $tokens[$next]['code'] === \T_NS_SEPARATOR) {
4✔
148
                    // Namespace level with same name. Continue searching.
149
                    continue;
4✔
150
                }
151

152
                break;
4✔
153
            } while (true);
4✔
154

155
            if ($reportPtr === false) {
4✔
156
                // Find the non-aliased name on PHPCS 4.x.
NEW
157
                $reportPtr = $phpcsFile->findNext(
×
NEW
158
                    [\T_NAME_QUALIFIED],
×
NEW
159
                    ($stackPtr + 1),
×
NEW
160
                    $endOfStatement,
×
NEW
161
                    false,
×
162
                    $qualifiedName
NEW
163
                );
×
164

NEW
165
                if ($reportPtr === false) {
×
166
                    // This may be an imported class (incorrectly) passed as an FQN.
NEW
167
                    $reportPtr = $phpcsFile->findNext(
×
NEW
168
                        [\T_NAME_FULLY_QUALIFIED],
×
NEW
169
                        ($stackPtr + 1),
×
NEW
170
                        $endOfStatement,
×
NEW
171
                        false,
×
172
                        '\\' . $qualifiedName
NEW
173
                    );
×
174

NEW
175
                    if ($reportPtr === false) {
×
176
                        // This may be a partial name in a group use statement.
NEW
177
                        $groupStart = $phpcsFile->findNext(\T_OPEN_USE_GROUP, ($stackPtr + 1), $endOfStatement);
×
NEW
178
                        if ($groupStart === false) {
×
179
                            // Shouldn't be possible.
180
                            continue; // @codeCoverageIgnore
181
                        }
182

NEW
183
                        for ($i = ($groupStart + 1); $i < $endOfStatement; $i++) {
×
NEW
184
                            if ($tokens[$i]['code'] === \T_NAME_QUALIFIED
×
NEW
185
                                || $tokens[$i]['code'] === \T_NAME_FULLY_QUALIFIED
×
186
                            ) {
NEW
187
                                $contentLength = \strlen($tokens[$i]['content']);
×
NEW
188
                                if (\substr($qualifiedName, -$contentLength) === $tokens[$i]['content']) {
×
NEW
189
                                    $reportPtr = $i;
×
NEW
190
                                    break;
×
191
                                }
192
                            }
193
                        }
194

NEW
195
                        if ($reportPtr === false) {
×
196
                            // Shouldn't be possible.
197
                            continue; // @codeCoverageIgnore
198
                        }
199
                    }
200
                }
201
            }
202

203
            /*
204
             * Build the error message and code.
205
             *
206
             * Check whether this is a non-namespaced (global) import and check whether this is an
207
             * import from within the same namespace.
208
             *
209
             * Takes incorrect use statements with leading backslash into account.
210
             * Takes case-INsensitivity of namespaces names into account.
211
             *
212
             * The "GlobalNamespace" error code takes precedence over the "SameNamespace" error code
213
             * in case this is a non-namespaced file.
214
             */
215

216
            $error     = 'Use import statements for class/interface/trait/enum%s are not allowed.';
4✔
217
            $error    .= ' Found import statement for: "%s"';
4✔
218
            $errorCode = 'Found';
4✔
219
            $data      = [
2✔
220
                '',
4✔
221
                $qualifiedName,
4✔
222
            ];
4✔
223

224
            $globalNamespace = false;
4✔
225
            $sameNamespace   = false;
4✔
226
            if (\strpos($qualifiedName, '\\') === false) {
4✔
227
                $globalNamespace = true;
4✔
228
                $errorCode       = 'FromGlobalNamespace';
4✔
229
                $data[0]         = ' from the global namespace';
4✔
230

231
                $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'global namespace');
4✔
232
            } elseif ($this->currentNamespace !== ''
4✔
233
                && \stripos($qualifiedName, $this->currentNamespace . '\\') === 0
4✔
234
            ) {
2✔
235
                $sameNamespace = true;
4✔
236
                $errorCode     = 'FromSameNamespace';
4✔
237
                $data[0]       = ' from the same namespace';
4✔
238

239
                $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'same namespace');
4✔
240
            } else {
2✔
241
                $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'different namespace');
4✔
242
            }
243

244
            $hasAlias = false;
4✔
245
            $lastLeaf = \strtolower(\substr($qualifiedName, -(\strlen($alias) + 1)));
4✔
246
            $aliasLC  = \strtolower($alias);
4✔
247
            if ($lastLeaf !== $aliasLC && $lastLeaf !== '\\' . $aliasLC) {
4✔
248
                $hasAlias   = true;
4✔
249
                $error     .= ' with alias: "%s"';
4✔
250
                $errorCode .= 'WithAlias';
4✔
251
                $data[]     = $alias;
4✔
252

253
                $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'with alias');
4✔
254
            } else {
2✔
255
                $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_ALIAS, 'without alias');
4✔
256
            }
257

258
            if ($errorCode === 'Found') {
4✔
259
                $errorCode = 'FoundWithoutAlias';
4✔
260
            }
2✔
261

262
            $phpcsFile->addError($error, $reportPtr, $errorCode, $data);
4✔
263
        }
2✔
264
    }
2✔
265
}
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