1<?php
2/**
3 * PSR2_Sniffs_Methods_MethodDeclarationSniff.
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('PHP_CodeSniffer_Standards_AbstractScopeSniff', true) === false) {
16    throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractScopeSniff not found');
17}
18
19/**
20 * PSR2_Sniffs_Methods_MethodDeclarationSniff.
21 *
22 * Checks that the method declaration is correct.
23 *
24 * @category  PHP
25 * @package   PHP_CodeSniffer
26 * @author    Greg Sherwood <gsherwood@squiz.net>
27 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
28 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
29 * @version   Release: @package_version@
30 * @link      http://pear.php.net/package/PHP_CodeSniffer
31 */
32class PSR2_Sniffs_Methods_MethodDeclarationSniff extends PHP_CodeSniffer_Standards_AbstractScopeSniff
33{
34
35
36    /**
37     * Constructs a Squiz_Sniffs_Scope_MethodScopeSniff.
38     */
39    public function __construct()
40    {
41        parent::__construct(array(T_CLASS, T_INTERFACE), array(T_FUNCTION));
42
43    }//end __construct()
44
45
46    /**
47     * Processes the function tokens within the class.
48     *
49     * @param PHP_CodeSniffer_File $phpcsFile The file where this token was found.
50     * @param int                  $stackPtr  The position where the token was found.
51     * @param int                  $currScope The current scope opener token.
52     *
53     * @return void
54     */
55    protected function processTokenWithinScope(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $currScope)
56    {
57        $tokens = $phpcsFile->getTokens();
58
59        $methodName = $phpcsFile->getDeclarationName($stackPtr);
60        if ($methodName === null) {
61            // Ignore closures.
62            return;
63        }
64
65        if ($methodName[0] === '_' && isset($methodName[1]) === true && $methodName[1] !== '_') {
66            $error = 'Method name "%s" should not be prefixed with an underscore to indicate visibility';
67            $data  = array($methodName);
68            $phpcsFile->addWarning($error, $stackPtr, 'Underscore', $data);
69        }
70
71        $visibility = 0;
72        $static     = 0;
73        $abstract   = 0;
74        $final      = 0;
75
76        $find   = PHP_CodeSniffer_Tokens::$methodPrefixes;
77        $find[] = T_WHITESPACE;
78        $prev   = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);
79
80        $prefix = $stackPtr;
81        while (($prefix = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$methodPrefixes, ($prefix - 1), $prev)) !== false) {
82            switch ($tokens[$prefix]['code']) {
83            case T_STATIC:
84                $static = $prefix;
85                break;
86            case T_ABSTRACT:
87                $abstract = $prefix;
88                break;
89            case T_FINAL:
90                $final = $prefix;
91                break;
92            default:
93                $visibility = $prefix;
94                break;
95            }
96        }
97
98        $fixes = array();
99
100        if ($visibility !== 0 && $final > $visibility) {
101            $error = 'The final declaration must precede the visibility declaration';
102            $fix   = $phpcsFile->addFixableError($error, $final, 'FinalAfterVisibility');
103            if ($fix === true) {
104                $fixes[$final]       = '';
105                $fixes[($final + 1)] = '';
106                if (isset($fixes[$visibility]) === true) {
107                    $fixes[$visibility] = 'final '.$fixes[$visibility];
108                } else {
109                    $fixes[$visibility] = 'final '.$tokens[$visibility]['content'];
110                }
111            }
112        }
113
114        if ($visibility !== 0 && $abstract > $visibility) {
115            $error = 'The abstract declaration must precede the visibility declaration';
116            $fix   = $phpcsFile->addFixableError($error, $abstract, 'AbstractAfterVisibility');
117            if ($fix === true) {
118                $fixes[$abstract]       = '';
119                $fixes[($abstract + 1)] = '';
120                if (isset($fixes[$visibility]) === true) {
121                    $fixes[$visibility] = 'abstract '.$fixes[$visibility];
122                } else {
123                    $fixes[$visibility] = 'abstract '.$tokens[$visibility]['content'];
124                }
125            }
126        }
127
128        if ($static !== 0 && $static < $visibility) {
129            $error = 'The static declaration must come after the visibility declaration';
130            $fix   = $phpcsFile->addFixableError($error, $static, 'StaticBeforeVisibility');
131            if ($fix === true) {
132                $fixes[$static]       = '';
133                $fixes[($static + 1)] = '';
134                if (isset($fixes[$visibility]) === true) {
135                    $fixes[$visibility] = $fixes[$visibility].' static';
136                } else {
137                    $fixes[$visibility] = $tokens[$visibility]['content'].' static';
138                }
139            }
140        }
141
142        // Batch all the fixes together to reduce the possibility of conflicts.
143        if (empty($fixes) === false) {
144            $phpcsFile->fixer->beginChangeset();
145            foreach ($fixes as $stackPtr => $content) {
146                $phpcsFile->fixer->replaceToken($stackPtr, $content);
147            }
148
149            $phpcsFile->fixer->endChangeset();
150        }
151
152    }//end processTokenWithinScope()
153
154
155}//end class
156