1<?php
2/**
3 * Squiz_Sniffs_Functions_MultiLineFunctionDeclarationSniff.
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
15if (class_exists('PEAR_Sniffs_Functions_FunctionDeclarationSniff', true) === false) {
16    $error = 'Class PEAR_Sniffs_Functions_FunctionDeclarationSniff not found';
17    throw new PHP_CodeSniffer_Exception($error);
18}
19
20/**
21 * Squiz_Sniffs_Functions_MultiLineFunctionDeclarationSniff.
22 *
23 * Ensure single and multi-line function declarations are defined correctly.
24 *
25 * @category  PHP
26 * @package   PHP_CodeSniffer
27 * @author    Greg Sherwood <gsherwood@squiz.net>
28 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
29 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
30 * @version   Release: @package_version@
31 * @link      http://pear.php.net/package/PHP_CodeSniffer
32 */
33class Squiz_Sniffs_Functions_MultiLineFunctionDeclarationSniff extends PEAR_Sniffs_Functions_FunctionDeclarationSniff
34{
35
36    /**
37     * A list of tokenizers this sniff supports.
38     *
39     * @var array
40     */
41    public $supportedTokenizers = array(
42                                   'PHP',
43                                   'JS',
44                                  );
45
46
47    /**
48     * Determine if this is a multi-line function declaration.
49     *
50     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
51     * @param int                  $stackPtr    The position of the current token
52     *                                          in the stack passed in $tokens.
53     * @param int                  $openBracket The position of the opening bracket
54     *                                          in the stack passed in $tokens.
55     * @param array                $tokens      The stack of tokens that make up
56     *                                          the file.
57     *
58     * @return void
59     */
60    public function isMultiLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens)
61    {
62        $bracketsToCheck = array($stackPtr => $openBracket);
63
64        // Closures may use the USE keyword and so be multi-line in this way.
65        if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
66            $use = $phpcsFile->findNext(T_USE, ($tokens[$openBracket]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']);
67            if ($use !== false) {
68                $open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1));
69                if ($open !== false) {
70                    $bracketsToCheck[$use] = $open;
71                }
72            }
73        }
74
75        foreach ($bracketsToCheck as $stackPtr => $openBracket) {
76            // If the first argument is on a new line, this is a multi-line
77            // function declaration, even if there is only one argument.
78            $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($openBracket + 1), null, true);
79            if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
80                return true;
81            }
82
83            $closeBracket = $tokens[$openBracket]['parenthesis_closer'];
84
85            $end = $phpcsFile->findEndOfStatement($openBracket + 1);
86            while ($tokens[$end]['code'] === T_COMMA) {
87                // If the next bit of code is not on the same line, this is a
88                // multi-line function declaration.
89                $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($end + 1), $closeBracket, true);
90                if ($next === false) {
91                    continue(2);
92                }
93
94                if ($tokens[$next]['line'] !== $tokens[$end]['line']) {
95                    return true;
96                }
97
98                $end = $phpcsFile->findEndOfStatement($next);
99            }
100
101            // We've reached the last argument, so see if the next content
102            // (should be the close bracket) is also on the same line.
103            $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($end + 1), $closeBracket, true);
104            if ($next !== false && $tokens[$next]['line'] !== $tokens[$end]['line']) {
105                return true;
106            }
107        }//end foreach
108
109        return false;
110
111    }//end isMultiLineDeclaration()
112
113
114    /**
115     * Processes multi-line declarations.
116     *
117     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
118     * @param int                  $stackPtr  The position of the current token
119     *                                        in the stack passed in $tokens.
120     * @param array                $tokens    The stack of tokens that make up
121     *                                        the file.
122     *
123     * @return void
124     */
125    public function processMultiLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
126    {
127        // We do everything the parent sniff does, and a bit more.
128        parent::processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens);
129
130        $openBracket = $tokens[$stackPtr]['parenthesis_opener'];
131        $this->processBracket($phpcsFile, $openBracket, $tokens, 'function');
132
133        if ($tokens[$stackPtr]['code'] !== T_CLOSURE) {
134            return;
135        }
136
137        $use = $phpcsFile->findNext(T_USE, ($tokens[$stackPtr]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']);
138        if ($use === false) {
139            return;
140        }
141
142        $openBracket = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1), null);
143        $this->processBracket($phpcsFile, $openBracket, $tokens, 'use');
144
145        // Also check spacing.
146        if ($tokens[($use - 1)]['code'] === T_WHITESPACE) {
147            $gap = strlen($tokens[($use - 1)]['content']);
148        } else {
149            $gap = 0;
150        }
151
152    }//end processMultiLineDeclaration()
153
154
155    /**
156     * Processes the contents of a single set of brackets.
157     *
158     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
159     * @param int                  $openBracket The position of the open bracket
160     *                                          in the stack passed in $tokens.
161     * @param array                $tokens      The stack of tokens that make up
162     *                                          the file.
163     * @param string               $type        The type of the token the brackets
164     *                                          belong to (function or use).
165     *
166     * @return void
167     */
168    public function processBracket(PHP_CodeSniffer_File $phpcsFile, $openBracket, $tokens, $type='function')
169    {
170        $errorPrefix = '';
171        if ($type === 'use') {
172            $errorPrefix = 'Use';
173        }
174
175        $closeBracket = $tokens[$openBracket]['parenthesis_closer'];
176
177        // The open bracket should be the last thing on the line.
178        if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) {
179            $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($openBracket + 1), null, true);
180            if ($tokens[$next]['line'] !== ($tokens[$openBracket]['line'] + 1)) {
181                $error = 'The first parameter of a multi-line '.$type.' declaration must be on the line after the opening bracket';
182                $fix   = $phpcsFile->addFixableError($error, $next, $errorPrefix.'FirstParamSpacing');
183                if ($fix === true) {
184                    $phpcsFile->fixer->addNewline($openBracket);
185                }
186            }
187        }
188
189        // Each line between the brackets should contain a single parameter.
190        $lastComma = null;
191        for ($i = ($openBracket + 1); $i < $closeBracket; $i++) {
192            // Skip brackets, like arrays, as they can contain commas.
193            if (isset($tokens[$i]['bracket_opener']) === true) {
194                $i = $tokens[$i]['bracket_closer'];
195                continue;
196            }
197
198            if (isset($tokens[$i]['parenthesis_opener']) === true) {
199                $i = $tokens[$i]['parenthesis_closer'];
200                continue;
201            }
202
203            if ($tokens[$i]['code'] !== T_COMMA) {
204                continue;
205            }
206
207            $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true);
208            if ($tokens[$next]['line'] === $tokens[$i]['line']) {
209                $error = 'Multi-line '.$type.' declarations must define one parameter per line';
210                $fix   = $phpcsFile->addFixableError($error, $next, $errorPrefix.'OneParamPerLine');
211                if ($fix === true) {
212                    $phpcsFile->fixer->addNewline($i);
213                }
214            }
215        }//end for
216
217    }//end processBracket()
218
219
220}//end class
221