Passed
Pull Request — master (#13)
by
unknown
02:02
created

MethodDocSniff::checkCapitalLetter()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 3
Ratio 23.08 %

Code Coverage

Tests 8
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 3
loc 13
ccs 8
cts 8
cp 1
rs 8.8571
cc 5
eloc 7
nc 4
nop 2
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BestIt\Sniffs\Commenting;
6
7
use PHP_CodeSniffer_File;
8
use PHP_CodeSniffer_Sniff;
9
10
/**
11
 * This Sniff checks the method phpdoc
12
 *
13
 * It is a modified version of the Generic DocCommentSniff adjusted for the bestit codestyle
14
 *
15
 * @package BestIt\Sniffs\Commenting
16
 * @author Nils Hardeweg <[email protected]>
17
 */
18
class MethodDocSniff implements PHP_CodeSniffer_Sniff
19
{
20
    #region constants
21
    /**
22
     * Code for an empty doc block
23
     *
24
     * @var string
25
     */
26
    const CODE_EMPTY = 'Empty';
27
28
    /**
29
     * Message for an empty doc block
30
     *
31
     * @var string
32
     */
33
    const MESSAGE_DOC_EMPTY = 'Doc comment is empty';
34
35
    /**
36
     * Code for a Spacing Error after the Summary
37
     *
38
     * @var string
39
     */
40
    const CODE_SPACING_AFTER = 'SpacingAfter';
41
42
    /**
43
     * Message for a Spacing Error after the Summary
44
     *
45
     * @var string
46
     */
47
    const MESSAGE_SPACING_AFTER = 'Additional blank lines found at end of doc comment';
48
49
    /**
50
     * Code for missing spacing between descriptions
51
     *
52
     * @var string
53
     */
54
    const CODE_SPACING_BETWEEN = 'SpacingBetween';
55
56
    /**
57
     * Message for missing spacing between descriptions
58
     *
59
     * @var string
60
     */
61
    const MESSAGE_SPACING_BETWEEN = 'There must be exactly one blank line between descriptions in a doc comment';
62
63
    /**
64
     * Code for missing spacing before the tags
65
     *
66
     * @var string
67
     */
68
    const CODE_SPACING_BEFORE_TAGS = 'SpacingBeforeTags';
69
70
    /**
71
     * Message for missing spacing before the tags
72
     *
73
     * @var string
74
     */
75
    const MESSAGE_SPACING_BEFORE_TAGS = 'There must be exactly one blank line before the tags in a doc comment';
76
77
    /**
78
     * Code for missing short description (Summary)
79
     *
80
     * @var string
81
     */
82
    const CODE_MISSING_SHORT = 'MissingShort';
83
84
    /**
85
     * Message for missing short description (Summary)
86
     *
87
     * @var string
88
     */
89
    const MESSAGE_MISSING_SHORT = 'Missing short description in doc comment';
90
91
    /**
92
     * Code for not capitalized short description
93
     *
94
     * @var string
95
     */
96
    const CODE_SHORT_NOT_CAPITAL = 'ShortNotCapital';
97
98
    /**
99
     * Message for not capitalized short description
100
     *
101
     * @var string
102
     */
103
    const MESSAGE_SHORT_NOT_CAPITAL = 'Doc comment short description must start with a capital letter';
104
105
    /**
106
     * Code for not Capitalized long description
107
     *
108
     * @var string
109
     */
110
    const CODE_LONG_NOT_CAPITAL = 'LongNotCapital';
111
112
    /**
113
     * Message for not Capitalized long description
114
     *
115
     * @var string
116
     */
117
    const MESSAGE_LONG_NOT_CAPITAL = 'Doc comment long description must start with a capital letter';
118
119
    /**
120
     * Code for empty lines before the short description
121
     *
122
     * @var string
123
     */
124
    const CODE_SPACING_BEFORE_SHORT = 'SpacingBeforeShort';
125
126
    /**
127
     * Message for empty lines before the short description
128
     *
129
     * @var string
130
     */
131
    const MESSAGE_CODE_SPACING_BEFORE = 'Doc comment short description must be on the first line';
132
    #endregion
133
134
    /**
135
     * A list of tokenizers this sniff supports.
136
     *
137
     * @var array
138
     */
139
    public $supportedTokenizers = ['PHP', 'JS'];
140
141
    /**
142
     * The token array is the base of this evaluation
143
     *
144
     * @var array
145
     */
146
    public $tokens;
147
148
    /**
149
     * Definitions for an empty doc block
150
     *
151
     * @var array
152
     */
153
    public $empty = [T_DOC_COMMENT_WHITESPACE, T_DOC_COMMENT_STAR];
154
155
    /**
156
     * The phpcs file
157
     *
158
     * @var PHP_CodeSniffer_File
159
     */
160
    public $phpcsFile;
161
162
    /**
163
     * Pointer to the start of the comment
164
     *
165
     * @var int
166
     */
167
    public $commentStart;
168
169
    /**
170
     * Pointer to the start of the short description
171
     *
172
     * @var int
173
     */
174
    public $short;
175
176
    /**
177
     * Returns an array of tokens this test wants to listen for.
178
     *
179
     * @return array
180
     */
181 8
    public function register()
182
    {
183 8
        return [T_DOC_COMMENT_OPEN_TAG];
184
    }
185
186
    /**
187
     * Checks if there is a blank line at the end of the doc
188
     *
189
     * @param $commentEnd
190
     * @param $prev
191
     *
192
     * @return void
193
     */
194 7
    private function checkBlankLines($commentEnd, $prev)
195
    {
196
        // Check for additional blank lines at the end of the comment.
197 7 View Code Duplication
        if ($this->tokens[$prev]['line'] < ($this->tokens[$commentEnd]['line'] - 1)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
198
            $this->phpcsFile->addError(self::MESSAGE_SPACING_AFTER, $commentEnd, self::CODE_SPACING_AFTER);
199
        }
200 7
    }
201
202
    /**
203
     * Checks if short or long description starts with a capital letter
204
     *
205
     * @param $shortContent
206
     * @param $long
207
     *
208
     * @return void
209
     */
210 6
    private function checkCapitalLetter($shortContent, $long)
211
    {
212 6 View Code Duplication
        if (preg_match('/^\p{Ll}/u', $shortContent) === 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213 1
            $this->phpcsFile->addError(self::MESSAGE_SHORT_NOT_CAPITAL, $this->short, self::CODE_SHORT_NOT_CAPITAL);
214
        }
215
216 6
        if ($long !== false
217 6
            && $this->tokens[$long]['code'] === T_DOC_COMMENT_STRING && preg_match('
218 6
            /^\p{Ll}/u', $this->tokens[$long]['content']) === 1
219
        ) {
220 1
            $this->phpcsFile->addError(self::MESSAGE_LONG_NOT_CAPITAL, $long, self::CODE_LONG_NOT_CAPITAL);
221
        }
222 6
    }
223
224
    /**
225
     * Checks capital letter and spacing of long description
226
     *
227
     * @param $shortEnd
228
     * @param $long
229
     *
230
     * @return void
231
     */
232 6
    private function checkLongDescription(
233
        $shortEnd,
234
        $long
235
    ) {
236 6
        if ($long !== false && $this->tokens[$long]['code'] === T_DOC_COMMENT_STRING) {
237 2 View Code Duplication
            if ($this->tokens[$long]['line'] !== ($this->tokens[$shortEnd]['line'] + 2)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
238 1
                $this->phpcsFile->addError(self::MESSAGE_SPACING_BETWEEN, $long, self::CODE_SPACING_BETWEEN);
239
            }
240
241 2
            if (preg_match('/^\p{Ll}/u', $this->tokens[$long]['content']) === 1) {
242 1
                $this->phpcsFile->addError(self::MESSAGE_LONG_NOT_CAPITAL, $long, self::CODE_LONG_NOT_CAPITAL);
243
            }
244
        }
245 6
    }
246
247
    /**
248
     * Checks for a blank line before the doc tags
249
     *
250
     * @param $stackPtr
251
     *
252
     * @return void
253
     */
254 6
    private function checkCommentTags($stackPtr)
255
    {
256 6
        if (!empty($this->tokens[$this->commentStart]['comment_tags'])) {
257 6
            $firstTag = $this->tokens[$this->commentStart]['comment_tags'][0];
258 6
            $prev = $this->phpcsFile->findPrevious($this->empty, $firstTag - 1, $stackPtr, true);
259
260 6
            if ($this->tokens[$firstTag]['line'] !== ($this->tokens[$prev]['line'] + 2)
261 6
                && $this->tokens[$this->short]['content'] !== '@inheritdoc'
262 6
                && $this->tokens[$this->short]['content'] !== '@var'
263
            ) {
264
                $this
265 1
                    ->phpcsFile
266 1
                    ->addError(self::MESSAGE_SPACING_BEFORE_TAGS, $firstTag, self::CODE_SPACING_BEFORE_TAGS);
267
            }
268
        }
269 6
    }
270
271
    /**
272
     * Checks long and short description
273
     *
274
     * @param $stackPtr
275
     * @param $commentEnd
276
     *
277
     * @return void
278
     */
279 6
    private function checkSummaryAndLongDescription($stackPtr, $commentEnd)
280
    {
281
        // No extra newline before short description.
282 6
        if ($this->tokens[$this->short]['line'] !== ($this->tokens[$stackPtr]['line'] + 1)
283 6
            && $this->tokens[$this->short]['content'] !== '@var') {
284 1
            $this->phpcsFile->addError(
285 1
                self::MESSAGE_CODE_SPACING_BEFORE,
286 1
                $this->short,
287 1
                self::CODE_SPACING_BEFORE_SHORT
288
            );
289
        }
290
291 6
        $shortContent = $this->tokens[$this->short]['content'];
292
        /** @var int $shortEnd */
293 6
        $shortEnd = $this->short;
294
295 6 View Code Duplication
        if (preg_match('/^\p{Ll}/u', $shortContent) === 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
296 1
            $this->phpcsFile->addError(self::MESSAGE_SHORT_NOT_CAPITAL, $this->short, self::CODE_SHORT_NOT_CAPITAL);
297
        }
298
299 6
        $long = $this->phpcsFile->findNext($this->empty, $shortEnd + 1, $commentEnd - 1, true);
300
301 6
        $this->checkCapitalLetter($shortContent, $long);
302
303 6
        $long = $this->phpcsFile->findNext($this->empty, $shortEnd + 1, $commentEnd - 1, true);
304
305 6
        $this->checkLongDescription($shortEnd, $long);
306
307 6
        $this->checkCommentTags($stackPtr);
308 6
    }
309
310
    /**
311
     * Checks the method doc block
312
     *
313
     * @param $stackPtr
314
     * @param $commentEnd
315
     *
316
     * @return void
317
     */
318 7
    private function checkMethodPhpDoc($stackPtr, $commentEnd)
319
    {
320
        // The last line of the comment should just be the */ code.
321 7
        $prev = $this->phpcsFile->findPrevious($this->empty, $commentEnd - 1, $stackPtr, true);
322
323 7
        $this->checkBlankLines($commentEnd, $prev);
324
325
        // Check for a comment description.
326 7
        if ($this->tokens[$this->short]['code'] !== T_DOC_COMMENT_STRING
327 7
            && $this->tokens[$this->short]['content'] !== '@inheritdoc'
328 7
            && $this->tokens[$this->short]['content'] !== '@var'
329
        ) {
330 1
            $error = self::MESSAGE_MISSING_SHORT;
331 1
            $this->phpcsFile->addError($error, $stackPtr, self::CODE_MISSING_SHORT);
332 1
            return;
333
        }
334 6
        $this->checkSummaryAndLongDescription($stackPtr, $commentEnd);
335 6
    }
336
337
    /**
338
     * Processes this test, when one of its tokens is encountered.
339
     *
340
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
341
     * @param int $stackPtr The position of the current token in the stack passed in $tokens.
342
     *
343
     * @return void
344
     */
345 8
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
346
    {
347 8
        $this->tokens = $phpcsFile->getTokens();
348 8
        $this->phpcsFile = $phpcsFile;
349
350 8
        $this->commentStart = $stackPtr;
351 8
        $commentEnd = $this->tokens[$stackPtr]['comment_closer'];
352
353 8
        $this->short = $this->phpcsFile->findNext($this->empty, $stackPtr + 1, $commentEnd, true);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->phpcsFile->findNe...+ 1, $commentEnd, true) can also be of type false. However, the property $short is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
354
355 8
        if ($this->short === false) {
356
            // No content at all.
357 1
            $phpcsFile->addError(self::MESSAGE_DOC_EMPTY, $stackPtr, self::CODE_EMPTY);
358 1
            return;
359
        }
360 7
        $this->checkMethodPhpDoc(
361
            $stackPtr,
362
            $commentEnd
363
        );
364 7
    }
365
}
366