Passed
Branch master (ccf1c2)
by Christian
03:57
created

UnnecessaryNamespaceUsageSniff::process()   D

Complexity

Conditions 10
Paths 4

Size

Total Lines 121
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 121
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 80
nc 4
nop 2

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
 * PHP version 5
7
 *
8
 * @category PHP
9
 * @package  PHP_CodeSniffer-MO4
10
 * @author   Xaver Loppenstedt <[email protected]>
11
 * @license  http://spdx.org/licenses/MIT MIT License
12
 * @version  GIT: master
13
 * @link     https://github.com/Mayflower/mo4-coding-standard
14
 */
15
16
namespace MO4\Sniffs\Formatting;
17
18
use PHP_CodeSniffer\Sniffs\Sniff;
19
use PHP_CodeSniffer\Files\File;
20
use PHP_CodeSniffer\Util\Tokens as PHP_CodeSniffer_Tokens;
21
22
/**
23
 * Unnecessary Namespace Usage sniff.
24
 *
25
 * Full namespace declaration should be skipped in favour of the short declaration.
26
 *
27
 * @category  PHP
28
 * @package   PHP_CodeSniffer-MO4
29
 * @author    Xaver Loppenstedt <[email protected]>
30
 * @author    Marco Jantke <[email protected]>
31
 * @author    Steffen Ritter <[email protected]>
32
 * @copyright 2013 Xaver Loppenstedt, some rights reserved.
33
 * @license   http://spdx.org/licenses/MIT MIT License
34
 * @link      https://github.com/Mayflower/mo4-coding-standard
35
 */
