Failed Conditions
Push — master ( c35fd9...2f93af )
by Alexander
02:57
created

FunctionCommentSniff::process()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 2
dl 0
loc 31
ccs 18
cts 18
cp 1
crap 5
rs 9.1128
c 0
b 0
f 0
1
<?php
2
/**
3
 * CodingStandard_Sniffs_Commenting_FunctionCommentSniff.
4
 *
5
 * PHP version 5
6
 *
7
 * @category PHP
8
 * @package  PHP_CodeSniffer
9
 * @author   Greg Sherwood <[email protected]>
10
 * @author   Alexander Obuhovich <[email protected]>
11
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
12
 * @link     https://github.com/aik099/CodingStandard
13
 */
14
15
namespace CodingStandard\Sniffs\Commenting;
16
17
use PHP_CodeSniffer\Files\File;
18
use PHP_CodeSniffer\Standards\Squiz\Sniffs\Commenting\FunctionCommentSniff as Squiz_FunctionCommentSniff;
19
use PHP_CodeSniffer\Util\Tokens;
20
21
/**
22
 * Parses and verifies the doc comments for functions.
23
 *
24
 * Same as the Squiz standard, but adds support for API tags.
25
 *
26
 * @category PHP
27
 * @package  PHP_CodeSniffer
28
 * @author   Greg Sherwood <[email protected]>
29
 * @author   Alexander Obuhovich <[email protected]>
30
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
31
 * @link     https://github.com/aik099/CodingStandard
32
 */
33
class FunctionCommentSniff extends Squiz_FunctionCommentSniff
34
{
35
36
37
    /**
38
     * Processes this test, when one of its tokens is encountered.
39
     *
40
     * @param File $phpcsFile The file being scanned.
41
     * @param int  $stackPtr  The position of the current token in the
42
     *                        stack passed in $tokens.
43
     *
44
     * @return void
45
     */
46 1
    public function process(File $phpcsFile, $stackPtr)
47
    {
48 1
        parent::process($phpcsFile, $stackPtr);
49
50 1
        $tokens = $phpcsFile->getTokens();
51 1
        $find   = Tokens::$methodPrefixes;
52 1
        $find[] = T_WHITESPACE;
53
54 1
        $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);
55 1
        if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) {
56 1
            return;
57
        }
58
59 1
        $commentStart = $tokens[$commentEnd]['comment_opener'];
60
61
        $empty = array(
62 1
                  T_DOC_COMMENT_WHITESPACE,
63 1
                  T_DOC_COMMENT_STAR,
64
                 );
65
66 1
        $short = $phpcsFile->findNext($empty, ($commentStart + 1), $commentEnd, true);
0 ignored issues
show
Bug introduced by
It seems like $commentEnd defined by $phpcsFile->findPrevious...ackPtr - 1, null, true) on line 54 can also be of type boolean; however, PHP_CodeSniffer\Files\File::findNext() does only seem to accept integer|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
67 1
        if ($short === false || $tokens[$short]['code'] !== T_DOC_COMMENT_STRING) {
68 1
            return;
69
        }
70
71 1
        if ($this->isInheritDoc($phpcsFile, $commentStart) === true) {
72 1
            return;
73
        }
74
75 1
        $this->checkShort($phpcsFile, $stackPtr, $tokens[$short]['content'], $short);
76 1
    }//end process()
77
78
    /**
79
     * Process the function parameter comments.
80
     *
81
     * @param File $phpcsFile    The file being scanned.
82
     * @param int  $stackPtr     The position of the current token
83
     *                           in the stack passed in $tokens.
84
     * @param int  $commentStart The position in the stack where the comment started.
85
     *
86
     * @return void
87
     */
88 1
    protected function processParams(File $phpcsFile, $stackPtr, $commentStart)
89
    {
90 1
        if ($this->isInheritDoc($phpcsFile, $commentStart) === true) {
91 1
            return;
92
        }
93
94 1
        parent::processParams($phpcsFile, $stackPtr, $commentStart);
95 1
    }//end processParams()
96
97
98
    /**
99
     * Process the return comment of this function comment.
100
     *
101
     * @param File $phpcsFile    The file being scanned.
102
     * @param int  $stackPtr     The position of the current token
103
     *                           in the stack passed in $tokens.
104
     * @param int  $commentStart The position in the stack where the comment started.
105
     *
106
     * @return void
107
     */
108 1
    protected function processReturn(File $phpcsFile, $stackPtr, $commentStart)
109
    {
110 1
        if ($this->isInheritDoc($phpcsFile, $commentStart) === true) {
111 1
            return;
112
        }
113
114 1
        parent::processReturn($phpcsFile, $stackPtr, $commentStart);
115
116 1
        $return = null;
117 1
        $tokens = $phpcsFile->getTokens();
118
119 1
        foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
120 1
            if ($tokens[$tag]['content'] === '@return') {
121 1
                $return = $tag;
122 1
                break;
123
            }
124
        }
125
126 1
        if ($return !== null) {
127 1
            $returnType = $tokens[($return + 2)]['content'];
128 1
            if ($returnType === '$this') {
129 1
                $error = 'Function return type "%s" is invalid, please use "static" or "self" instead';
130 1
                $data  = array($returnType);
131 1
                $phpcsFile->addError($error, $return, 'InvalidThisReturn', $data);
132
            }
133
        }
