Skip to content

Commit 55cf76d

Browse files
committed
feature #4500 NoSuperfluousPhpdocTags - Add remove_inheritdoc option (julienfalque)
This PR was merged into the 2.16-dev branch. Discussion ---------- NoSuperfluousPhpdocTags - Add remove_inheritdoc option `@inheritDoc` is superfluous when not surrounded by text because it is already the default behavior. Commits ------- 6af35be Add remove_inheritdoc option
2 parents ac6f757 + 6af35be commit 55cf76d

File tree

3 files changed

+850
-34
lines changed

3 files changed

+850
-34
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,7 @@ Choose from the list of available rules:
11331133

11341134
- ``allow_mixed`` (``bool``): whether type ``mixed`` without description is allowed
11351135
(``true``) or considered superfluous (``false``); defaults to ``false``
1136+
- ``remove_inheritdoc`` (``bool``): remove ``@inheritDoc`` tags; defaults to ``false``
11361137

11371138
* **no_trailing_comma_in_list_call** [@Symfony, @PhpCsFixer]
11381139

src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php

Lines changed: 154 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ class Foo {
6767
public function doFoo(Bar $bar, $baz): Baz {}
6868
}
6969
', new VersionSpecification(70000)),
70+
new CodeSample('<?php
71+
class Foo {
72+
/**
73+
* @inheritDoc
74+
*/
75+
public function doFoo(Bar $bar, $baz) {}
76+
}
77+
', ['remove_inheritdoc' => true]),
7078
]
7179
);
7280
}
@@ -105,46 +113,27 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
105113
continue;
106114
}
107115

108-
$functionIndex = $this->findDocumentedFunction($tokens, $index);
109-
if (null === $functionIndex) {
110-
continue;
111-
}
112-
113-
$docBlock = new DocBlock($token->getContent());
116+
$content = $initialContent = $token->getContent();
114117

115-
$openingParenthesisIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']);
116-
$closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex);
118+
$documentedElementIndex = $this->findDocumentedElement($tokens, $index);
117119

118-
$argumentsInfo = $this->getArgumentsInfo(
119-
$tokens,
120-
$openingParenthesisIndex + 1,
121-
$closingParenthesisIndex - 1
122-
);
123-
124-
foreach ($docBlock->getAnnotationsOfType('param') as $annotation) {
125-
if (0 === Preg::match('/@param(?:\s+[^\$]\S+)?\s+(\$\S+)/', $annotation->getContent(), $matches)) {
126-
continue;
127-
}
120+
if (null === $documentedElementIndex) {
121+
continue;
122+
}
128123

129-
$argumentName = $matches[1];
124+
$token = $tokens[$documentedElementIndex];
130125

131-
if (
132-
!isset($argumentsInfo[$argumentName])
133-
|| $this->annotationIsSuperfluous($annotation, $argumentsInfo[$argumentName], $shortNames)
134-
) {
135-
$annotation->remove();
136-
}
126+
if ($token->isGivenKind(T_FUNCTION)) {
127+
$content = $this->fixFunctionDocComment($content, $tokens, $index, $shortNames);
137128
}
138129

139-
$returnTypeInfo = $this->getReturnTypeInfo($tokens, $closingParenthesisIndex);
140-
141-
foreach ($docBlock->getAnnotationsOfType('return') as $annotation) {
142-
if ($this->annotationIsSuperfluous($annotation, $returnTypeInfo, $shortNames)) {
143-
$annotation->remove();
144-
}
130+
if ($this->configuration['remove_inheritdoc']) {
131+
$content = $this->removeSuperfluousInheritDoc($content);
145132
}
146133

147-
$tokens[$index] = new Token([T_DOC_COMMENT, $docBlock->getContent()]);
134+
if ($content !== $initialContent) {
135+
$tokens[$index] = new Token([T_DOC_COMMENT, $content]);
136+
}
148137
}
149138
}
150139

@@ -158,22 +147,97 @@ protected function createConfigurationDefinition()
158147
->setAllowedTypes(['bool'])
159148
->setDefault(false)
160149
->getOption(),
150+
(new FixerOptionBuilder('remove_inheritdoc', 'Remove `@inheritDoc` tags'))
151+
->setAllowedTypes(['bool'])
152+
->setDefault(false)
153+
->getOption(),
161154
]);
162155
}
163156

