Passed
Pull Request — master (#13)
by Tim
03:40 queued 01:49
created

MethodDocSniff   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 350
Duplicated Lines 3.43 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 98.57%

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 1
dl 12
loc 350
ccs 69
cts 70
cp 0.9857
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 4 1
A process() 0 20 2
A checkBlankLines() 3 7 2
B checkCapitalLetter() 3 13 5
B checkLongDescription() 3 14 5
B checkCommentTags() 0 16 5
B checkSummaryAndLongDescription() 3 30 4
A checkMethodPhpDoc() 0 18 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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
    #region Properties
135
    /**
136
     * A list of tokenizers this sniff supports.
137
     *
138
     * @var array
139
     */
140
    public $supportedTokenizers = ['PHP', 'JS'];
141
142
    /**
143
     * The token array is the base of this evaluation
144
     *
145
     * @var array
146
     */
147
    public $tokens;
148
149
    /**
150
     * Definitions for an empty doc block
151
     *
152
     * @var array
153
     */
154
    public $empty = [T_DOC_COMMENT_WHITESPACE, T_DOC_COMMENT_STAR];
155
156
    /**
157
     * The phpcs file
158
     *
159
     * @var PHP_CodeSniffer_File
160
     */
161
    public $phpcsFile;
162
163
    /**
164
     * Pointer to the start of the comment
165
     *
166
     * @var int
167
     */
168
    public $commentStart;
169
170
    /**
171
     * Pointer to the start of the short description
172
     *
173
     * @var int
174
     */
175
    public $short;
176
    #endregion
177
178
    /**
179
     * Returns an array of tokens this test wants to listen for.
180
     *
181
     * @return array
182
     */
183 8
    public function register(): array
184
    {
185 8
        return [T_DOC_COMMENT_OPEN_TAG];
186
    }
187
188
    /**
189
     * Processes this test, when one of its tokens is encountered.
190
     *
191
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
192
     * @param int $stackPtr The position of the current token in the stack passed in $tokens.
193
     *
194
     * @return void
195
     */
196 8
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
197
    {
198 8
        $this->tokens = $phpcsFile->getTokens();
199 8
        $this->phpcsFile = $phpcsFile;
200
201 8
        $this->commentStart = $stackPtr;
202 8
        $commentEnd = $this->tokens[$stackPtr]['comment_closer'];
203
204 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...
205
206 8
        if ($this->short === false) {
207
            // No content at all.
208 1
            $phpcsFile->addError(self::MESSAGE_DOC_EMPTY, $stackPtr, self::CODE_EMPTY);
209 1
            return;
210
        }
211 7
        $this->checkMethodPhpDoc(
212
            $stackPtr,
213
            $commentEnd
214
        );
215 7
    }
216
217
    /**
218
     * Checks if there is a blank line at the end of the doc
219
     *
220
     * @param $commentEnd
221
     * @param $prev
222
     *
223
     * @return void
224
     */
225 7
    private function checkBlankLines($commentEnd, $prev)
226
    {
227
        // Check for additional blank lines at the end of the comment.
228 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...
229
            $this->phpcsFile->addError(self::MESSAGE_SPACING_AFTER, $commentEnd, self::CODE_SPACING_AFTER);
230
        }
231 7
    }
232
233
    /**
234
     * Checks if short or long description starts with a capital letter
235
     *
236
     * @param $shortContent
237
     * @param $long
238
     *
239
     * @return void
240
     */
241 6
    private function checkCapitalLetter($shortContent, $long)
242
    {
243 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...
244 1
            $this->phpcsFile->addError(self::MESSAGE_SHORT_NOT_CAPITAL, $this->short, self::CODE_SHORT_NOT_CAPITAL);
245
        }
246
247 6
        if ($long !== false
248 6
            && $this->tokens[$long]['code'] === T_DOC_COMMENT_STRING
249 6
            && preg_match('/^\p{Ll}/u', $this->tokens[$long]['content']) === 1
250
        ) {
251 1
            $this->phpcsFile->addError(self::MESSAGE_LONG_NOT_CAPITAL, $long, self::CODE_LONG_NOT_CAPITAL);
252
        }
253 6
    }
254
255
    /**
256
     * Checks capital letter and spacing of long description
257
     *
258
     * @param $shortEnd
259
     * @param $long
260
     *
261
     * @return void
262
     */