36
class UnnecessaryNamespaceUsageSniff implements Sniff
37
{
38
39
    /**
40
     * Tokens used in full class name.
41
     *
42
     * @var array
43
     */
44
    private $classNameTokens = array(
45
                                T_NS_SEPARATOR,
46
                                T_STRING,
47
                               );
48
49
50
    /**
51
     * Registers the tokens that this sniff wants to listen for.
52
     *
53
     * @return array(int)
0 ignored issues
show
Documentation introduced by
The doc-type array(int) could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
54
     * @see    Tokens.php
55
     */
56
    public function register()
57
    {
58
        return array(T_CLASS);
59
60
    }//end register()
61
62
63
    /**
64
     * Called when one of the token types that this sniff is listening for
65
     * is found.
66
     *
67
     * @param File $phpcsFile The PHP_CodeSniffer file where the
68
     *                        token was found.
69
     * @param int  $stackPtr  The position in the PHP_CodeSniffer
70
     *                        file's token stack where the token
71
     *                        was found.
72
     *
73
     * @return void
74
     */
75
    public function process(File $phpcsFile, $stackPtr)
76
    {
77
        $docCommentTags = array(
78
                           '@param'  => 1,
79
                           '@return' => 1,
80
                           '@throws' => 1,
81
                           '@var'    => 2,
82
                          );
83
        $scanTokens     = array(
84
                           T_NS_SEPARATOR,
85
                           T_DOC_COMMENT_OPEN_TAG,
86
                          );
87
88
        $tokens        = $phpcsFile->getTokens();
89
        $useStatements = $this->getUseStatements($phpcsFile, 0, ($stackPtr - 1));
90
        $namespace     = $this->getNamespace($phpcsFile, 0, ($stackPtr - 1));
91
92
        $nsSep = $phpcsFile->findNext($scanTokens, ($stackPtr + 1));
93
94
        while ($nsSep !== false) {
95
            $nsSep = (int) $nsSep;
96
97
            $classNameEnd = $phpcsFile->findNext(
98
                $this->classNameTokens,
99
                $nsSep,
100
                null,
101
                true
102
            );
103
104
            if ($tokens[$nsSep]['code'] === T_NS_SEPARATOR) {
105
                if ($tokens[($nsSep - 1)]['code'] === T_STRING) {
106
                    --$nsSep;
107
                }
108
109
                $className = $phpcsFile->getTokensAsString(
110
                    $nsSep,
111
                    ($classNameEnd - $nsSep)
112
                );
113
114
                $this->checkShorthandPossible(
115
                    $phpcsFile,
116
                    $useStatements,
117
                    $className,
118
                    $namespace,
119
                    $nsSep,
120
                    ($classNameEnd - 1),
121
                    false
122
                );
123
            } else {
124
                // Doc comment block.
125
                foreach ($tokens[$nsSep]['comment_tags'] as $tag) {
126
                    $content = $tokens[$tag]['content'];
127
                    if (array_key_exists($content, $docCommentTags) === false) {
128
                        continue;
129
                    }
130
131
                    $next = ($tag + 1);
132
                    // PHP Code Sniffer will magically add  T_DOC_COMMENT_CLOSE_TAG with empty string content.
133
                    $lineEnd = $phpcsFile->findNext(
134
                        array(
135
                         T_DOC_COMMENT_CLOSE_TAG,
136
                         T_DOC_COMMENT_STAR,
137
                        ),
138
                        $next
139
                    );
140
141
                    $docCommentStringPtr = $phpcsFile->findNext(
142
                        array(T_DOC_COMMENT_STRING),
143
                        $next,
144
                        (int) $lineEnd
145
                    );
146
147
                    if ($docCommentStringPtr === false) {
148
                        continue;
149
                    }
150
151
                    $docCommentStringPtr = (int) $docCommentStringPtr;
152
153
                    $docLine = $tokens[$docCommentStringPtr]['content'];
154
155
                    $docLineTokens = preg_split(
156
                        '/\s+/',
157
                        $docLine,
158
                        -1,
159
                        PREG_SPLIT_NO_EMPTY
160
                    );
161
                    $docLineTokens = array_slice(
162
                        $docLineTokens,
163
                        0,
164
                        $docCommentTags[$content]
165
                    );
166
                    foreach ($docLineTokens as $docLineToken) {
167
                        $typeTokens = preg_split(
168
                            '/\|/',
169
                            $docLineToken,
170
                            -1,
171
                            PREG_SPLIT_NO_EMPTY
172
                        );
173
                        foreach ($typeTokens as $typeToken) {
174
                            if (true === in_array($typeToken, $useStatements)) {
175
                                continue;
176
                            }
177
178
                            $this->checkShorthandPossible(
179
                                $phpcsFile,
180
                                $useStatements,
181
                                $typeToken,
182
                                $namespace,
183
                                $docCommentStringPtr,
184
                                $docCommentStringPtr,
185
                                true
186
                            );
187
                        }//end foreach
188
                    }//end foreach
189
                }//end foreach
190
            }//end if
191
192
            $nsSep = $phpcsFile->findNext($scanTokens, ($classNameEnd + 1));
193
        }//end while
194
195
    }//end process()
196
197
198
    /**
199
     * Get all use statements in range
200
     *
201
     * @param File $phpcsFile PHP CS File
202
     * @param int  $start     start pointer
203
     * @param int  $end       end pointer
204
     *
205
     * @return array
206
     */
207
    protected function getUseStatements(
208
        File $phpcsFile,
209
        $start,
210
        $end
211
    ) {
212
        $useStatements = array();
213
        $i           = $start;
214
        $tokens      = $phpcsFile->getTokens();
215
        $useTokenPtr = $phpcsFile->findNext(T_USE, $i, $end);
216
217
        while ($useTokenPtr !== false) {
218
            $classNameStart = (int) $phpcsFile->findNext(
219
                PHP_CodeSniffer_Tokens::$emptyTokens,
220
                ($useTokenPtr + 1),
221
                $end,
222
                true
223
            );
224
            $classNameEnd   = (int) $phpcsFile->findNext(
225
                $this->classNameTokens,
226
                ($classNameStart + 1),
227
                $end,
228
                true
229
            );
230
            $useEnd         = $phpcsFile->findNext(
231
                array(
232
                 T_SEMICOLON,
233
                 T_COMMA,
234
                ),
235
                $classNameEnd,
236
                $end
237
            );
238
            // Prevent endless loop when 'use ;' is the last use statement.
239
            if ($useEnd === false) {
240
                break;
241
            }
242
243
            $aliasNamePtr = $phpcsFile->findPrevious(
244
                PHP_CodeSniffer_Tokens::$emptyTokens,
245
                ($useEnd - 1),
246
                null,
247
                true
248
            );
249
250
            $length    = ($classNameEnd - $classNameStart);
251
            $className = $phpcsFile->getTokensAsString($classNameStart, $length);
252
253
            $className = $this->getFullyQualifiedClassName($className);
254
            $useStatements[$className] = $tokens[$aliasNamePtr]['content'];
255
            $i = ($useEnd + 1);
256
257
            if ($tokens[$useEnd]['code'] === T_COMMA) {
258
                $useTokenPtr = $i;
259
            } else {
260
                $useTokenPtr = $phpcsFile->findNext(T_USE, $i, $end);
261
            }
262
        }//end while
263
264
        return $useStatements;
265
266
    }//end getUseStatements()
267
268
269
    /**
270
     * Get the namespace of the current class file
271
     *
272
     * @param File $phpcsFile PHP CS File
273
     * @param int  $start     start pointer
274
     * @param int  $end       end pointer
275
     *
276
     * @return string
277
     */
278
    protected function getNamespace(File $phpcsFile, $start, $end)
279
    {
280
        $namespace      = $phpcsFile->findNext(T_NAMESPACE, $start, $end);
281
        $namespaceStart = $phpcsFile->findNext(
282
            PHP_CodeSniffer_Tokens::$emptyTokens,
283
            ($namespace + 1),
284
            $end,
285
            true
286
        );
287
288
        if (false === $namespaceStart) {
289
            return '';
290
        }
291
292
        $namespaceStart = (int) $namespaceStart;
293
294
        $namespaceEnd = $phpcsFile->findNext(
295
            $this->classNameTokens,
296
            ($namespaceStart + 1),
297
            $end,
298
            true
299
        );
300
301
        $nslen = ($namespaceEnd - $namespaceStart);
302
        $name  = $phpcsFile->getTokensAsString($namespaceStart, $nslen);
303
304
        return "\\{$name}\\";
305
306
    }//end getNamespace()
307
308
309
    /**
310
     * Return the fully qualified class name, e.g. '\Foo\Bar\Faz'
311
     *
312
     * @param string $className class name
313
     *
314
     * @return string
315
     */
316
    private function getFullyQualifiedClassName($className)
317
    {
318
        if ($className[0] !== '\\') {
319
            $className = "\\{$className}";
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $className. This often makes code more readable.
Loading history...
320
321
            return $className;
322
        }
323
324
        return $className;
325
326
    }//end getFullyQualifiedClassName()
327
328
329
    /**
330
     * Check if short hand is possible.
331
     *
332
     * @param File   $phpcsFile     PHP CS File
333
     * @param array  $useStatements array with class use statements
334
     * @param string $className     class name
335
     * @param string $namespace     name space
336
     * @param int    $startPtr      start token pointer
337
     * @param int    $endPtr        end token pointer
338
     * @param bool   $isDocBlock    true if fixing doc block
339
     *
340
     * @return void
341
     */
342
    private function checkShorthandPossible(
343
        File $phpcsFile,
344
        $useStatements,
345
        $className,
346
        $namespace,
347
        $startPtr,
348
        $endPtr,
349
        $isDocBlock=false
350
    ) {
351
        $msg     = 'Shorthand possible. Replace "%s" with "%s"';
352
        $code    = 'UnnecessaryNamespaceUsage';
353
        $fixable = false;
354
        $replaceClassName = false;
355
        $replacement      = null;
356
357
        $fullClassName = $this->getFullyQualifiedClassName($className);
358
359
        if (array_key_exists($fullClassName, $useStatements) === true) {
360
            $replacement = $useStatements[$fullClassName];
361
362
            $data = array(
363
                     $className,
364
                     $replacement,
365
                    );
366
367
            $fixable = $phpcsFile->addFixableWarning(
368
                $msg,
369
                $startPtr,
370
                $code,
371
                $data
372
            );
373
374
            $replaceClassName = true;
375
        } else if (strpos($fullClassName, $namespace) === 0) {
376
            $replacement = substr($fullClassName, strlen($namespace));
377
378
            $data    = array(
379
                        $className,
380
                        $replacement,
381
                       );
382
            $fixable = $phpcsFile->addFixableWarning(
383
                $msg,
384
                $startPtr,
385
                $code,
386
                $data
387
            );
388
        }//end if
389
390
        if (true === $fixable) {
391
            $phpcsFile->fixer->beginChangeset();
392
            if (true === $isDocBlock) {
393
                $tokens     = $phpcsFile->getTokens();
394
                $oldContent = $tokens[$startPtr]['content'];
395
                $newContent = str_replace($className, $replacement, $oldContent);
396
                $phpcsFile->fixer->replaceToken($startPtr, $newContent);
397
            } else {
398
                for ($i = $startPtr; $i < $endPtr; $i++) {
399
                    $phpcsFile->fixer->replaceToken($i, '');
400
                }
401
402
                if (true === $replaceClassName) {
403
                    $phpcsFile->fixer->replaceToken($endPtr, $replacement);
404
                }
405
            }
406
407
            $phpcsFile->fixer->endChangeset();
408
        }//end if
409
410
    }//end checkShorthandPossible()
411
412
413
}//end class
414