1<?php
2/**
3 * Squiz_Sniffs_Classes_ClassFileNameSniff.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @author    Marc McIntyre <mmcintyre@squiz.net>
11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 * @link      http://pear.php.net/package/PHP_CodeSniffer
14 */
15
16if (class_exists('PHP_CodeSniffer_Standards_AbstractScopeSniff', true) === false) {
17    $error = 'Class PHP_CodeSniffer_Standards_AbstractScopeSniff not found';
18    throw new PHP_CodeSniffer_Exception($error);
19}
20
21/**
22 * Tests self member references.
23 *
24 * Verifies that :
25 * <ul>
26 *  <li>self:: is used instead of Self::</li>
27 *  <li>self:: is used for local static member reference</li>
28 *  <li>self:: is used instead of self ::</li>
29 * </ul>
30 *
31 * @category  PHP
32 * @package   PHP_CodeSniffer
33 * @author    Greg Sherwood <gsherwood@squiz.net>
34 * @author    Marc McIntyre <mmcintyre@squiz.net>
35 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
36 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
37 * @version   Release: @package_version@
38 * @link      http://pear.php.net/package/PHP_CodeSniffer
39 */
40class Squiz_Sniffs_Classes_SelfMemberReferenceSniff extends PHP_CodeSniffer_Standards_AbstractScopeSniff
41{
42
43
44    /**
45     * Constructs a Squiz_Sniffs_Classes_SelfMemberReferenceSniff.
46     */
47    public function __construct()
48    {
49        parent::__construct(array(T_CLASS), array(T_DOUBLE_COLON));
50
51    }//end __construct()
52
53
54    /**
55     * Processes the function tokens within the class.
56     *
57     * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found.
58     * @param int                  $stackPtr  The position where the token was found.
59     * @param int                  $currScope The current scope opener token.
60     *
61     * @return void
62     */
63    protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope)
64    {
65        $tokens = $phpcsFile->getTokens();
66
67        $calledClassName = ($stackPtr - 1);
68        if ($tokens[$calledClassName]['code'] === T_SELF) {
69            if ($tokens[$calledClassName]['content'] !== 'self') {
70                $error = 'Must use "self::" for local static member reference; found "%s::"';
71                $data  = array($tokens[$calledClassName]['content']);
72                $fix   = $phpcsFile->addFixableError($error, $calledClassName, 'IncorrectCase', $data);
73                if ($fix === true) {
74                    $phpcsFile->fixer->replaceToken($calledClassName, 'self');
75                }
76
77                return;
78            }
79        } else if ($tokens[$calledClassName]['code'] === T_STRING) {
80            // If the class is called with a namespace prefix, build fully qualified
81            // namespace calls for both current scope class and requested class.
82            if ($tokens[($calledClassName - 1)]['code'] === T_NS_SEPARATOR) {
83                $declarationName        = $this->getDeclarationNameWithNamespace($tokens, $calledClassName);
84                $declarationName        = substr($declarationName, 1);
85                $fullQualifiedClassName = $this->getNamespaceOfScope($phpcsFile, $currScope);
86                if ($fullQualifiedClassName === '\\') {
87                    $fullQualifiedClassName = '';
88                } else {
89                    $fullQualifiedClassName .= '\\';
90                }
91
92                $fullQualifiedClassName .= $phpcsFile->getDeclarationName($currScope);
93            } else {
94                $declarationName        = $phpcsFile->getDeclarationName($currScope);
95                $fullQualifiedClassName = $tokens[$calledClassName]['content'];
96            }
97
98            if ($declarationName === $fullQualifiedClassName) {
99                // Class name is the same as the current class, which is not allowed
100                // except if being used inside a closure.
101                if ($phpcsFile->hasCondition($stackPtr, T_CLOSURE) === false) {
102                    $error = 'Must use "self::" for local static member reference';
103                    $fix   = $phpcsFile->addFixableError($error, $calledClassName, 'NotUsed');
104
105                    if ($fix === true) {
106                        $prev = $phpcsFile->findPrevious(array(T_NS_SEPARATOR, T_STRING), ($stackPtr - 1), null, true);
107                        $phpcsFile->fixer->beginChangeset();
108                        for ($i = ($prev + 1); $i < $stackPtr; $i++) {
109                            $phpcsFile->fixer->replaceToken($i, '');
110                        }
111
112                        $phpcsFile->fixer->replaceToken($stackPtr, 'self::');
113                        $phpcsFile->fixer->endChangeset();
114                    }
115
116                    return;
117                }
118            }//end if
119        }//end if
120
121        if ($tokens[($stackPtr - 1)]['code'] === T_WHITESPACE) {
122            $found = strlen($tokens[($stackPtr - 1)]['content']);
123            $error = 'Expected 0 spaces before double colon; %s found';
124            $data  = array($found);
125            $fix   = $phpcsFile->addFixableError($error, $calledClassName, 'SpaceBefore', $data);
126
127            if ($fix === true) {
128                $phpcsFile->fixer->replaceToken(($stackPtr - 1), '');
129            }
130        }
131
132        if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) {
133            $found = strlen($tokens[($stackPtr + 1)]['content']);
134            $error = 'Expected 0 spaces after double colon; %s found';
135            $data  = array($found);
136            $fix   = $phpcsFile->addFixableError($error, $calledClassName, 'SpaceAfter', $data);
137
138            if ($fix === true) {
139                $phpcsFile->fixer->replaceToken(($stackPtr + 1), '');
140            }
141        }
142
143    }//end processTokenWithinScope()
144
145
146    /**
147     * Returns the declaration names for classes/interfaces/functions with a namespace.
148     *
149     * @param array $tokens   Token stack for this file
150     * @param int   $stackPtr The position where the namespace building will start.
151     *
152     * @return string
153     */
154    protected function getDeclarationNameWithNamespace(array $tokens, $stackPtr)
155    {
156        $nameParts      = array();
157        $currentPointer = $stackPtr;
158        while ($tokens[$currentPointer]['code'] === T_NS_SEPARATOR
159            || $tokens[$currentPointer]['code'] === T_STRING
160        ) {
161            $nameParts[] = $tokens[$currentPointer]['content'];
162            $currentPointer--;
163        }
164
165        $nameParts = array_reverse($nameParts);
166        return implode('', $nameParts);
167
168    }//end getDeclarationNameWithNamespace()
169
170
171    /**
172     * Returns the namespace declaration of a file.
173     *
174     * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found.
175     * @param int                  $stackPtr  The position where the search for the
176     *                                        namespace declaration will start.
177     *
178     * @return string
179     */
180    protected function getNamespaceOfScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
181    {
182        $namespace            = '\\';
183        $namespaceDeclaration = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr);
184
185        if ($namespaceDeclaration !== false) {
186            $endOfNamespaceDeclaration = $phpcsFile->findNext(T_SEMICOLON, $namespaceDeclaration);
187            $namespace = $this->getDeclarationNameWithNamespace(
188                $phpcsFile->getTokens(),
189                ($endOfNamespaceDeclaration - 1)
190            );
191        }
192
193        return $namespace;
194
195    }//end getNamespaceOfScope()
196
197
198}//end class
199