Completed
Pull Request — master (#403)
by Juliette
02:01
created

NewMagicClassConstantSniff   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 96
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 3
dl 0
loc 96
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 4 1
C process() 0 73 14
1
<?php
2
/**
3
 * PHPCompatibility_Sniffs_PHP_NewMagicClassConstantSniff.
4
 *
5
 * PHP version 5.5
6
 *
7
 * @category PHP
8
 * @package  PHPCompatibility
9
 * @author   Juliette Reinders Folmer <[email protected]>
10
 */
11
12
/**
13
 * PHPCompatibility_Sniffs_PHP_NewMagicClassConstantSniff.
14
 *
15
 * The special ClassName::class constant is available as of PHP 5.5.0, and allows for
16
 * fully qualified class name resolution at compile.
17
 *
18
 * PHP version 5.5
19
 *
20
 * @category PHP
21
 * @package  PHPCompatibility
22
 * @author   Juliette Reinders Folmer <[email protected]>
23
 */
24
class PHPCompatibility_Sniffs_PHP_NewMagicClassConstantSniff extends PHPCompatibility_Sniff
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
25
{
26
27
    /**
28
     * Returns an array of tokens this test wants to listen for.
29
     *
30
     * @return array
31
     */
32
    public function register()
33
    {
34
        return array(T_STRING);
35
    }
36
37
    /**
38
     * Processes this test, when one of its tokens is encountered.
39
     *
40
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
41
     * @param int                  $stackPtr  The position of the current token in the
42
     *                                        stack passed in $tokens.
43
     *
44
     * @return void
45
     */
46
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
47
    {
48
        $tokens = $phpcsFile->getTokens();
49
50
        if (strtolower($tokens[$stackPtr]['content']) !== 'class') {
51
            return;
52
        }
53
54
        $prevToken = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true, null, true);
55
        if ($prevToken === false || $tokens[$prevToken]['code'] !== T_DOUBLE_COLON) {
56
            return;
57
        }
58
59
        if ($this->supportsBelow('5.4')) {
60
            $phpcsFile->addError(
61
                'The magic class constant ClassName::class was not available in PHP 5.4 or earlier',
62
                $stackPtr,
63
                'Found'
64
            );
65
        }
66
67
        /*
68
         * Check against invalid use of the magic `::class` constant.
69
         */
70
        if ($this->supportsAbove('5.5') === false) {
71
            return;
72
        }
73
74
        $classNameToken = $phpcsFile->findPrevious(T_STRING, ($prevToken - 1), null, false, null, true);
75
76
        // Useless if not in a namespace.
77
        $hasNamespace = false;
78
        if ($classNameToken !== false) {
79
            $namespace = $this->determineNamespace($phpcsFile, $classNameToken);
0 ignored issues
show
Bug introduced by
It seems like $classNameToken defined by $phpcsFile->findPrevious...ull, false, null, true) on line 74 can also be of type boolean; however, PHPCompatibility_Sniff::determineNamespace() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
80
            if (empty($namespace) === false) {
81
                $hasNamespace = true;
82
            }
83
        }
84
85
        if ($hasNamespace === false) {
86
            $phpcsFile->addWarning(
87
                'Using the magic class constant ClassName::class is only useful in combination with a namespaced class',
88
                $stackPtr,
89
                'NotInNamespace'
90
            );
91
        }
92
93
94
        // Is the magic constant used in a file which actually contains the referenced class ?
95
        if ($classNameToken === false) {
96
            return;
97
        }
98
99
        $targetClassName = $tokens[$classNameToken]['content'];
100
        $classPtr        = $stackPtr;
101
        while ($classPtr > 0) {
102
            $classPtr = $phpcsFile->findPrevious(T_CLASS, ($classPtr - 1));
103
104
            if ($classPtr !== false) {
105
                $className = $phpcsFile->getDeclarationName($classPtr);
0 ignored issues
show
Bug introduced by
It seems like $classPtr defined by $phpcsFile->findPrevious(T_CLASS, $classPtr - 1) on line 102 can also be of type boolean; however, PHP_CodeSniffer_File::getDeclarationName() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
106
                if (!empty($className) && $className === $targetClassName) {
107
                    return;
108
                }
109
            }
110
        }
111
112
        // Still here? In that case, the magic constant is used in a file which doesn't contain the target class.
113
        $phpcsFile->addWarning(
114
            'The magic class constant ClassName::class can only be used in the same file as where the class is defined',
115
            $stackPtr,
116
            'FileDoesNotContainClass'
117
        );
118
    }
119
}
120