1<?php
2/**
3 * Generic_Sniffs_Functions_OpeningFunctionBraceKernighanRitchieSniff.
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_OpeningFunctionBraceKernighanRitchieSniff.
18 *
19 * Checks that the opening brace of a function is on the same line
20 * as the function declaration.
21 *
22 * @category  PHP
23 * @package   PHP_CodeSniffer
24 * @author    Greg Sherwood <gsherwood@squiz.net>
25 * @author    Marc McIntyre <mmcintyre@squiz.net>
26 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
27 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
28 * @version   Release: @package_version@
29 * @link      http://pear.php.net/package/PHP_CodeSniffer
30 */
31class Generic_Sniffs_Functions_OpeningFunctionBraceKernighanRitchieSniff implements PHP_CodeSniffer_Sniff
32{
33
34
35    /**
36     * Should this sniff check function braces?
37     *
38     * @var bool
39     */
40    public $checkFunctions = true;
41
42    /**
43     * Should this sniff check closure braces?
44     *
45     * @var bool
46     */
47    public $checkClosures = false;
48
49
50    /**
51     * Registers the tokens that this sniff wants to listen for.
52     *
53     * @return void
54     */
55    public function register()
56    {
57        return array(
58                T_FUNCTION,
59                T_CLOSURE,
60               );
61
62    }//end register()
63
64
65    /**
66     * Processes this test, when one of its tokens is encountered.
67     *
68     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
69     * @param int                  $stackPtr  The position of the current token in the
70     *                                        stack passed in $tokens.
71     *
72     * @return void
73     */
74    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
75    {
76        $tokens = $phpcsFile->getTokens();
77
78        if (isset($tokens[$stackPtr]['scope_opener']) === false) {
79            return;
80        }
81
82        if (($tokens[$stackPtr]['code'] === T_FUNCTION
83            && (bool) $this->checkFunctions === false)
84            || ($tokens[$stackPtr]['code'] === T_CLOSURE
85            && (bool) $this->checkClosures === false)
86        ) {
87            return;
88        }
89
90        $openingBrace = $tokens[$stackPtr]['scope_opener'];
91        $closeBracket = $tokens[$stackPtr]['parenthesis_closer'];
92        if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
93            $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']);
94            if ($use !== false) {
95                $openBracket  = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1));
96                $closeBracket = $tokens[$openBracket]['parenthesis_closer'];
97            }
98        }
99
100        $functionLine = $tokens[$closeBracket]['line'];
101        $braceLine    = $tokens[$openingBrace]['line'];
102
103        $lineDifference = ($braceLine - $functionLine);
104
105        if ($lineDifference > 0) {
106            $phpcsFile->recordMetric($stackPtr, 'Function opening brace placement', 'new line');
107            $error = 'Opening brace should be on the same line as the declaration';
108            $fix   = $phpcsFile->addFixableError($error, $openingBrace, 'BraceOnNewLine');
109            if ($fix === true) {
110                $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($openingBrace - 1), $closeBracket, true);
111                $phpcsFile->fixer->beginChangeset();
112                $phpcsFile->fixer->addContent($prev, ' {');
113                $phpcsFile->fixer->replaceToken($openingBrace, '');
114                if ($tokens[($openingBrace + 1)]['code'] === T_WHITESPACE
115                    && $tokens[($openingBrace + 2)]['line'] > $tokens[$openingBrace]['line']
116                ) {
117                    // Brace is followed by a new line, so remove it to ensure we don't
118                    // leave behind a blank line at the top of the block.
119                    $phpcsFile->fixer->replaceToken(($openingBrace + 1), '');
120
121                    if ($tokens[($openingBrace - 1)]['code'] === T_WHITESPACE
122                        && $tokens[($openingBrace - 1)]['line'] === $tokens[$openingBrace]['line']
123                        && $tokens[($openingBrace - 2)]['line'] < $tokens[$openingBrace]['line']
124                    ) {
125                        // Brace is preceeded by indent, so remove it to ensure we don't
126                        // leave behind more indent than is required for the first line.
127                        $phpcsFile->fixer->replaceToken(($openingBrace - 1), '');
128                    }
129                }
130
131                $phpcsFile->fixer->endChangeset();
132            }//end if
133        }//end if
134
135        $phpcsFile->recordMetric($stackPtr, 'Function opening brace placement', 'same line');
136
137        $next = $phpcsFile->findNext(T_WHITESPACE, ($openingBrace + 1), null, true);
138        if ($tokens[$next]['line'] === $tokens[$openingBrace]['line']) {
139            if ($next === $tokens[$stackPtr]['scope_closer']) {
140                // Ignore empty functions.
141                return;
142            }
143
144            $error = 'Opening brace must be the last content on the line';
145            $fix   = $phpcsFile->addFixableError($error, $openingBrace, 'ContentAfterBrace');
146            if ($fix === true) {
147                $phpcsFile->fixer->addNewline($openingBrace);
148            }
149        }
150
151        // Only continue checking if the opening brace looks good.
152        if ($lineDifference > 0) {
153            return;
154        }
155
156        if ($tokens[($openingBrace - 1)]['code'] !== T_WHITESPACE) {
157            $length = 0;
158        } else if ($tokens[($openingBrace - 1)]['content'] === "\t") {
159            $length = '\t';
160        } else {
161            $length = strlen($tokens[($openingBrace - 1)]['content']);
162        }
163
164        if ($length !== 1) {
165            $error = 'Expected 1 space before opening brace; found %s';
166            $data  = array($length);
167            $fix   = $phpcsFile->addFixableError($error, $closeBracket, 'SpaceBeforeBrace', $data);
168            if ($fix === true) {
169                if ($length === 0 || $length === '\t') {
170                    $phpcsFile->fixer->addContentBefore($openingBrace, ' ');
171                } else {
172                    $phpcsFile->fixer->replaceToken(($openingBrace - 1), ' ');
173                }
174            }
175        }
176
177    }//end process()
178
179
180}//end class
181