UnnecessaryNamespaceUsageSniff::process()   C
last analyzed

Complexity

Conditions 10
Paths 4

Size

Total Lines 123
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 72
c 2
b 0
f 0
nc 4
nop 2
dl 0
loc 123
rs 6.7442

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of the mo4-coding-standard (phpcs standard)
5
 *
6
 * @author  Xaver Loppenstedt <[email protected]>
7
 *
8
 * @license http://spdx.org/licenses/MIT MIT License
9
 *
10
 * @link    https://github.com/mayflower/mo4-coding-standard
11
 */
12
13
declare(strict_types=1);
14
15
namespace MO4\Sniffs\Formatting;
16
17
use MO4\Library\PregLibrary;
18
use PHP_CodeSniffer\Exceptions\RuntimeException;
19
use PHP_CodeSniffer\Files\File;
20
use PHP_CodeSniffer\Sniffs\Sniff;
21
use PHP_CodeSniffer\Util\Tokens as PHP_CodeSniffer_Tokens;
22
23
/**
24
 * Unnecessary Namespace Usage sniff.
25
 *
26
 * Full namespace declaration should be skipped in favour of the short declaration.
27
 *
28
 * @author    Xaver Loppenstedt <[email protected]>
29
 * @author    Marco Jantke <[email protected]>
30
 * @author    Steffen Ritter <[email protected]>
31
 *
32
 * @copyright 2013 Xaver Loppenstedt, some rights reserved.
33
 *
34
 * @license   http://spdx.org/licenses/MIT MIT License
35
 *
36
 * @link      https://github.com/mayflower/mo4-coding-standard
37
 */
