1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Ensures doc blocks follow basic formatting. |
4
|
|
|
* |
5
|
|
|
* PHP version 5 |
6
|
|
|
* |
7
|
|
|
* @category PHP |
8
|
|
|
* @package PHP_CodeSniffer |
9
|
|
|
* @author Greg Sherwood <[email protected]> |
10
|
|
|
* @copyright 2006-2012 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
|
|
|
|
15
|
|
|
namespace PSR2R\Sniffs\Commenting; |
16
|
|
|
|
17
|
|
|
use PHP_CodeSniffer_File; |
18
|
|
|
use PSR2R\Tools\AbstractSniff; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Ensures doc blocks follow basic formatting. |
22
|
|
|
* |
23
|
|
|
* @author Greg Sherwood <[email protected]> |
24
|
|
|
* @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) |
25
|
|
|
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence |
26
|
|
|
* @version Release: @package_version@ |
27
|
|
|
* @link http://pear.php.net/package/PHP_CodeSniffer |
28
|
|
|
*/ |
29
|
|
|
class DocCommentSniff extends AbstractSniff { |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* A list of tokenizers this sniff supports. |
33
|
|
|
* |
34
|
|
|
* @var array |
35
|
|
|
*/ |
36
|
|
|
public $supportedTokenizers = [ |
37
|
|
|
'PHP', |
38
|
|
|
'JS', |
39
|
|
|
]; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @inheritDoc |
43
|
|
|
*/ |
44
|
|
|
public function register() { |
45
|
|
|
return [T_DOC_COMMENT_OPEN_TAG]; |
46
|
|
|
|
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @inheritDoc |
51
|
|
|
*/ |
52
|
|
|
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) { |
53
|
|
|
$tokens = $phpcsFile->getTokens(); |
54
|
|
|
$commentStart = $stackPtr; |
55
|
|
|
$commentEnd = $tokens[$stackPtr]['comment_closer']; |
56
|
|
|
|
57
|
|
|
$indentationLevel = $this->getIndentationLevel($phpcsFile, $stackPtr); |
58
|
|
|
|
59
|
|
|
// Skip for inline comments |
60
|
|
|
if ($indentationLevel > 1) { |
61
|
|
|
return; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$empty = [ |
65
|
|
|
T_DOC_COMMENT_WHITESPACE, |
66
|
|
|
T_DOC_COMMENT_STAR, |
67
|
|
|
]; |
68
|
|
|
|
69
|
|
|
$short = $phpcsFile->findNext($empty, ($stackPtr + 1), $commentEnd, true); |
70
|
|
|
if ($short === false) { |
71
|
|
|
return; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
// The first line of the comment should just be the /** code. |
75
|
|
|
if ($tokens[$short]['line'] === $tokens[$stackPtr]['line']) { |
76
|
|
|
$error = 'The open comment tag must be the only content on the line'; |
77
|
|
|
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'ContentAfterOpen'); |
78
|
|
|
if ($fix === true) { |
79
|
|
|
$indentation = $tokens[$commentStart]['column'] - 1; |
80
|
|
|
$indentation = str_repeat("\t", $indentation); |
81
|
|
|
|
82
|
|
|
$phpcsFile->fixer->beginChangeset(); |
83
|
|
|
$phpcsFile->fixer->addContentBefore($short, $indentation . ' * '); |
84
|
|
|
$phpcsFile->fixer->addNewlineBefore($short); |
85
|
|
|
$phpcsFile->fixer->endChangeset(); |
86
|
|
|
} |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
// The last line of the comment should just be the */ code. |
90
|
|
|
$prev = $phpcsFile->findPrevious($empty, ($commentEnd - 1), $stackPtr, true); |
91
|
|
|
if ($tokens[$prev]['line'] === $tokens[$commentEnd]['line']) { |
92
|
|
|
$error = 'The close comment tag must be the only content on the line'; |
93
|
|
|
$fix = $phpcsFile->addFixableError($error, $commentEnd, 'ContentBeforeClose'); |
94
|
|
|
if ($fix === true) { |
95
|
|
|
$indentation = $tokens[$commentStart]['column'] - 1; |
96
|
|
|
$indentation = str_repeat("\t", $indentation); |
97
|
|
|
|
98
|
|
|
$phpcsFile->fixer->beginChangeset(); |
99
|
|
|
|
100
|
|
|
$phpcsFile->fixer->replaceToken($commentEnd, $indentation . ' ' . $tokens[$commentEnd]['content']); |
101
|
|
|
$phpcsFile->fixer->addNewlineBefore($commentEnd); |
102
|
|
|
|
103
|
|
|
$phpcsFile->fixer->endChangeset(); |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
// Check for additional blank lines at the end of the comment. |
108
|
|
View Code Duplication |
if ($tokens[$prev]['line'] < ($tokens[$commentEnd]['line'] - 1)) { |
|
|
|
|
109
|
|
|
$error = 'Additional blank lines found at end of doc comment'; |
110
|
|
|
$fix = $phpcsFile->addFixableError($error, $commentEnd, 'SpacingAfter'); |
111
|
|
|
if ($fix === true) { |
112
|
|
|
$phpcsFile->fixer->beginChangeset(); |
113
|
|
|
for ($i = ($prev + 1); $i < $commentEnd; $i++) { |
114
|
|
|
if ($tokens[($i + 1)]['line'] === $tokens[$commentEnd]['line']) { |
115
|
|
|
break; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$phpcsFile->fixer->replaceToken($i, ''); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$phpcsFile->fixer->endChangeset(); |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
// No extra newline before short description. |
126
|
|
View Code Duplication |
if ($tokens[$short]['line'] !== ($tokens[$stackPtr]['line'] + 1)) { |
|
|
|
|
127
|
|
|
$error = 'Doc comment short description must be on the first line'; |
128
|
|
|
$fix = $phpcsFile->addFixableError($error, $short, 'SpacingBeforeShort'); |
129
|
|
|
if ($fix === true) { |
130
|
|
|
$phpcsFile->fixer->beginChangeset(); |
131
|
|
|
for ($i = $stackPtr; $i < $short; $i++) { |
132
|
|
|
if ($tokens[$i]['line'] === $tokens[$stackPtr]['line']) { |
133
|
|
|
continue; |
134
|
|
|
} |
135
|
|
|
if ($tokens[$i]['line'] === $tokens[$short]['line']) { |
136
|
|
|
break; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
$phpcsFile->fixer->replaceToken($i, ''); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
$phpcsFile->fixer->endChangeset(); |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
// Account for the fact that a short description might cover |
147
|
|
|
// multiple lines. |
148
|
|
|
$shortContent = $tokens[$short]['content']; |
149
|
|
|
$shortEnd = $short; |
150
|
|
View Code Duplication |
for ($i = ($short + 1); $i < $commentEnd; $i++) { |
|
|
|
|
151
|
|
|
if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { |
152
|
|
|
if ($tokens[$i]['line'] === ($tokens[$shortEnd]['line'] + 1)) { |
153
|
|
|
$shortContent .= $tokens[$i]['content']; |
154
|
|
|
$shortEnd = $i; |
155
|
|
|
} else { |
156
|
|
|
break; |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
if (empty($tokens[$commentStart]['comment_tags']) === true) { |
162
|
|
|
// No tags in the comment. |
163
|
|
|
return; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
$firstTag = $tokens[$commentStart]['comment_tags'][0]; |
167
|
|
|
if ($tokens[$firstTag]['line'] === $tokens[$commentStart]['line'] + 1) { |
168
|
|
|
return; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
$prev = $phpcsFile->findPrevious($empty, ($firstTag - 1), $stackPtr, true); |
172
|
|
View Code Duplication |
if ($tokens[$firstTag]['line'] !== ($tokens[$prev]['line'] + 2)) { |
|
|
|
|
173
|
|
|
$error = 'There must be exactly one blank line before the tags in a doc comment'; |
174
|
|
|
$fix = $phpcsFile->addFixableError($error, $firstTag, 'SpacingBeforeTags'); |
175
|
|
|
if ($fix === true) { |
176
|
|
|
$phpcsFile->fixer->beginChangeset(); |
177
|
|
|
for ($i = ($prev + 1); $i < $firstTag; $i++) { |
178
|
|
|
if ($tokens[$i]['line'] === $tokens[$firstTag]['line']) { |
179
|
|
|
break; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$phpcsFile->fixer->replaceToken($i, ''); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
$indent = str_repeat("\t", $tokens[$stackPtr]['column'] - 1) . ' '; |
186
|
|
|
$phpcsFile->fixer->addContent($prev, $phpcsFile->eolChar . $indent . '*' . $phpcsFile->eolChar); |
187
|
|
|
$phpcsFile->fixer->endChangeset(); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
} |
193
|
|
|
|
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.