1<?php
2/**
3 * Generic_Sniffs_Functions_FunctionCallArgumentSpacingSniff.
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
16/**
17 * Generic_Sniffs_Functions_FunctionCallArgumentSpacingSniff.
18 *
19 * Checks that calls to methods and functions are spaced correctly.
20 *
21 * @category  PHP
22 * @package   PHP_CodeSniffer
23 * @author    Greg Sherwood <gsherwood@squiz.net>
24 * @author    Marc McIntyre <mmcintyre@squiz.net>
25 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
26 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
27 * @version   Release: @package_version@
28 * @link      http://pear.php.net/package/PHP_CodeSniffer
29 */
30class Generic_Sniffs_Functions_FunctionCallArgumentSpacingSniff implements PHP_CodeSniffer_Sniff
31{
32
33
34    /**
35     * Returns an array of tokens this test wants to listen for.
36     *
37     * @return array
38     */
39    public function register()
40    {
41        $tokens = PHP_CodeSniffer_Tokens::$functionNameTokens;
42
43        // For calling closures.
44        $tokens[] = T_VARIABLE;
45
46        return $tokens;
47
48    }//end register()
49
50
51    /**
52     * Processes this test, when one of its tokens is encountered.
53     *
54     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
55     * @param int                  $stackPtr  The position of the current token in the
56     *                                        stack passed in $tokens.
57     *
58     * @return void
59     */
60    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
61    {
62        $tokens = $phpcsFile->getTokens();
63
64        // Skip tokens that are the names of functions or classes
65        // within their definitions. For example:
66        // function myFunction...
67        // "myFunction" is T_STRING but we should skip because it is not a
68        // function or method *call*.
69        $functionName    = $stackPtr;
70        $ignoreTokens    = PHP_CodeSniffer_Tokens::$emptyTokens;
71        $ignoreTokens[]  = T_BITWISE_AND;
72        $functionKeyword = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
73        if ($tokens[$functionKeyword]['code'] === T_FUNCTION || $tokens[$functionKeyword]['code'] === T_CLASS) {
74            return;
75        }
76
77        // If the next non-whitespace token after the function or method call
78        // is not an opening parenthesis then it cant really be a *call*.
79        $openBracket = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($functionName + 1), null, true);
80        if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) {
81            return;
82        }
83
84        if (isset($tokens[$openBracket]['parenthesis_closer']) === false) {
85            return;
86        }
87
88        $closeBracket  = $tokens[$openBracket]['parenthesis_closer'];
89        $nextSeparator = $openBracket;
90
91        $find = array(
92                 T_COMMA,
93                 T_VARIABLE,
94                 T_CLOSURE,
95                 T_OPEN_SHORT_ARRAY,
96                );
97
98        while (($nextSeparator = $phpcsFile->findNext($find, ($nextSeparator + 1), $closeBracket)) !== false) {
99            if ($tokens[$nextSeparator]['code'] === T_CLOSURE) {
100                // Skip closures.
101                $nextSeparator = $tokens[$nextSeparator]['scope_closer'];
102                continue;
103            } else if ($tokens[$nextSeparator]['code'] === T_OPEN_SHORT_ARRAY) {
104                // Skips arrays using short notation.
105                $nextSeparator = $tokens[$nextSeparator]['bracket_closer'];
106                continue;
107            }
108
109            // Make sure the comma or variable belongs directly to this function call,
110            // and is not inside a nested function call or array.
111            $brackets    = $tokens[$nextSeparator]['nested_parenthesis'];
112            $lastBracket = array_pop($brackets);
113            if ($lastBracket !== $closeBracket) {
114                continue;
115            }
116
117            if ($tokens[$nextSeparator]['code'] === T_COMMA) {
118                if ($tokens[($nextSeparator - 1)]['code'] === T_WHITESPACE) {
119                    $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($nextSeparator - 2), null, true);
120                    if (isset(PHP_CodeSniffer_Tokens::$heredocTokens[$tokens[$prev]['code']]) === false) {
121                        $error = 'Space found before comma in function call';
122                        $fix   = $phpcsFile->addFixableError($error, $nextSeparator, 'SpaceBeforeComma');
123                        if ($fix === true) {
124                            $phpcsFile->fixer->replaceToken(($nextSeparator - 1), '');
125                        }
126                    }
127                }
128
129                if ($tokens[($nextSeparator + 1)]['code'] !== T_WHITESPACE) {
130                    $error = 'No space found after comma in function call';
131                    $fix   = $phpcsFile->addFixableError($error, $nextSeparator, 'NoSpaceAfterComma');
132                    if ($fix === true) {
133                        $phpcsFile->fixer->addContent($nextSeparator, ' ');
134                    }
135                } else {
136                    // If there is a newline in the space, then they must be formatting
137                    // each argument on a newline, which is valid, so ignore it.
138                    $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($nextSeparator + 1), null, true);
139                    if ($tokens[$next]['line'] === $tokens[$nextSeparator]['line']) {
140                        $space = strlen($tokens[($nextSeparator + 1)]['content']);
141                        if ($space > 1) {
142                            $error = 'Expected 1 space after comma in function call; %s found';
143                            $data  = array($space);
144                            $fix   = $phpcsFile->addFixableError($error, $nextSeparator, 'TooMuchSpaceAfterComma', $data);
145                            if ($fix === true) {
146                                $phpcsFile->fixer->replaceToken(($nextSeparator + 1), ' ');
147                            }
148                        }
149                    }
150                }//end if
151            } else {
152                // Token is a variable.
153                $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($nextSeparator + 1), $closeBracket, true);
154                if ($nextToken !== false) {
155                    if ($tokens[$nextToken]['code'] === T_EQUAL) {
156                        if (($tokens[($nextToken - 1)]['code']) !== T_WHITESPACE) {
157                            $error = 'Expected 1 space before = sign of default value';
158                            $fix   = $phpcsFile->addFixableError($error, $nextToken, 'NoSpaceBeforeEquals');
159                            if ($fix === true) {
160                                $phpcsFile->fixer->addContentBefore($nextToken, ' ');
161                            }
162                        }
163
164                        if ($tokens[($nextToken + 1)]['code'] !== T_WHITESPACE) {
165                            $error = 'Expected 1 space after = sign of default value';
166                            $fix   = $phpcsFile->addFixableError($error, $nextToken, 'NoSpaceAfterEquals');
167                            if ($fix === true) {
168                                $phpcsFile->fixer->addContent($nextToken, ' ');
169                            }
170                        }
171                    }
172                }
173            }//end if
174        }//end while
175
176    }//end process()
177
178
179}//end class
180