164-
private function findDocumentedFunction(Tokens $tokens, $index)
157+
/**
158+
* @param Tokens $tokens
159+
* @param int $docCommentIndex
160+
*
161+
* @return null|int
162+
*/
163+
private function findDocumentedElement(Tokens $tokens, $docCommentIndex)
165164
{
165+
$index = $docCommentIndex;
166+
166167
do {
167168
$index = $tokens->getNextMeaningfulToken($index);
168169

169-
if (null === $index || $tokens[$index]->isGivenKind(T_FUNCTION)) {
170+
if (null === $index || $tokens[$index]->isGivenKind([T_FUNCTION, T_CLASS, T_INTERFACE])) {
170171
return $index;
171172
}
172173
} while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL, T_STATIC, T_PRIVATE, T_PROTECTED, T_PUBLIC]));
173174

175+
$index = $tokens->getNextMeaningfulToken($docCommentIndex);
176+
177+
$kindsBeforeProperty = [T_STATIC, T_PRIVATE, T_PROTECTED, T_PUBLIC];
178+
179+
if (!$tokens[$index]->isGivenKind($kindsBeforeProperty)) {
180+
return null;
181+
}
182+
183+
do {
184+
$index = $tokens->getNextMeaningfulToken($index);
185+
186+
if ($tokens[$index]->isGivenKind(T_VARIABLE)) {
187+
return $index;
188+
}
189+
} while ($tokens[$index]->isGivenKind($kindsBeforeProperty));
190+
174191
return null;
175192
}
176193

194+
/**
195+
* @param string $content
196+
* @param Tokens $tokens
197+
* @param int $functionIndex
198+
* @param array $shortNames
199+
*
200+
* @return string
201+
*/
202+
private function fixFunctionDocComment($content, Tokens $tokens, $functionIndex, array $shortNames)
203+
{
204+
$docBlock = new DocBlock($content);
205+
206+
$openingParenthesisIndex = $tokens->getNextTokenOfKind($functionIndex, ['(']);
207+
$closingParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex);
208+
209+
$argumentsInfo = $this->getArgumentsInfo(
210+
$tokens,
211+
$openingParenthesisIndex + 1,
212+
$closingParenthesisIndex - 1
213+
);
214+
215+
foreach ($docBlock->getAnnotationsOfType('param') as $annotation) {
216+
if (0 === Preg::match('/@param(?:\s+[^\$]\S+)?\s+(\$\S+)/', $annotation->getContent(), $matches)) {
217+
continue;
218+
}
219+
220+
$argumentName = $matches[1];
221+
222+
if (
223+
!isset($argumentsInfo[$argumentName])
224+
|| $this->annotationIsSuperfluous($annotation, $argumentsInfo[$argumentName], $shortNames)
225+
) {
226+
$annotation->remove();
227+
}
228+
}
229+
230+
$returnTypeInfo = $this->getReturnTypeInfo($tokens, $closingParenthesisIndex);
231+
232+
foreach ($docBlock->getAnnotationsOfType('return') as $annotation) {
233+
if ($this->annotationIsSuperfluous($annotation, $returnTypeInfo, $shortNames)) {
234+
$annotation->remove();
235+
}
236+
}
237+
238+
return $docBlock->getContent();
239+
}
240+
177241
/**
178242
* @param Tokens $tokens
179243
* @param int $start
@@ -327,4 +391,60 @@ function ($type) use ($symbolShortNames) {
327391

328392
return $normalized;
329393
}
394+
395+
/**
396+
* @param string $docComment
397+
*
398+
* @return string
399+
*/
400+
private function removeSuperfluousInheritDoc($docComment)
401+
{
402+
return Preg::replace('~
403+
# $1: before @inheritDoc tag
404+
(
405+
# beginning of comment or a PHPDoc tag
406+
(?:
407+
^/\*\*
408+
(?:
409+
\R
410+
[ \t]*(?:\*[ \t]*)?
411+
)*?
412+
|
413+
@\N+
414+
)
415+
416+
# empty comment lines
417+
(?:
418+
\R
419+
[ \t]*(?:\*[ \t]*?)?
420+
)*
421+
)
422+
423+
# spaces before @inheritDoc tag
424+
[ \t]*
425+
426+
# @inheritDoc tag
427+
(?:@inheritDocs?|\{@inheritDocs?\})
428+
429+
# $2: after @inheritDoc tag
430+
(
431+
# empty comment lines
432+
(?:
433+
\R
434+
[ \t]*(?:\*[ \t]*)?
435+
)*
436+
437+
# a PHPDoc tag or end of comment
438+
(?:
439+
@\N+
440+
|
441+
(?:
442+
\R
443+
[ \t]*(?:\*[ \t]*)?
444+
)*
445+
[ \t]*\*/$
446+
)
447+
)
448+
~ix', '$1$2', $docComment);
449+
}
330450
}

0 commit comments

Comments
 (0)