Skip to content

Commit a56545d

Browse files
author
Igor Melnikov
authored
Merge pull request #3101 from magento-architects/MAGETWO-91315-static-test-to-validate-structure-of-class-method-description
Magetwo 91315 static test to validate structure of class method description
2 parents 9a6382c + 023e58a commit a56545d

19 files changed

+1203
-2
lines changed
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
namespace Magento\Sniffs\Annotation;
8+
9+
use PHP_CodeSniffer\Files\File;
10+
11+
/**
12+
* Class to validate annotation format
13+
*/
14+
class AnnotationFormatValidator
15+
{
16+
/**
17+
* Gets the short description end pointer position
18+
*
19+
* @param File $phpcsFile
20+
* @param int $shortPtr
21+
* @param int $commentEndPtr
22+
* @return int
23+
*/
24+
private function getShortDescriptionEndPosition(File $phpcsFile, int $shortPtr, $commentEndPtr) : int
25+
{
26+
$tokens = $phpcsFile->getTokens();
27+
$shortPtrEnd = $shortPtr;
28+
for ($i = ($shortPtr + 1); $i < $commentEndPtr; $i++) {
29+
if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
30+
if ($tokens[$i]['line'] === $tokens[$shortPtrEnd]['line'] + 1) {
31+
$shortPtrEnd = $i;
32+
} else {
33+
break;
34+
}
35+
}
36+
}
37+
return $shortPtrEnd;
38+
}
39+
40+
/**
41+
* Validates whether the short description has multi lines in description
42+
*
43+
* @param File $phpcsFile
44+
* @param int $shortPtr
45+
* @param int $commentEndPtr
46+
*/
47+
private function validateMultiLinesInShortDescription(
48+
File $phpcsFile,
49+
int $shortPtr,
50+
int $commentEndPtr
51+
) : void {
52+
$tokens = $phpcsFile->getTokens();
53+
$shortPtrEnd = $this->getShortDescriptionEndPosition(
54+
$phpcsFile,
55+
(int) $shortPtr,
56+
$commentEndPtr
57+
);
58+
$shortPtrEndContent = $tokens[$shortPtrEnd]['content'];
59+
if (preg_match('/^[a-z]/', $shortPtrEndContent)
60+
&& $shortPtrEnd != $shortPtr
61+
&& !preg_match('/\bSee\b/', $shortPtrEndContent)
62+
&& $tokens[$shortPtr]['line']+1 === $tokens[$shortPtrEnd]['line']
63+
&& $tokens[$shortPtrEnd]['code'] !== T_DOC_COMMENT_TAG
64+
) {
65+
$error = 'Short description should not be in multi lines';
66+
$phpcsFile->addFixableError($error, $shortPtrEnd+1, 'MethodAnnotation');
67+
}
68+
}
69+
70+
/**
71+
* Validates whether the spacing between short and long descriptions
72+
*
73+
* @param File $phpcsFile
74+
* @param int $shortPtr
75+
* @param int $commentEndPtr
76+
* @param array $emptyTypeTokens
77+
*/
78+
private function validateSpacingBetweenShortAndLongDescriptions(
79+
File $phpcsFile,
80+
int $shortPtr,
81+
int $commentEndPtr,
82+
array $emptyTypeTokens
83+
) : void {
84+
$tokens = $phpcsFile->getTokens();
85+
$shortPtrEnd = $this->getShortDescriptionEndPosition(
86+
$phpcsFile,
87+
(int) $shortPtr,
88+
$commentEndPtr
89+
);
90+
$shortPtrEndContent = $tokens[$shortPtrEnd]['content'];
91+
if (preg_match('/^[A-Z]/', $shortPtrEndContent)
92+
&& !preg_match('/\bSee\b/', $shortPtrEndContent)
93+
&& $tokens[$shortPtr]['line']+1 === $tokens[$shortPtrEnd]['line']
94+
&& $tokens[$shortPtrEnd]['code'] !== T_DOC_COMMENT_TAG
95+
) {
96+
$error = 'There must be exactly one blank line between lines';
97+
$phpcsFile->addFixableError($error, $shortPtrEnd + 1, 'MethodAnnotation');
98+
}
99+
if ($shortPtrEnd != $shortPtr) {
100+
$this->validateLongDescriptionFormat($phpcsFile, $shortPtrEnd, $commentEndPtr, $emptyTypeTokens);
101+
} else {
102+
$this->validateLongDescriptionFormat($phpcsFile, $shortPtr, $commentEndPtr, $emptyTypeTokens);
103+
}
104+
}
105+
106+
/**
107+
* Validates short description format
108+
*
109+
* @param File $phpcsFile
110+
* @param int $shortPtr
111+
* @param int $stackPtr
112+
* @param int $commentEndPtr
113+
* @param array $emptyTypeTokens
114+
*/
115+
private function validateShortDescriptionFormat(
116+
File $phpcsFile,
117+
int $shortPtr,
118+
int $stackPtr,
119+
int $commentEndPtr,
120+
array $emptyTypeTokens
121+
) : void {
122+
$tokens = $phpcsFile->getTokens();
123+
if ($tokens[$shortPtr]['line'] !== $tokens[$stackPtr]['line'] + 1) {
124+
$error = 'No blank lines are allowed before short description';
125+
$phpcsFile->addFixableError($error, $shortPtr, 'MethodAnnotation');
126+
}
127+
if (strtolower($tokens[$shortPtr]['content']) === '{@inheritdoc}') {
128+
$error = '{@inheritdoc} imports only short description, annotation must have long description';
129+
$phpcsFile->addFixableError($error, $shortPtr, 'MethodAnnotation');
130+
}
131+
$shortPtrContent = $tokens[$shortPtr]['content'];
132+
if (preg_match('/^\p{Ll}/u', $shortPtrContent) === 1) {
133+
$error = 'Short description must start with a capital letter';
134+
$phpcsFile->addFixableError($error, $shortPtr, 'MethodAnnotation');
135+
}
136+
$this->validateNoExtraNewLineBeforeShortDescription(
137+
$phpcsFile,
138+
$stackPtr,
139+
$commentEndPtr,
140+
$emptyTypeTokens
141+
);
142+
$this->validateSpacingBetweenShortAndLongDescriptions(
143+
$phpcsFile,
144+
$shortPtr,
145+
$commentEndPtr,
146+
$emptyTypeTokens
147+
);
148+
$this->validateMultiLinesInShortDescription(
149+
$phpcsFile,
150+
$shortPtr,
151+
$commentEndPtr
152+
);
153+
}
154+
155+
/**
156+
* Validates long description format
157+
*
158+
* @param File $phpcsFile
159+
* @param int $shortPtrEnd
160+
* @param int $commentEndPtr
161+
* @param array $emptyTypeTokens
162+
*/
163+
private function validateLongDescriptionFormat(
164+
File $phpcsFile,
165+
int $shortPtrEnd,
166+
int $commentEndPtr,
167+
array $emptyTypeTokens
168+
) : void {
169+
$tokens = $phpcsFile->getTokens();
170+
$longPtr = $phpcsFile->findNext($emptyTypeTokens, $shortPtrEnd + 1, $commentEndPtr - 1, true);
171+
if (strtolower($tokens[$longPtr]['content']) === '{@inheritdoc}') {
172+
$error = '{@inheritdoc} imports only short description, annotation must have long description';
173+
$phpcsFile->addFixableError($error, $longPtr, 'MethodAnnotation');
174+
}
175+
if ($longPtr !== false && $tokens[$longPtr]['code'] === T_DOC_COMMENT_STRING) {
176+
if ($tokens[$longPtr]['line'] !== $tokens[$shortPtrEnd]['line'] + 2) {
177+
$error = 'There must be exactly one blank line between descriptions';
178+
$phpcsFile->addFixableError($error, $longPtr, 'MethodAnnotation');
179+
}
180+
if (preg_match('/^\p{Ll}/u', $tokens[$longPtr]['content']) === 1) {
181+
$error = 'Long description must start with a capital letter';
182+
$phpcsFile->addFixableError($error, $longPtr, 'MethodAnnotation');
183+
}
184+
}
185+
}
186+
187+
/**
188+
* Validates tags spacing format
189+
*
190+
* @param File $phpcsFile
191+
* @param int $commentStartPtr
192+
* @param array $emptyTypeTokens
193+
*/
194+
public function validateTagsSpacingFormat(File $phpcsFile, int $commentStartPtr, array $emptyTypeTokens) : void
195+
{
196+
$tokens = $phpcsFile->getTokens();
197+
if (isset($tokens[$commentStartPtr]['comment_tags'][0])) {
198+
$firstTagPtr = $tokens[$commentStartPtr]['comment_tags'][0];
199+
$commentTagPtrContent = $tokens[$firstTagPtr]['content'];
200+
$prevPtr = $phpcsFile->findPrevious($emptyTypeTokens, $firstTagPtr - 1, $commentStartPtr, true);
201+
if ($tokens[$firstTagPtr]['line'] !== $tokens[$prevPtr]['line'] + 2
202+
&& strtolower($commentTagPtrContent) !== '@inheritdoc'
203+
) {
204+
$error = 'There must be exactly one blank line before tags';
205+
$phpcsFile->addFixableError($error, $firstTagPtr, 'MethodAnnotation');
206+
}
207+
}
208+
}
209+
210+
/**
211+
* Validates tag grouping format
212+
*
213+
* @param File $phpcsFile
214+
* @param int $commentStartPtr
215+
*/
216+
public function validateTagGroupingFormat(File $phpcsFile, int $commentStartPtr) : void
217+
{
218+
$tokens = $phpcsFile->getTokens();
219+
$tagGroups = [];
220+
$groupId = 0;
221+
$paramGroupId = null;
222+
foreach ($tokens[$commentStartPtr]['comment_tags'] as $position => $tag) {
223+
if ($position > 0) {
224+
$prevPtr = $phpcsFile->findPrevious(
225+
T_DOC_COMMENT_STRING,
226+
$tag - 1,
227+
$tokens[$commentStartPtr]['comment_tags'][$position - 1]
228+
);
229+
if ($prevPtr === false) {
230+
$prevPtr = $tokens[$commentStartPtr]['comment_tags'][$position - 1];
231+
}
232+
233+
if ($tokens[$prevPtr]['line'] !== $tokens[$tag]['line'] - 1) {
234+
$groupId++;
235+
}
236+
}
237+
238+
if (strtolower($tokens[$tag]['content']) === '@param') {
239+
if ($paramGroupId !== null
240+
&& $paramGroupId !== $groupId) {
241+
$error = 'Parameter tags must be grouped together';
242+
$phpcsFile->addFixableError($error, $tag, 'MethodAnnotation');
243+
}
244+
if ($paramGroupId === null) {
245+
$paramGroupId = $groupId;
246+
}
247+
}
248+
$tagGroups[$groupId][] = $tag;
249+
}
250+
}
251+
252+
/**
253+
* Validates extra newline before short description
254+
*
255+
* @param File $phpcsFile
256+
* @param int $commentStartPtr
257+
* @param int $commentEndPtr
258+
* @param array $emptyTypeTokens
259+
*/
260+
private function validateNoExtraNewLineBeforeShortDescription(
261+
File $phpcsFile,
262+
int $commentStartPtr,
263+
int $commentEndPtr,
264+
array $emptyTypeTokens
265+
) : void {
266+
$tokens = $phpcsFile->getTokens();
267+
$prevPtr = $phpcsFile->findPrevious($emptyTypeTokens, $commentEndPtr - 1, $commentStartPtr, true);
268+
if ($tokens[$prevPtr]['line'] < ($tokens[$commentEndPtr]['line'] - 1)) {
269+
$error = 'Additional blank lines found at end of the annotation block';
270+
$phpcsFile->addFixableError($error, $commentEndPtr, 'MethodAnnotation');
271+
}
272+
}
273+
274+
/**
275+
* Validates structure description format
276+
*
277+
* @param File $phpcsFile
278+
* @param int $commentStartPtr
279+
* @param int $shortPtr
280+
* @param int $commentEndPtr
281+
* @param array $emptyTypeTokens
282+
*/
283+
public function validateDescriptionFormatStructure(
284+
File $phpcsFile,
285+
int $commentStartPtr,
286+
int $shortPtr,
287+
int $commentEndPtr,
288+
array $emptyTypeTokens
289+
) : void {
290+
$tokens = $phpcsFile->getTokens();
291+
if (isset($tokens[$commentStartPtr]['comment_tags'][0])
292+
) {
293+
$commentTagPtr = $tokens[$commentStartPtr]['comment_tags'][0];
294+
$commentTagPtrContent = $tokens[$commentTagPtr]['content'];
295+
if ($tokens[$shortPtr]['code'] !== T_DOC_COMMENT_STRING
296+
&& strtolower($commentTagPtrContent) !== '@inheritdoc'
297+
) {
298+
$error = 'Missing short description';
299+
$phpcsFile->addFixableError($error, $commentStartPtr, 'MethodAnnotation');
300+
} else {
301+
$this->validateShortDescriptionFormat(
302+
$phpcsFile,
303+
(int) $shortPtr,
304+
$commentStartPtr,
305+
$commentEndPtr,
306+
$emptyTypeTokens
307+
);
308+
}
309+
} else {
310+
$this->validateShortDescriptionFormat(
311+
$phpcsFile,
312+
(int) $shortPtr,
313+
$commentStartPtr,
314+
$commentEndPtr,
315+
$emptyTypeTokens
316+
);
317+
}
318+
}
319+
}

0 commit comments

Comments
 (0)