Completed
Pull Request — master (#13)
by
unknown
03:25
created

MethodDocSniff::checkBlankLines()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 3
Ratio 42.86 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 3
loc 7
ccs 3
cts 4
cp 0.75
rs 9.4285
cc 2
eloc 3
nc 2
nop 2
crap 2.0625
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
    #region Properties
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
    #endregion
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
     * Processes this test, when one of its tokens is encountered.
188
     *
189
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
190
     * @param int $stackPtr The position of the current token in the stack passed in $tokens.
191
     *
192
     * @return void
193
     */
194 8
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
195
    {
196 8
        $this->tokens = $phpcsFile->getTokens();
197 8
        $this->phpcsFile = $phpcsFile;
198
199 8
        $this->commentStart = $stackPtr;
200 8
        $commentEnd = $this->tokens[$stackPtr]['comment_closer'];
201
202 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...
203
204 8
        if ($this->short === false) {
205
            // No content at all.
206 1
            $phpcsFile->addError(self::MESSAGE_DOC_EMPTY, $stackPtr, self::CODE_EMPTY);
207 1
            return;
208
        }
209 7
        $this->checkMethodPhpDoc(
210
            $stackPtr,
211
            $commentEnd
212
        );
213 7
    }
214
215
    /**
216
     * Checks if there is a blank line at the end of the doc
217
     *
218
     * @param $commentEnd
219
     * @param $prev
220
     *
221
     * @return void
222
     */
223 7
    private function checkBlankLines($commentEnd, $prev)
224
    {
225
        // Check for additional blank lines at the end of the comment.
226 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...
227
            $this->phpcsFile->addError(self::MESSAGE_SPACING_AFTER, $commentEnd, self::CODE_SPACING_AFTER);
228
        }
229 7
    }
230
231
    /**
232
     * Checks if short or long description starts with a capital letter
233
     *
234
     * @param $shortContent
235
     * @param $long
236
     *
237
     * @return void
238
     */
239 6
    private function checkCapitalLetter($shortContent, $long)
240
    {
241 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...
242 1
            $this->phpcsFile->addError(self::MESSAGE_SHORT_NOT_CAPITAL, $this->short, self::CODE_SHORT_NOT_CAPITAL);
243
        }
244
245 6
        if ($long !== false
246 6
            && $this->tokens[$long]['code'] === T_DOC_COMMENT_STRING && preg_match('
247 6
            /^\p{Ll}/u', $this->tokens[$long]['content']) === 1
248
        ) {
249 1
            $this->phpcsFile->addError(self::MESSAGE_LONG_NOT_CAPITAL, $long, self::CODE_LONG_NOT_CAPITAL);
250
        }
251 6
    }
252
253
    /**
254
     * Checks capital letter and spacing of long description
255
     *
256
     * @param $shortEnd
257
     * @param $long
258
     *
259
     * @return void
260
     */
261 6
    private function checkLongDescription(
262
        $shortEnd,
263
        $long
264
    ) {
265 6
        if ($long !== false && $this->tokens[$long]['code'] === T_DOC_COMMENT_STRING) {
266 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...
267 1
                $this->phpcsFile->addError(self::MESSAGE_SPACING_BETWEEN, $long, self::CODE_SPACING_BETWEEN);
268
            }
269
270 2
            if (preg_match('/^\p{Ll}/u', $this->tokens[$long]['content']) === 1) {
271 1
                $this->phpcsFile->addError(self::MESSAGE_LONG_NOT_CAPITAL, $long, self::CODE_LONG_NOT_CAPITAL);
272
            }
273
        }
274 6
    }
275
276
    /**
277
     * Checks for a blank line before the doc tags
278
     *
279
     * @param $stackPtr
280
     *
281
     * @return void
282
     */
283 6
    private function checkCommentTags($stackPtr)
284
    {
285 6
        if (!empty($this->tokens[$this->commentStart]['comment_tags'])) {
286 6
            $firstTag = $this->tokens[$this->commentStart]['comment_tags'][0];
287 6
            $prev = $this->phpcsFile->findPrevious($this->empty, $firstTag - 1, $stackPtr, true);
288
289 6
            if ($this->tokens[$firstTag]['line'] !== ($this->tokens[$prev]['line'] + 2)
290 6
                && $this->tokens[$this->short]['content'] !== '@inheritdoc'
291 6
                && $this->tokens[$this->short]['content'] !== '@var'
292
            ) {
293
                $this
294 1
                    ->phpcsFile
295 1
                    ->addError(self::MESSAGE_SPACING_BEFORE_TAGS, $firstTag, self::CODE_SPACING_BEFORE_TAGS);
296
            }
297
        }
298 6
    }
299
300
    /**
301
     * Checks long and short description
302
     *
303
     * @param $stackPtr
304
     * @param $commentEnd
305
     *
306
     * @return void
307
     */
308 6
    private function checkSummaryAndLongDescription($stackPtr, $commentEnd)
309
    {
310
        // No extra newline before short description.
311 6
        if ($this->tokens[$this->short]['line'] !== ($this->tokens[$stackPtr]['line'] + 1)
312 6
            && $this->tokens[$this->short]['content'] !== '@var') {
313 1
            $this->phpcsFile->addError(
314 1
                self::MESSAGE_CODE_SPACING_BEFORE,
315 1
                $this->short,
316 1
                self::CODE_SPACING_BEFORE_SHORT
317
            );
318
        }
319
320 6
        $shortContent = $this->tokens[$this->short]['content'];
321
        /** @var int $shortEnd */
322 6
        $shortEnd = $this->short;
323
324 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...
325 1
            $this->phpcsFile->addError(self::MESSAGE_SHORT_NOT_CAPITAL, $this->short, self::CODE_SHORT_NOT_CAPITAL);
326
        }
327
328 6
        $long = $this->phpcsFile->findNext($this->empty, $shortEnd + 1, $commentEnd - 1, true);
329
330 6
        $this->checkCapitalLetter($shortContent, $long);
331
332 6
        $long = $this->phpcsFile->findNext($this->empty, $shortEnd + 1, $commentEnd - 1, true);
333
334 6
        $this->checkLongDescription($shortEnd, $long);
335
336 6
        $this->checkCommentTags($stackPtr);
337 6
    }
338
339
    /**
340
     * Checks the method doc block
341
     *
342
     * @param $stackPtr
343
     * @param $commentEnd
344
     *
345
     * @return void
346
     */
347 7
    private function checkMethodPhpDoc($stackPtr, $commentEnd)
348
    {
349
        // The last line of the comment should just be the */ code.
350 7
        $prev = $this->phpcsFile->findPrevious($this->empty, $commentEnd - 1, $stackPtr, true);
351
352 7
        $this->checkBlankLines($commentEnd, $prev);
353
354
        // Check for a comment description.
355 7
        if ($this->tokens[$this->short]['code'] !== T_DOC_COMMENT_STRING
356 7
            && $this->tokens[$this->short]['content'] !== '@inheritdoc'
357 7
            && $this->tokens[$this->short]['content'] !== '@var'
358
        ) {
359 1
            $error = self::MESSAGE_MISSING_SHORT;
360 1
            $this->phpcsFile->addError($error, $stackPtr, self::CODE_MISSING_SHORT);
361 1
            return;
362
        }
363 6
        $this->checkSummaryAndLongDescription($stackPtr, $commentEnd);
364 6
    }
365
}
366