Skip to content

Commit 8deeced

Browse files
authored
Merge pull request #13 from magento-commerce/imported-magento-magento-coding-standard-209
[Imported] Move phpcs checks from magento2 to magento-coding-standard repo
2 parents 1499615 + 6fb644f commit 8deeced

28 files changed

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

0 commit comments

Comments
 (0)