@@ -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