1<?php
2/**
3 * Verifies that control statements conform to their coding standards.
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 * Verifies that control statements conform to their coding standards.
17 *
18 * @category  PHP
19 * @package   PHP_CodeSniffer
20 * @author    Greg Sherwood <gsherwood@squiz.net>
21 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
22 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
23 * @version   Release: @package_version@
24 * @link      http://pear.php.net/package/PHP_CodeSniffer
25 */
26class Squiz_Sniffs_ControlStructures_ControlSignatureSniff implements PHP_CodeSniffer_Sniff
27{
28
29    /**
30     * A list of tokenizers this sniff supports.
31     *
32     * @var array
33     */
34    public $supportedTokenizers = array(
35                                   'PHP',
36                                   'JS',
37                                  );
38
39
40    /**
41     * Returns an array of tokens this test wants to listen for.
42     *
43     * @return int[]
44     */
45    public function register()
46    {
47        return array(
48                T_TRY,
49                T_CATCH,
50                T_DO,
51                T_WHILE,
52                T_FOR,
53                T_IF,
54                T_FOREACH,
55                T_ELSE,
56                T_ELSEIF,
57                T_SWITCH,
58               );
59
60    }//end register()
61
62
63    /**
64     * Processes this test, when one of its tokens is encountered.
65     *
66     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
67     * @param int                  $stackPtr  The position of the current token in the
68     *                                        stack passed in $tokens.
69     *
70     * @return void
71     */
72    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
73    {
74        $tokens = $phpcsFile->getTokens();
75
76        if (isset($tokens[($stackPtr + 1)]) === false) {
77            return;
78        }
79
80        // Single space after the keyword.
81        $found = 1;
82        if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) {
83            $found = 0;
84        } else if ($tokens[($stackPtr + 1)]['content'] !== ' ') {
85            if (strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) !== false) {
86                $found = 'newline';
87            } else {
88                $found = strlen($tokens[($stackPtr + 1)]['content']);
89            }
90        }
91
92        if ($found !== 1) {
93            $error = 'Expected 1 space after %s keyword; %s found';
94            $data  = array(
95                      strtoupper($tokens[$stackPtr]['content']),
96                      $found,
97                     );
98
99            $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data);
100            if ($fix === true) {
101                if ($found === 0) {
102                    $phpcsFile->fixer->addContent($stackPtr, ' ');
103                } else {
104                    $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' ');
105                }
106            }
107        }
108
109        // Single space after closing parenthesis.
110        if (isset($tokens[$stackPtr]['parenthesis_closer']) === true
111            && isset($tokens[$stackPtr]['scope_opener']) === true
112        ) {
113            $closer  = $tokens[$stackPtr]['parenthesis_closer'];
114            $opener  = $tokens[$stackPtr]['scope_opener'];
115            $content = $phpcsFile->getTokensAsString(($closer + 1), ($opener - $closer - 1));
116
117            if ($content !== ' ') {
118                $error = 'Expected 1 space after closing parenthesis; found %s';
119                if (trim($content) === '') {
120                    $found = strlen($content);
121                } else {
122                    $found = '"'.str_replace($phpcsFile->eolChar, '\n', $content).'"';
123                }
124
125                $fix = $phpcsFile->addFixableError($error, $closer, 'SpaceAfterCloseParenthesis', array($found));
126                if ($fix === true) {
127                    if ($closer === ($opener - 1)) {
128                        $phpcsFile->fixer->addContent($closer, ' ');
129                    } else {
130                        $phpcsFile->fixer->beginChangeset();
131                        if (trim($content) === '') {
132                            $phpcsFile->fixer->addContent($closer, ' ');
133                            if ($found !== 0) {
134                                for ($i = ($closer + 1); $i < $opener; $i++) {
135                                    $phpcsFile->fixer->replaceToken($i, '');
136                                }
137                            }
138                        } else {
139                            $phpcsFile->fixer->addContent($closer, ' '.$tokens[$opener]['content']);
140                            $phpcsFile->fixer->replaceToken($opener, '');
141
142                            if ($tokens[$opener]['line'] !== $tokens[$closer]['line']) {
143                                $next = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), null, true);
144                                if ($tokens[$next]['line'] !== $tokens[$opener]['line']) {
145                                    for ($i = ($opener + 1); $i < $next; $i++) {
146                                        $phpcsFile->fixer->replaceToken($i, '');
147                                    }
148                                }
149                            }
150                        }
151
152                        $phpcsFile->fixer->endChangeset();
153                    }//end if
154                }//end if
155            }//end if
156        }//end if
157
158        // Single newline after opening brace.
159        if (isset($tokens[$stackPtr]['scope_opener']) === true) {
160            $opener = $tokens[$stackPtr]['scope_opener'];
161            for ($next = ($opener + 1); $next < $phpcsFile->numTokens; $next++) {
162                $code = $tokens[$next]['code'];
163
164                if ($code === T_WHITESPACE
165                    || ($code === T_INLINE_HTML
166                    && trim($tokens[$next]['content']) === '')
167                ) {
168                    continue;
169                }
170
171                // Skip all empty tokens on the same line as the opener.
172                if ($tokens[$next]['line'] === $tokens[$opener]['line']
173                    && (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$code]) === true
174                    || $code === T_CLOSE_TAG)
175                ) {
176                    continue;
177                }
178
179                // We found the first bit of a code, or a comment on the
180                // following line.
181                break;
182            }//end for
183
184            if ($tokens[$next]['line'] === $tokens[$opener]['line']) {
185                $error = 'Newline required after opening brace';
186                $fix   = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterOpenBrace');
187                if ($fix === true) {
188                    $phpcsFile->fixer->beginChangeset();
189                    for ($i = ($opener + 1); $i < $next; $i++) {
190                        if (trim($tokens[$i]['content']) !== '') {
191                            break;
192                        }
193
194                        // Remove whitespace.
195                        $phpcsFile->fixer->replaceToken($i, '');
196                    }
197
198                    $phpcsFile->fixer->addContent($opener, $phpcsFile->eolChar);
199                    $phpcsFile->fixer->endChangeset();
200                }
201            }//end if
202        } else if ($tokens[$stackPtr]['code'] === T_WHILE) {
203            // Zero spaces after parenthesis closer.
204            $closer = $tokens[$stackPtr]['parenthesis_closer'];
205            $found  = 0;
206            if ($tokens[($closer + 1)]['code'] === T_WHITESPACE) {
207                if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) {
208                    $found = 'newline';
209                } else {
210                    $found = strlen($tokens[($closer + 1)]['content']);
211                }
212            }
213
214            if ($found !== 0) {
215                $error = 'Expected 0 spaces before semicolon; %s found';
216                $data  = array($found);
217                $fix   = $phpcsFile->addFixableError($error, $closer, 'SpaceBeforeSemicolon', $data);
218                if ($fix === true) {
219                    $phpcsFile->fixer->replaceToken(($closer + 1), '');
220                }
221            }
222        }//end if
223
224        // Only want to check multi-keyword structures from here on.
225        if ($tokens[$stackPtr]['code'] === T_DO) {
226            if (isset($tokens[$stackPtr]['scope_closer']) === false) {
227                return;
228            }
229
230            $closer = $tokens[$stackPtr]['scope_closer'];
231        } else if ($tokens[$stackPtr]['code'] === T_ELSE
232            || $tokens[$stackPtr]['code'] === T_ELSEIF
233            || $tokens[$stackPtr]['code'] === T_CATCH
234        ) {
235            if (isset($tokens[$stackPtr]['scope_opener']) === true
236                && $tokens[$tokens[$stackPtr]['scope_opener']]['code'] === T_COLON
237            ) {
238                // Special case for alternate syntax, where this token is actually
239                // the closer for the previous block, so there is no spacing to check.
240                return;
241            }
242
243            $closer = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
244            if ($closer === false || $tokens[$closer]['code'] !== T_CLOSE_CURLY_BRACKET) {
245                return;
246            }
247        } else {
248            return;
249        }//end if
250
251        // Single space after closing brace.
252        $found = 1;
253        if ($tokens[($closer + 1)]['code'] !== T_WHITESPACE) {
254            $found = 0;
255        } else if ($tokens[($closer + 1)]['content'] !== ' ') {
256            if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) {
257                $found = 'newline';
258            } else {
259                $found = strlen($tokens[($closer + 1)]['content']);
260            }
261        }
262
263        if ($found !== 1) {
264            $error = 'Expected 1 space after closing brace; %s found';
265            $data  = array($found);
266            $fix   = $phpcsFile->addFixableError($error, $closer, 'SpaceAfterCloseBrace', $data);
267            if ($fix === true) {
268                if ($found === 0) {
269                    $phpcsFile->fixer->addContent($closer, ' ');
270                } else {
271                    $phpcsFile->fixer->replaceToken(($closer + 1), ' ');
272                }
273            }
274        }
275
276    }//end process()
277
278
279}//end class
280