1<?php
2/**
3 * PSR2_Sniffs_Namespaces_UseDeclarationSniff.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
11 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12 * @link      http://pear.php.net/package/PHP_CodeSniffer
13 */
14
15/**
16 * PSR2_Sniffs_Namespaces_UseDeclarationSniff.
17 *
18 * Ensures USE blocks are declared correctly.
19 *
20 * @category  PHP
21 * @package   PHP_CodeSniffer
22 * @author    Greg Sherwood <gsherwood@squiz.net>
23 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
24 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
25 * @version   Release: @package_version@
26 * @link      http://pear.php.net/package/PHP_CodeSniffer
27 */
28class PSR2_Sniffs_Namespaces_UseDeclarationSniff implements PHP_CodeSniffer_Sniff
29{
30
31
32    /**
33     * Returns an array of tokens this test wants to listen for.
34     *
35     * @return array
36     */
37    public function register()
38    {
39        return array(T_USE);
40
41    }//end register()
42
43
44    /**
45     * Processes this test, when one of its tokens is encountered.
46     *
47     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
48     * @param int                  $stackPtr  The position of the current token in
49     *                                        the stack passed in $tokens.
50     *
51     * @return void
52     */
53    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
54    {
55        if ($this->_shouldIgnoreUse($phpcsFile, $stackPtr) === true) {
56            return;
57        }
58
59        $tokens = $phpcsFile->getTokens();
60
61        // One space after the use keyword.
62        if ($tokens[($stackPtr + 1)]['content'] !== ' ') {
63            $error = 'There must be a single space after the USE keyword';
64            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterUse');
65            if ($fix === true) {
66                $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' ');
67            }
68        }
69
70        // Only one USE declaration allowed per statement.
71        $next = $phpcsFile->findNext(array(T_COMMA, T_SEMICOLON, T_OPEN_USE_GROUP), ($stackPtr + 1));
72        if ($tokens[$next]['code'] !== T_SEMICOLON) {
73            $error = 'There must be one USE keyword per declaration';
74            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'MultipleDeclarations');
75            if ($fix === true) {
76                if ($tokens[$next]['code'] === T_COMMA) {
77                    $phpcsFile->fixer->replaceToken($next, ';'.$phpcsFile->eolChar.'use ');
78                } else {
79                    $baseUse      = rtrim($phpcsFile->getTokensAsString($stackPtr, ($next - $stackPtr)));
80                    $closingCurly = $phpcsFile->findNext(T_CLOSE_USE_GROUP, ($next + 1));
81
82                    $phpcsFile->fixer->beginChangeset();
83
84                    // Remove base use statement.
85                    for ($i = $stackPtr; $i <= $next; $i++) {
86                        $phpcsFile->fixer->replaceToken($i, '');
87                    }
88
89                    // Convert grouped use statements into full use statements.
90                    do {
91                        $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), $closingCurly, true);
92
93                        $whitespace = $phpcsFile->findPrevious(T_WHITESPACE, ($next - 1), null, true);
94                        for ($i = ($whitespace + 1); $i < $next; $i++) {
95                            $phpcsFile->fixer->replaceToken($i, '');
96                        }
97
98                        if ($tokens[$next]['code'] === T_CONST || $tokens[$next]['code'] === T_FUNCTION) {
99                            $phpcsFile->fixer->addContentBefore($next, 'use ');
100                            $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($next + 1), $closingCurly, true);
101                            $phpcsFile->fixer->addContentBefore($next, str_replace('use ', '', $baseUse));
102                        } else {
103                            $phpcsFile->fixer->addContentBefore($next, $baseUse);
104                        }
105
106                        $next = $phpcsFile->findNext(T_COMMA, ($next + 1), $closingCurly);
107                        if ($next !== false) {
108                            $phpcsFile->fixer->replaceToken($next, ';'.$phpcsFile->eolChar);
109                        }
110                    } while ($next !== false);
111
112                    $phpcsFile->fixer->replaceToken($closingCurly, '');
113
114                    // Remove any trailing whitespace.
115                    $next       = $phpcsFile->findNext(T_SEMICOLON, $closingCurly);
116                    $whitespace = $phpcsFile->findPrevious(T_WHITESPACE, ($closingCurly - 1), null, true);
117                    for ($i = ($whitespace + 1); $i < $next; $i++) {
118                        $phpcsFile->fixer->replaceToken($i, '');
119                    }
120
121                    $phpcsFile->fixer->endChangeset();
122                }//end if
123            }//end if
124        }//end if
125
126        // Make sure this USE comes after the first namespace declaration.
127        $prev = $phpcsFile->findPrevious(T_NAMESPACE, ($stackPtr - 1));
128        if ($prev !== false) {
129            $first = $phpcsFile->findNext(T_NAMESPACE, 1);
130            if ($prev !== $first) {
131                $error = 'USE declarations must go after the first namespace declaration';
132                $phpcsFile->addError($error, $stackPtr, 'UseAfterNamespace');
133            }
134        }
135
136        // Only interested in the last USE statement from here onwards.
137        $nextUse = $phpcsFile->findNext(T_USE, ($stackPtr + 1));
138        while ($this->_shouldIgnoreUse($phpcsFile, $nextUse) === true) {
139            $nextUse = $phpcsFile->findNext(T_USE, ($nextUse + 1));
140            if ($nextUse === false) {
141                break;
142            }
143        }
144
145        if ($nextUse !== false) {
146            return;
147        }
148
149        $end  = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1));
150        $next = $phpcsFile->findNext(T_WHITESPACE, ($end + 1), null, true);
151
152        if ($tokens[$next]['code'] === T_CLOSE_TAG) {
153            return;
154        }
155
156        $diff = ($tokens[$next]['line'] - $tokens[$end]['line'] - 1);
157        if ($diff !== 1) {
158            if ($diff < 0) {
159                $diff = 0;
160            }
161
162            $error = 'There must be one blank line after the last USE statement; %s found;';
163            $data  = array($diff);
164            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterLastUse', $data);
165            if ($fix === true) {
166                if ($diff === 0) {
167                    $phpcsFile->fixer->addNewline($end);
168                } else {
169                    $phpcsFile->fixer->beginChangeset();
170                    for ($i = ($end + 1); $i < $next; $i++) {
171                        if ($tokens[$i]['line'] === $tokens[$next]['line']) {
172                            break;
173                        }
174
175                        $phpcsFile->fixer->replaceToken($i, '');
176                    }
177
178                    $phpcsFile->fixer->addNewline($end);
179                    $phpcsFile->fixer->endChangeset();
180                }
181            }
182        }//end if
183
184    }//end process()
185
186
187    /**
188     * Check if this use statement is part of the namespace block.
189     *
190     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
191     * @param int                  $stackPtr  The position of the current token in
192     *                                        the stack passed in $tokens.
193     *
194     * @return void
195     */
196    private function _shouldIgnoreUse(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
197    {
198        $tokens = $phpcsFile->getTokens();
199
200        // Ignore USE keywords inside closures.
201        $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
202        if ($tokens[$next]['code'] === T_OPEN_PARENTHESIS) {
203            return true;
204        }
205
206        // Ignore USE keywords for traits.
207        if ($phpcsFile->hasCondition($stackPtr, array(T_CLASS, T_TRAIT)) === true) {
208            return true;
209        }
210
211        return false;
212
213    }//end _shouldIgnoreUse()
214
215
216}//end class
217