1<?php
2/**
3 * Squiz_Sniffs_WhiteSpace_FunctionClosingBraceSpaceSniff.
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 * Squiz_Sniffs_WhiteSpace_FunctionClosingBraceSpaceSniff.
18 *
19 * Checks that there is one empty line before the closing brace of a function.
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 Squiz_Sniffs_WhiteSpace_FunctionClosingBraceSpaceSniff implements PHP_CodeSniffer_Sniff
31{
32
33    /**
34     * A list of tokenizers this sniff supports.
35     *
36     * @var array
37     */
38    public $supportedTokenizers = array(
39                                   'PHP',
40                                   'JS',
41                                  );
42
43
44    /**
45     * Returns an array of tokens this test wants to listen for.
46     *
47     * @return array
48     */
49    public function register()
50    {
51        return array(
52                T_FUNCTION,
53                T_CLOSURE,
54               );
55
56    }//end register()
57
58
59    /**
60     * Processes this test, when one of its tokens is encountered.
61     *
62     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
63     * @param int                  $stackPtr  The position of the current token
64     *                                        in the stack passed in $tokens.
65     *
66     * @return void
67     */
68    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
69    {
70        $tokens = $phpcsFile->getTokens();
71
72        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
73            // Probably an interface method.
74            return;
75        }
76
77        $closeBrace  = $tokens[$stackPtr]['scope_closer'];
78        $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, ($closeBrace - 1), null, true);
79
80        // Special case for empty JS functions.
81        if ($phpcsFile->tokenizerType === 'JS' && $prevContent === $tokens[$stackPtr]['scope_opener']) {
82            // In this case, the opening and closing brace must be
83            // right next to each other.
84            if ($tokens[$stackPtr]['scope_closer'] !== ($tokens[$stackPtr]['scope_opener'] + 1)) {
85                $error = 'The opening and closing braces of empty functions must be directly next to each other; e.g., function () {}';
86                $fix   = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBetween');
87                if ($fix === true) {
88                    $phpcsFile->fixer->beginChangeset();
89                    for ($i = ($tokens[$stackPtr]['scope_opener'] + 1); $i < $closeBrace; $i++) {
90                        $phpcsFile->fixer->replaceToken($i, '');
91                    }
92
93                    $phpcsFile->fixer->endChangeset();
94                }
95            }
96
97            return;
98        }
99
100        $nestedFunction = false;
101        if ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true
102            || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true
103            || isset($tokens[$stackPtr]['nested_parenthesis']) === true
104        ) {
105            $nestedFunction = true;
106        }
107
108        $braceLine = $tokens[$closeBrace]['line'];
109        $prevLine  = $tokens[$prevContent]['line'];
110        $found     = ($braceLine - $prevLine - 1);
111
112        $afterKeyword  = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
113        $beforeKeyword = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
114        if ($nestedFunction === true) {
115            if ($found < 0) {
116                $error = 'Closing brace of nested function must be on a new line';
117                $fix   = $phpcsFile->addFixableError($error, $closeBrace, 'ContentBeforeClose');
118                if ($fix === true) {
119                    $phpcsFile->fixer->addNewlineBefore($closeBrace);
120                }
121            } else if ($found > 0) {
122                $error = 'Expected 0 blank lines before closing brace of nested function; %s found';
123                $data  = array($found);
124                $fix   = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBeforeNestedClose', $data);
125
126                if ($fix === true) {
127                    $phpcsFile->fixer->beginChangeset();
128                    $changeMade = false;
129                    for ($i = ($prevContent + 1); $i < $closeBrace; $i++) {
130                        // Try and maintain indentation.
131                        if ($tokens[$i]['line'] === ($braceLine - 1)) {
132                            break;
133                        }
134
135                        $phpcsFile->fixer->replaceToken($i, '');
136                        $changeMade = true;
137                    }
138
139                    // Special case for when the last content contains the newline
140                    // token as well, like with a comment.
141                    if ($changeMade === false) {
142                        $phpcsFile->fixer->replaceToken(($prevContent + 1), '');
143                    }
144
145                    $phpcsFile->fixer->endChangeset();
146                }//end if
147            }//end if
148        } else {
149            if ($found !== 1) {
150                if ($found < 0) {
151                    $found = 0;
152                }
153
154                $error = 'Expected 1 blank line before closing function brace; %s found';
155                $data  = array($found);
156                $fix   = $phpcsFile->addFixableError($error, $closeBrace, 'SpacingBeforeClose', $data);
157
158                if ($fix === true) {
159                    if ($found > 1) {
160                        $phpcsFile->fixer->beginChangeset();
161                        for ($i = ($prevContent + 1); $i < ($closeBrace - 1); $i++) {
162                            $phpcsFile->fixer->replaceToken($i, '');
163                        }
164
165                        $phpcsFile->fixer->replaceToken($i, $phpcsFile->eolChar);
166                        $phpcsFile->fixer->endChangeset();
167                    } else {
168                        // Try and maintain indentation.
169                        if ($tokens[($closeBrace - 1)]['code'] === T_WHITESPACE) {
170                            $phpcsFile->fixer->addNewlineBefore($closeBrace - 1);
171                        } else {
172                            $phpcsFile->fixer->addNewlineBefore($closeBrace);
173                        }
174                    }
175                }
176            }//end if
177        }//end if
178
179    }//end process()
180
181
182}//end class
183