134 1
    }//end processReturn()
135
136
    /**
137
     * Process the short description of a function comment.
138
     *
139
     * @param File   $phpcsFile The file being scanned.
140
     * @param int    $stackPtr  The position of the function token
141
     *                          in the stack passed in $tokens.
142
     * @param string $short     The content of the short description.
143
     * @param int    $errorPos  The position where an error should be thrown.
144
     *
145
     * @return void
146
     */
147 1
    public function checkShort(File $phpcsFile, $stackPtr, $short, $errorPos)
148
    {
149 1
        $tokens = $phpcsFile->getTokens();
150
151 1
        $classToken = null;
152 1
        foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condition) {
153 1
            if ($condition === T_CLASS || $condition === T_INTERFACE || $condition === T_TRAIT) {
154 1
                $classToken = $condPtr;
155 1
                break;
156
            }
157
        }
158
159 1
        $isEvent = false;
160 1
        if ($classToken !== null) {
161 1
            $className = $phpcsFile->getDeclarationName($classToken);
162 1
            if (strpos($className, 'EventHandler') !== false) {
163 1
                $methodName = $phpcsFile->getDeclarationName($stackPtr);
164 1
                if (substr($methodName, 0, 2) === 'On') {
165 1
                    $isEvent = true;
166
                }
167
            }
168
        }
169
170 1
        if ($isEvent === true && preg_match('/(\p{Lu}|\[)/u', $short[0]) === 0) {
171 1
            $error = 'Event comment short description must start with a capital letter or an [';
172 1
            $phpcsFile->addError($error, $errorPos, 'EventShortNotCapital');
173 1
        } elseif ($isEvent === false && preg_match('/\p{Lu}/u', $short[0]) === 0) {
174 1
            $error = 'Doc comment short description must start with a capital letter';
175 1
            $phpcsFile->addError($error, $errorPos, 'NonEventShortNotCapital');
176
        }
177 1
    }//end checkShort()
178
179
180
    /**
181
     * Process any throw tags that this function comment has.
182
     *
183
     * @param File $phpcsFile    The file being scanned.
184
     * @param int  $stackPtr     The position of the current token
185
     *                           in the stack passed in $tokens.
186
     * @param int  $commentStart The position in the stack where the comment started.
187
     *
188
     * @return void
189
     */
190 1
    protected function processThrows(File $phpcsFile, $stackPtr, $commentStart)
191
    {
192 1
        parent::processThrows($phpcsFile, $stackPtr, $commentStart);
193
194 1
        $tokens = $phpcsFile->getTokens();
195
196 1
        foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
197
            // Only process method with scope (e.g. non-abstract methods or methods on the interface).
198 1
            if ($tokens[$tag]['content'] !== '@throws' || isset($tokens[$stackPtr]['scope_opener']) === false) {
199 1
                continue;
200
            }
201
202 1
            $throwPtr = $phpcsFile->findNext(
203 1
                T_THROW,
204 1
                $tokens[$stackPtr]['scope_opener'],
205 1
                $tokens[$stackPtr]['scope_closer']
206
            );
207
208 1
            if ($throwPtr !== false) {
209 1
                break;
210
            }
211
212 1
            $throwsWithString = null;
213 1
            $error            = '@throws tag found, but no exceptions are thrown by the function';
214
215 1
            if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) {
216 1
                $throwsWithString = true;
217 1
            } elseif ($tokens[($tag + 1)]['code'] === T_DOC_COMMENT_WHITESPACE
218 1
                && $tokens[($tag + 1)]['content'] === $phpcsFile->eolChar
219
            ) {
220 1
                $throwsWithString = false;
221
            }
222
223 1
            if ($tokens[($tag - 2)]['code'] === T_DOC_COMMENT_STAR && isset($throwsWithString) === true) {
224 1
                $fix = $phpcsFile->addFixableError($error, $tag, 'ExcessiveThrows');
225
226 1
                if ($fix === true) {
227 1
                    $phpcsFile->fixer->beginChangeset();
228
229 1
                    $removeEndPtr = $throwsWithString === true ? ($tag + 3) : ($tag + 1);
230
231 1
                    for ($i = ($tag - 4); $i < $removeEndPtr; $i++) {
232 1
                        $phpcsFile->fixer->replaceToken($i, '');
233
                    }
234
235 1
                    $phpcsFile->fixer->endChangeset();
236
                }
237
            } else {
238 1
                $phpcsFile->addError($error, $tag, 'ExcessiveThrows');
239
            }
240
        }//end foreach
241 1
    }//end processThrows()
242
243
244
    /**
245
     * Is the comment an inheritdoc?
246
     *
247
     * @param File $phpcsFile    The file being scanned.
248
     * @param int  $commentStart The position in the stack where the comment started.
249
     *
250
     * @return bool
251
     */
252 1
    protected function isInheritDoc(File $phpcsFile, $commentStart)
253
    {
254 1
        $tokens = $phpcsFile->getTokens();
255
256 1
        $commentEnd  = $tokens[$commentStart]['comment_closer'];
257 1
        $commentText = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1));
258
259 1
        return stripos($commentText, '{@inheritdoc}') !== false;
260
    }// end isInheritDoc()
261
}//end class
262