Mercurial > hg > isophonics-drupal-site
comparison vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/InlineCommentSniff.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 129ea1e6d783 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 /** | |
3 * PHP_CodeSniffer_Sniffs_Drupal_Commenting_InlineCommentSniff. | |
4 * | |
5 * @category PHP | |
6 * @package PHP_CodeSniffer | |
7 * @link http://pear.php.net/package/PHP_CodeSniffer | |
8 */ | |
9 | |
10 /** | |
11 * PHP_CodeSniffer_Sniffs_Drupal_Commenting_InlineCommentSniff. | |
12 * | |
13 * Checks that no perl-style comments are used. Checks that inline comments ("//") | |
14 * have a space after //, start capitalized and end with proper punctuation. | |
15 * Largely copied from Squiz_Sniffs_Commenting_InlineCommentSniff. | |
16 * | |
17 * @category PHP | |
18 * @package PHP_CodeSniffer | |
19 * @link http://pear.php.net/package/PHP_CodeSniffer | |
20 */ | |
21 class Drupal_Sniffs_Commenting_InlineCommentSniff implements PHP_CodeSniffer_Sniff | |
22 { | |
23 | |
24 /** | |
25 * A list of tokenizers this sniff supports. | |
26 * | |
27 * @var array | |
28 */ | |
29 public $supportedTokenizers = array( | |
30 'PHP', | |
31 'JS', | |
32 ); | |
33 | |
34 | |
35 /** | |
36 * Returns an array of tokens this test wants to listen for. | |
37 * | |
38 * @return array | |
39 */ | |
40 public function register() | |
41 { | |
42 return array( | |
43 T_COMMENT, | |
44 T_DOC_COMMENT_OPEN_TAG, | |
45 ); | |
46 | |
47 }//end register() | |
48 | |
49 | |
50 /** | |
51 * Processes this test, when one of its tokens is encountered. | |
52 * | |
53 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. | |
54 * @param int $stackPtr The position of the current token in the | |
55 * stack passed in $tokens. | |
56 * | |
57 * @return void | |
58 */ | |
59 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) | |
60 { | |
61 $tokens = $phpcsFile->getTokens(); | |
62 | |
63 // If this is a function/class/interface doc block comment, skip it. | |
64 // We are only interested in inline doc block comments, which are | |
65 // not allowed. | |
66 if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) { | |
67 $nextToken = $phpcsFile->findNext( | |
68 PHP_CodeSniffer_Tokens::$emptyTokens, | |
69 ($stackPtr + 1), | |
70 null, | |
71 true | |
72 ); | |
73 | |
74 $ignore = array( | |
75 T_CLASS, | |
76 T_INTERFACE, | |
77 T_TRAIT, | |
78 T_FUNCTION, | |
79 T_CLOSURE, | |
80 T_PUBLIC, | |
81 T_PRIVATE, | |
82 T_PROTECTED, | |
83 T_FINAL, | |
84 T_STATIC, | |
85 T_ABSTRACT, | |
86 T_CONST, | |
87 T_PROPERTY, | |
88 T_VAR, | |
89 ); | |
90 | |
91 // Also ignore all doc blocks defined in the outer scope (no scope | |
92 // conditions are set). | |
93 if (in_array($tokens[$nextToken]['code'], $ignore) === true | |
94 || empty($tokens[$stackPtr]['conditions']) === true | |
95 ) { | |
96 return; | |
97 } | |
98 | |
99 if ($phpcsFile->tokenizerType === 'JS') { | |
100 // We allow block comments if a function or object | |
101 // is being assigned to a variable. | |
102 $ignore = PHP_CodeSniffer_Tokens::$emptyTokens; | |
103 $ignore[] = T_EQUAL; | |
104 $ignore[] = T_STRING; | |
105 $ignore[] = T_OBJECT_OPERATOR; | |
106 $nextToken = $phpcsFile->findNext($ignore, ($nextToken + 1), null, true); | |
107 if ($tokens[$nextToken]['code'] === T_FUNCTION | |
108 || $tokens[$nextToken]['code'] === T_CLOSURE | |
109 || $tokens[$nextToken]['code'] === T_OBJECT | |
110 || $tokens[$nextToken]['code'] === T_PROTOTYPE | |
111 ) { | |
112 return; | |
113 } | |
114 } | |
115 | |
116 $prevToken = $phpcsFile->findPrevious( | |
117 PHP_CodeSniffer_Tokens::$emptyTokens, | |
118 ($stackPtr - 1), | |
119 null, | |
120 true | |
121 ); | |
122 | |
123 if ($tokens[$prevToken]['code'] === T_OPEN_TAG) { | |
124 return; | |
125 } | |
126 | |
127 // Inline doc blocks are allowed in JSDoc. | |
128 if ($tokens[$stackPtr]['content'] === '/**' && $phpcsFile->tokenizerType !== 'JS') { | |
129 // The only exception to inline doc blocks is the /** @var */ | |
130 // declaration. | |
131 $content = $phpcsFile->getTokensAsString($stackPtr, ($tokens[$stackPtr]['comment_closer'] - $stackPtr + 1)); | |
132 if (preg_match('#^/\*\* @var [a-zA-Z0-9_\\\\\[\]|]+ \$[a-zA-Z0-9_]+ \*/$#', $content) !== 1) { | |
133 $error = 'Inline doc block comments are not allowed; use "/* Comment */" or "// Comment" instead'; | |
134 $phpcsFile->addError($error, $stackPtr, 'DocBlock'); | |
135 } | |
136 } | |
137 }//end if | |
138 | |
139 if ($tokens[$stackPtr]['content']{0} === '#') { | |
140 $error = 'Perl-style comments are not allowed; use "// Comment" instead'; | |
141 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'WrongStyle'); | |
142 if ($fix === true) { | |
143 $comment = ltrim($tokens[$stackPtr]['content'], "# \t"); | |
144 $phpcsFile->fixer->replaceToken($stackPtr, "// $comment"); | |
145 } | |
146 } | |
147 | |
148 // We don't want end of block comments. If the last comment is a closing | |
149 // curly brace. | |
150 $previousContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); | |
151 if ($tokens[$previousContent]['line'] === $tokens[$stackPtr]['line']) { | |
152 if ($tokens[$previousContent]['code'] === T_CLOSE_CURLY_BRACKET) { | |
153 return; | |
154 } | |
155 | |
156 // Special case for JS files. | |
157 if ($tokens[$previousContent]['code'] === T_COMMA | |
158 || $tokens[$previousContent]['code'] === T_SEMICOLON | |
159 ) { | |
160 $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($previousContent - 1), null, true); | |
161 if ($tokens[$lastContent]['code'] === T_CLOSE_CURLY_BRACKET) { | |
162 return; | |
163 } | |
164 } | |
165 } | |
166 | |
167 $comment = rtrim($tokens[$stackPtr]['content']); | |
168 | |
169 // Only want inline comments. | |
170 if (substr($comment, 0, 2) !== '//') { | |
171 return; | |
172 } | |
173 | |
174 // Ignore code example lines. | |
175 if ($this->isInCodeExample($phpcsFile, $stackPtr) === true) { | |
176 return; | |
177 } | |
178 | |
179 // Verify the indentation of this comment line. | |
180 $this->processIndentation($phpcsFile, $stackPtr); | |
181 | |
182 // If the current line starts with a tag such as "@see" then the trailing dot | |
183 // rules and upper case start rules don't apply. | |
184 if (strpos(trim(substr($tokens[$stackPtr]['content'], 2)), '@') === 0) { | |
185 return; | |
186 } | |
187 | |
188 // The below section determines if a comment block is correctly capitalised, | |
189 // and ends in a full-stop. It will find the last comment in a block, and | |
190 // work its way up. | |
191 $nextComment = $phpcsFile->findNext(array(T_COMMENT), ($stackPtr + 1), null, false); | |
192 if (($nextComment !== false) | |
193 && (($tokens[$nextComment]['line']) === ($tokens[$stackPtr]['line'] + 1)) | |
194 // A tag such as @todo means a separate comment block. | |
195 && strpos(trim(substr($tokens[$nextComment]['content'], 2)), '@') !== 0 | |
196 ) { | |
197 return; | |
198 } | |
199 | |
200 $topComment = $stackPtr; | |
201 $lastComment = $stackPtr; | |
202 while (($topComment = $phpcsFile->findPrevious(array(T_COMMENT), ($lastComment - 1), null, false)) !== false) { | |
203 if ($tokens[$topComment]['line'] !== ($tokens[$lastComment]['line'] - 1)) { | |
204 break; | |
205 } | |
206 | |
207 $lastComment = $topComment; | |
208 } | |
209 | |
210 $topComment = $lastComment; | |
211 $commentText = ''; | |
212 | |
213 for ($i = $topComment; $i <= $stackPtr; $i++) { | |
214 if ($tokens[$i]['code'] === T_COMMENT) { | |
215 $commentText .= trim(substr($tokens[$i]['content'], 2)); | |
216 } | |
217 } | |
218 | |
219 if ($commentText === '') { | |
220 $error = 'Blank comments are not allowed'; | |
221 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Empty'); | |
222 if ($fix === true) { | |
223 $phpcsFile->fixer->replaceToken($stackPtr, ''); | |
224 } | |
225 | |
226 return; | |
227 } | |
228 | |
229 $words = preg_split('/\s+/', $commentText); | |
230 if (preg_match('|\p{Lu}|u', $commentText[0]) === 0 && $commentText[0] !== '@') { | |
231 // Allow special lower cased words that contain non-alpha characters | |
232 // (function references, machine names with underscores etc.). | |
233 $matches = array(); | |
234 preg_match('/[a-z]+/', $words[0], $matches); | |
235 if (isset($matches[0]) === true && $matches[0] === $words[0]) { | |
236 $error = 'Inline comments must start with a capital letter'; | |
237 $fix = $phpcsFile->addFixableError($error, $topComment, 'NotCapital'); | |
238 if ($fix === true) { | |
239 $newComment = preg_replace("/$words[0]/", ucfirst($words[0]), $tokens[$topComment]['content'], 1); | |
240 $phpcsFile->fixer->replaceToken($topComment, $newComment); | |
241 } | |
242 } | |
243 } | |
244 | |
245 $commentCloser = $commentText[(strlen($commentText) - 1)]; | |
246 $acceptedClosers = array( | |
247 'full-stops' => '.', | |
248 'exclamation marks' => '!', | |
249 'colons' => ':', | |
250 'question marks' => '?', | |
251 'or closing parentheses' => ')', | |
252 ); | |
253 | |
254 // Allow @tag style comments without punctuation. | |
255 if (in_array($commentCloser, $acceptedClosers) === false && $commentText[0] !== '@') { | |
256 // Allow special last words like URLs or function references | |
257 // without punctuation. | |
258 $lastWord = $words[(count($words) - 1)]; | |
259 $matches = array(); | |
260 preg_match('/https?:\/\/.+/', $lastWord, $matches); | |
261 $isUrl = isset($matches[0]) === true; | |
262 preg_match('/[$a-zA-Z_]+\([$a-zA-Z_]*\)/', $lastWord, $matches); | |
263 $isFunction = isset($matches[0]) === true; | |
264 | |
265 // Also allow closing tags like @endlink or @endcode. | |
266 $isEndTag = $lastWord[0] === '@'; | |
267 | |
268 if ($isUrl === false && $isFunction === false && $isEndTag === false) { | |
269 $error = 'Inline comments must end in %s'; | |
270 $ender = ''; | |
271 foreach ($acceptedClosers as $closerName => $symbol) { | |
272 $ender .= ' '.$closerName.','; | |
273 } | |
274 | |
275 $ender = trim($ender, ' ,'); | |
276 $data = array($ender); | |
277 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'InvalidEndChar', $data); | |
278 if ($fix === true) { | |
279 $newContent = preg_replace('/(\s+)$/', '.$1', $tokens[$stackPtr]['content']); | |
280 $phpcsFile->fixer->replaceToken($stackPtr, $newContent); | |
281 } | |
282 } | |
283 }//end if | |
284 | |
285 // Finally, the line below the last comment cannot be empty if this inline | |
286 // comment is on a line by itself. | |
287 if ($tokens[$previousContent]['line'] < $tokens[$stackPtr]['line'] && ($stackPtr + 1) < $phpcsFile->numTokens) { | |
288 for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { | |
289 if ($tokens[$i]['line'] === ($tokens[$stackPtr]['line'] + 1)) { | |
290 if ($tokens[$i]['code'] !== T_WHITESPACE || $i === ($phpcsFile->numTokens - 1)) { | |
291 return; | |
292 } | |
293 } else if ($tokens[$i]['line'] > ($tokens[$stackPtr]['line'] + 1)) { | |
294 break; | |
295 } | |
296 } | |
297 | |
298 $warning = 'There must be no blank line following an inline comment'; | |
299 $fix = $phpcsFile->addFixableWarning($warning, $stackPtr, 'SpacingAfter'); | |
300 if ($fix === true) { | |
301 $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); | |
302 if ($next === ($phpcsFile->numTokens - 1)) { | |
303 return; | |
304 } | |
305 | |
306 $phpcsFile->fixer->beginChangeset(); | |
307 for ($i = ($stackPtr + 1); $i < $next; $i++) { | |
308 if ($tokens[$i]['line'] === $tokens[$next]['line']) { | |
309 break; | |
310 } | |
311 | |
312 $phpcsFile->fixer->replaceToken($i, ''); | |
313 } | |
314 | |
315 $phpcsFile->fixer->endChangeset(); | |
316 } | |
317 }//end if | |
318 | |
319 }//end process() | |
320 | |
321 | |
322 /** | |
323 * Determines if a comment line is part of an @code/@endcode example. | |
324 * | |
325 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. | |
326 * @param int $stackPtr The position of the current token | |
327 * in the stack passed in $tokens. | |
328 * | |
329 * @return boolean Returns true if the comment line is within a @code block, | |
330 * false otherwise. | |
331 */ | |
332 protected function isInCodeExample(PHP_CodeSniffer_File $phpcsFile, $stackPtr) | |
333 { | |
334 $tokens = $phpcsFile->getTokens(); | |
335 $prevComment = $stackPtr; | |
336 $lastComment = $stackPtr; | |
337 while (($prevComment = $phpcsFile->findPrevious(array(T_COMMENT), ($lastComment - 1), null, false)) !== false) { | |
338 if ($tokens[$prevComment]['line'] !== ($tokens[$lastComment]['line'] - 1)) { | |
339 return false; | |
340 } | |
341 | |
342 if ($tokens[$prevComment]['content'] === '// @code'.$phpcsFile->eolChar) { | |
343 return true; | |
344 } | |
345 | |
346 if ($tokens[$prevComment]['content'] === '// @endcode'.$phpcsFile->eolChar) { | |
347 return false; | |
348 } | |
349 | |
350 $lastComment = $prevComment; | |
351 } | |
352 | |
353 return false; | |
354 | |
355 }//end isInCodeExample() | |
356 | |
357 | |
358 /** | |
359 * Checks the indentation level of the comment contents. | |
360 * | |
361 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. | |
362 * @param int $stackPtr The position of the current token | |
363 * in the stack passed in $tokens. | |
364 * | |
365 * @return void | |
366 */ | |
367 protected function processIndentation(PHP_CodeSniffer_File $phpcsFile, $stackPtr) | |
368 { | |
369 $tokens = $phpcsFile->getTokens(); | |
370 $comment = rtrim($tokens[$stackPtr]['content']); | |
371 $spaceCount = 0; | |
372 $tabFound = false; | |
373 | |
374 $commentLength = strlen($comment); | |
375 for ($i = 2; $i < $commentLength; $i++) { | |
376 if ($comment[$i] === "\t") { | |
377 $tabFound = true; | |
378 break; | |
379 } | |
380 | |
381 if ($comment[$i] !== ' ') { | |
382 break; | |
383 } | |
384 | |
385 $spaceCount++; | |
386 } | |
387 | |
388 $fix = false; | |
389 if ($tabFound === true) { | |
390 $error = 'Tab found before comment text; expected "// %s" but found "%s"'; | |
391 $data = array( | |
392 ltrim(substr($comment, 2)), | |
393 $comment, | |
394 ); | |
395 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'TabBefore', $data); | |
396 } else if ($spaceCount === 0 && strlen($comment) > 2) { | |
397 $error = 'No space found before comment text; expected "// %s" but found "%s"'; | |
398 $data = array( | |
399 substr($comment, 2), | |
400 $comment, | |
401 ); | |
402 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBefore', $data); | |
403 }//end if | |
404 | |
405 if ($fix === true) { | |
406 $newComment = '// '.ltrim($tokens[$stackPtr]['content'], "/\t "); | |
407 $phpcsFile->fixer->replaceToken($stackPtr, $newComment); | |
408 } | |
409 | |
410 if ($spaceCount > 1) { | |
411 // Check if there is a comment on the previous line that justifies the | |
412 // indentation. | |
413 $prevComment = $phpcsFile->findPrevious(array(T_COMMENT), ($stackPtr - 1), null, false); | |
414 if (($prevComment !== false) && (($tokens[$prevComment]['line']) === ($tokens[$stackPtr]['line'] - 1))) { | |
415 $prevCommentText = rtrim($tokens[$prevComment]['content']); | |
416 $prevSpaceCount = 0; | |
417 for ($i = 2; $i < strlen($prevCommentText); $i++) { | |
418 if ($prevCommentText[$i] !== ' ') { | |
419 break; | |
420 } | |
421 | |
422 $prevSpaceCount++; | |
423 } | |
424 | |
425 if ($spaceCount > $prevSpaceCount && $prevSpaceCount > 0) { | |
426 // A previous comment could be a list item or @todo. | |
427 $indentationStarters = array( | |
428 '-', | |
429 '@todo', | |
430 ); | |
431 $words = preg_split('/\s+/', $prevCommentText); | |
432 $numberedList = (bool) preg_match('/^[0-9]+\./', $words[1]); | |
433 if (in_array($words[1], $indentationStarters) === true) { | |
434 if ($spaceCount !== ($prevSpaceCount + 2)) { | |
435 $error = 'Comment indentation error after %s element, expected %s spaces'; | |
436 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', array($words[1], $prevSpaceCount + 2)); | |
437 if ($fix === true) { | |
438 $newComment = '//'.str_repeat(' ', ($prevSpaceCount + 2)).ltrim($tokens[$stackPtr]['content'], "/\t "); | |
439 $phpcsFile->fixer->replaceToken($stackPtr, $newComment); | |
440 } | |
441 } | |
442 } else if ($numberedList === true) { | |
443 $expectedSpaceCount = ($prevSpaceCount + strlen($words[1]) + 1); | |
444 if ($spaceCount !== $expectedSpaceCount) { | |
445 $error = 'Comment indentation error, expected %s spaces'; | |
446 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', array($expectedSpaceCount)); | |
447 if ($fix === true) { | |
448 $newComment = '//'.str_repeat(' ', $expectedSpaceCount).ltrim($tokens[$stackPtr]['content'], "/\t "); | |
449 $phpcsFile->fixer->replaceToken($stackPtr, $newComment); | |
450 } | |
451 } | |
452 } else { | |
453 $error = 'Comment indentation error, expected only %s spaces'; | |
454 $phpcsFile->addError($error, $stackPtr, 'SpacingBefore', array($prevSpaceCount)); | |
455 }//end if | |
456 }//end if | |
457 } else { | |
458 $error = '%s spaces found before inline comment; expected "// %s" but found "%s"'; | |
459 $data = array( | |
460 $spaceCount, | |
461 substr($comment, (2 + $spaceCount)), | |
462 $comment, | |
463 ); | |
464 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', $data); | |
465 if ($fix === true) { | |
466 $phpcsFile->fixer->replaceToken($stackPtr, '// '.substr($comment, (2 + $spaceCount)).$phpcsFile->eolChar); | |
467 } | |
468 }//end if | |
469 }//end if | |
470 | |
471 }//end processIndentation() | |
472 | |
473 | |
474 }//end class |