38
class UnnecessaryNamespaceUsageSniff implements Sniff
39
{
40
    /**
41
     * Tokens used in full class name.
42
     *
43
     * @var array<int, int>
44
     */
45
    private $classNameTokens = [
46
        T_NS_SEPARATOR,
47
        T_STRING,
48
    ];
49
50
    /**
51
     * Registers the tokens that this sniff wants to listen for.
52
     *
53
     * @return array<int, int>
54
     *
55
     * @see    Tokens.php
56
     */
57
    public function register(): array
58
    {
59
        return [T_CLASS];
60
    }
61
62
    /**
63
     * Called when one of the token types that this sniff is listening for
64
     * is found.
65
     *
66
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint
67
     *
68
     * @param File $phpcsFile The PHP_CodeSniffer file where the
69
     *                        token was found.
70
     * @param int  $stackPtr  The position in the PHP_CodeSniffer
71
     *                        file's token stack where the token
72
     *                        was found.
73
     *
74
     * @return void
75
     *
76
     * @throws RuntimeException
77
     */
78
    public function process(File $phpcsFile, $stackPtr): void
79
    {
80
        $docCommentTags = [
81
            '@param'  => 1,
82
            '@return' => 1,
83
            '@throws' => 1,
84
            '@var'    => 2,
85
        ];
86
        $scanTokens     = [
87
            T_NS_SEPARATOR,
88
            T_DOC_COMMENT_OPEN_TAG,
89
        ];
90
91
        $tokens        = $phpcsFile->getTokens();
92
        $useStatements = $this->getUseStatements($phpcsFile, 0, ($stackPtr - 1));
93
        $namespace     = $this->getNamespace($phpcsFile, 0, ($stackPtr - 1));
94
95
        $nsSep = $phpcsFile->findNext($scanTokens, ($stackPtr + 1));
96
97
        while (false !== $nsSep) {
98
            $classNameEnd = (int) $phpcsFile->findNext(
99
                $this->classNameTokens,
100
                $nsSep,
101
                null,
102
                true
103
            );
104
105
            if (T_NS_SEPARATOR === $tokens[$nsSep]['code']) {
106
                if (T_STRING === $tokens[($nsSep - 1)]['code']) {
107
                    --$nsSep;
108
                }
109
110
                $className = $phpcsFile->getTokensAsString(
111
                    $nsSep,
112
                    ($classNameEnd - $nsSep)
113
                );
114
115
                $this->checkShorthandPossible(
116
                    $phpcsFile,
117
                    $useStatements,
118
                    $className,
119
                    $namespace,
120
                    $nsSep,
121
                    ($classNameEnd - 1)
122
                );
123
            } else {
124
                // Doc comment block.
125
                foreach ($tokens[$nsSep]['comment_tags'] as $tag) {
126
                    $content = $tokens[$tag]['content'];
127
128
                    if (false === \array_key_exists($content, $docCommentTags)) {
129
                        continue;
130
                    }
131
132
                    $next = ($tag + 1);
133
                    // PHP Code Sniffer will magically add  T_DOC_COMMENT_CLOSE_TAG with empty string content.
134
                    $lineEnd = $phpcsFile->findNext(
135
                        [
136
                            T_DOC_COMMENT_CLOSE_TAG,
137
                            T_DOC_COMMENT_STAR,
138
                        ],
139
                        $next
140
                    );
141
142
                    $docCommentStringPtr = $phpcsFile->findNext(
143
                        [T_DOC_COMMENT_STRING],
144
                        $next,
145
                        (int) $lineEnd
146
                    );
147
148
                    if (false === $docCommentStringPtr) {
149
                        continue;
150
                    }
151
152
                    $docLine = $tokens[$docCommentStringPtr]['content'];
153
154
                    $docLineTokens = PregLibrary::MO4PregSplit(
155
                        '/\s+/',
156
                        $docLine,
157
                        -1,
158
                        PREG_SPLIT_NO_EMPTY
159
                    );
160
161
                    // phpcs:disable
162
                    /** @var array<string> $docLineTokens */
163
                    $docLineTokens = \array_slice(
164
                        $docLineTokens,
165
                        0,
166
                        $docCommentTags[$content]
167
                    );
168
                    // phpcs:enable
169
170
                    foreach ($docLineTokens as $docLineToken) {
171
                        // phpcs:disable
172
                        /** @var array<string> $typeTokens */
173
                        $typeTokens = PregLibrary::MO4PregSplit(
174
                            '/\|/',
175
                            $docLineToken,
176
                            -1,
177
                            PREG_SPLIT_NO_EMPTY
178
                        );
179
                        // phpcs:enable
180
181
                        foreach ($typeTokens as $typeToken) {
182
                            if (true === \in_array($typeToken, $useStatements, true)) {
183
                                continue;
184
                            }
185
186
                            $this->checkShorthandPossible(
187
                                $phpcsFile,
188
                                $useStatements,
189
                                $typeToken,
190
                                $namespace,
191
                                $docCommentStringPtr,
192
                                $docCommentStringPtr,
193
                                true
194
                            );
195
                        }
196
                    }
197
                }
198
            }
199
200
            $nsSep = $phpcsFile->findNext($scanTokens, ($classNameEnd + 1));
201
        }
202
    }
203
204
    /**
205
     * Get all use statements in range
206
     *
207
     * @param File $phpcsFile PHP CS File
208
     * @param int  $start     start pointer
209
     * @param int  $end       end pointer
210
     *
211
     * @return array
212
     */
213
    protected function getUseStatements(File $phpcsFile, int $start, int $end): array
214
    {
215
        $useStatements = [];
216
        $i             = $start;
217
        $tokens        = $phpcsFile->getTokens();
218
        $useTokenPtr   = $phpcsFile->findNext(T_USE, $i, $end);
219
220
        while (false !== $useTokenPtr) {
221
            $classNameStart = (int) $phpcsFile->findNext(
222
                PHP_CodeSniffer_Tokens::$emptyTokens,
223
                ($useTokenPtr + 1),
224
                $end,
225
                true
226
            );
227
            $classNameEnd   = (int) $phpcsFile->findNext(
228
                $this->classNameTokens,
229
                ($classNameStart + 1),
230
                $end,
231
                true
232
            );
233
            $useEnd         = $phpcsFile->findNext(
234
                [
235
                    T_SEMICOLON,
236
                    T_COMMA,
237
                ],
238
                $classNameEnd,
239
                $end
240
            );
241
242
            // Prevent endless loop when 'use ;' is the last use statement.
243
            if (false === $useEnd) {
244
                break;
245
            }
246
247
            /** @var int $aliasNamePtr */
248
            $aliasNamePtr = $phpcsFile->findPrevious(
249
                PHP_CodeSniffer_Tokens::$emptyTokens,
250
                ($useEnd - 1),
251
                0,
252
                true
253
            );
254
255
            $length    = ($classNameEnd - $classNameStart);
256
            $className = $phpcsFile->getTokensAsString($classNameStart, $length);
257
258
            $className                 = $this->getFullyQualifiedClassName($className);
259
            $useStatements[$className] = $tokens[$aliasNamePtr]['content'];
260
            $i                         = ($useEnd + 1);
261
262
            $useTokenPtr = T_COMMA === $tokens[$useEnd]['code'] ? $i : $phpcsFile->findNext(T_USE, $i, $end);
263
        }
264
265
        return $useStatements;
266
    }
267
268
    /**
269
     * Get the namespace of the current class file
270
     *
271
     * @param File $phpcsFile PHP CS File
272
     * @param int  $start     start pointer
273
     * @param int  $end       end pointer
274
     *
275
     * @return string
276
     */
277
    protected function getNamespace(File $phpcsFile, int $start, int $end): string
278
    {
279
        $namespace      = (int) $phpcsFile->findNext(T_NAMESPACE, $start, $end);
280
        $namespaceStart = $phpcsFile->findNext(
281
            PHP_CodeSniffer_Tokens::$emptyTokens,
282
            ($namespace + 1),
283
            $end,
284
            true
285
        );
286
287
        if (false === $namespaceStart) {
288
            return '';
289
        }
290
291
        $namespaceEnd = (int) $phpcsFile->findNext(
292
            $this->classNameTokens,
293
            ($namespaceStart + 1),
294
            $end,
295
            true
296
        );
297
298
        $nslen = ($namespaceEnd - $namespaceStart);
299
        $name  = $phpcsFile->getTokensAsString($namespaceStart, $nslen);
300
301
        return "\\{$name}\\";
302
    }
303
304
    /**
305
     * Return the fully qualified class name, e.g. '\Foo\Bar\Faz'
306
     *
307
     * @param string $className class name
308
     *
309
     * @return string
310
     */
311
    private function getFullyQualifiedClassName(string $className): string
312
    {
313
        return '\\' !== $className[0] ? "\\{$className}" : $className;
314
    }
315
316
    /**
317
     * Check if short hand is possible.
318
     *
319
     * @param File   $phpcsFile     PHP CS File
320
     * @param array  $useStatements array with class use statements
321
     * @param string $className     class name
322
     * @param string $namespace     name space
323
     * @param int    $startPtr      start token pointer
324
     * @param int    $endPtr        end token pointer
325
     * @param bool   $isDocBlock    true if fixing doc block
326
     *
327
     * @return void
328
     */
329
    private function checkShorthandPossible(File $phpcsFile, array $useStatements, string $className, string $namespace, int $startPtr, int $endPtr, bool $isDocBlock = false): void
330
    {
331
        $msg              = 'Shorthand possible. Replace "%s" with "%s"';
332
        $code             = 'UnnecessaryNamespaceUsage';
333
        $fixable          = false;
334
        $replaceClassName = false;
335
        $replacement      = '';
336
337
        $fullClassName = $this->getFullyQualifiedClassName($className);
338
339
        if (true === \array_key_exists($fullClassName, $useStatements)) {
340
            $replacement = $useStatements[$fullClassName];
341
342
            $data = [
343
                $className,
344
                $replacement,
345
            ];
346
347
            $fixable = $phpcsFile->addFixableWarning(
348
                $msg,
349
                $startPtr,
350
                $code,
351
                $data
352
            );
353
354
            $replaceClassName = true;
355
        } elseif ('' !== $namespace && 0 === \strpos($fullClassName, $namespace)) {
356
            $replacement = \substr($fullClassName, \strlen($namespace));
357
358
            $data    = [
359
                $className,
360
                $replacement,
361
            ];
362
            $fixable = $phpcsFile->addFixableWarning(
363
                $msg,
364
                $startPtr,
365
                $code,
366
                $data
367
            );
368
        }
369
370
        if (true !== $fixable) {
371
            return;
372
        }
373
374
        $phpcsFile->fixer->beginChangeset();
375
376
        if (true === $isDocBlock) {
377
            $tokens     = $phpcsFile->getTokens();
378
            $oldContent = $tokens[$startPtr]['content'];
379
            $newContent = \str_replace($className, $replacement, $oldContent);
380
            $phpcsFile->fixer->replaceToken($startPtr, $newContent);
381
        } else {
382
            for ($i = $startPtr; $i < $endPtr; $i++) {
383
                $phpcsFile->fixer->replaceToken($i, '');
384
            }
385
386
            if (true === $replaceClassName) {
387
                $phpcsFile->fixer->replaceToken($endPtr, $replacement);
388
            }
389
        }
390
391
        $phpcsFile->fixer->endChangeset();
392
    }
393
}
394