263 6
    private function checkLongDescription(
264
        $shortEnd,
265
        $long
266
    ) {
267 6
        if ($long !== false && $this->tokens[$long]['code'] === T_DOC_COMMENT_STRING) {
268 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...
269 1
                $this->phpcsFile->addError(self::MESSAGE_SPACING_BETWEEN, $long, self::CODE_SPACING_BETWEEN);
270
            }
271
272 2
            if (preg_match('/^\p{Ll}/u', $this->tokens[$long]['content']) === 1) {
273 1
                $this->phpcsFile->addError(self::MESSAGE_LONG_NOT_CAPITAL, $long, self::CODE_LONG_NOT_CAPITAL);
274
            }
275
        }
276 6
    }
277
278
    /**
279
     * Checks for a blank line before the doc tags
280
     *
281
     * @param $stackPtr
282
     *
283
     * @return void
284
     */
285 6
    private function checkCommentTags($stackPtr)
286
    {
287 6
        if (!empty($this->tokens[$this->commentStart]['comment_tags'])) {
288 6
            $firstTag = $this->tokens[$this->commentStart]['comment_tags'][0];
289 6
            $prev = $this->phpcsFile->findPrevious($this->empty, $firstTag - 1, $stackPtr, true);
290
291 6
            if ($this->tokens[$this->short]['content'] !== '@inheritdoc'
292 6
                && $this->tokens[$this->short]['content'] !== '@var'
293 6
                && $this->tokens[$firstTag]['line'] !== ($this->tokens[$prev]['line'] + 2)
294
            ) {
295
                $this
296 1
                    ->phpcsFile
297 1
                    ->addError(self::MESSAGE_SPACING_BEFORE_TAGS, $firstTag, self::CODE_SPACING_BEFORE_TAGS);
298
            }
299
        }
300 6
    }
301
302
    /**
303
     * Checks long and short description
304
     *
305
     * @param $stackPtr
306
     * @param $commentEnd
307
     *
308
     * @return void
309
     */
310 6
    private function checkSummaryAndLongDescription($stackPtr, $commentEnd)
311
    {
312
        // No extra newline before short description.
313 6
        if ($this->tokens[$this->short]['content'] !== '@var'
314 6
            && $this->tokens[$this->short]['line'] !== ($this->tokens[$stackPtr]['line'] + 1)) {
315 1
            $this->phpcsFile->addError(
316 1
                self::MESSAGE_CODE_SPACING_BEFORE,
317 1
                $this->short,
318 1
                self::CODE_SPACING_BEFORE_SHORT
319
            );
320
        }
321
322 6
        $shortContent = $this->tokens[$this->short]['content'];
323
        /** @var int $shortEnd */
324 6
        $shortEnd = $this->short;
325
326 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...
327 1
            $this->phpcsFile->addError(self::MESSAGE_SHORT_NOT_CAPITAL, $this->short, self::CODE_SHORT_NOT_CAPITAL);
328
        }
329
330 6
        $long = $this->phpcsFile->findNext($this->empty, $shortEnd + 1, $commentEnd - 1, true);
331
332 6
        $this->checkCapitalLetter($shortContent, $long);
333
334 6
        $long = $this->phpcsFile->findNext($this->empty, $shortEnd + 1, $commentEnd - 1, true);
335
336 6
        $this->checkLongDescription($shortEnd, $long);
337
338 6
        $this->checkCommentTags($stackPtr);
339 6
    }
340
341
    /**
342
     * Checks the method doc block
343
     *
344
     * @param $stackPtr
345
     * @param $commentEnd
346
     *
347
     * @return void
348
     */
349 7
    private function checkMethodPhpDoc($stackPtr, $commentEnd)
350
    {
351
        // The last line of the comment should just be the */ code.
352 7
        $prev = $this->phpcsFile->findPrevious($this->empty, $commentEnd - 1, $stackPtr, true);
353
354 7
        $this->checkBlankLines($commentEnd, $prev);
355
356
        // Check for a comment description.
357 7
        if ($this->tokens[$this->short]['code'] !== T_DOC_COMMENT_STRING
358 7
            && $this->tokens[$this->short]['content'] !== '@inheritdoc'
359 7
            && $this->tokens[$this->short]['content'] !== '@var'
360
        ) {
361 1
            $error = self::MESSAGE_MISSING_SHORT;
362 1
            $this->phpcsFile->addError($error, $stackPtr, self::CODE_MISSING_SHORT);
363 1
            return;
364
        }
365 6
        $this->checkSummaryAndLongDescription($stackPtr, $commentEnd);
366 6
    }
367
}
368