1<?php
2/**
3 * Parses and verifies the doc comments for functions.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @author    Marc McIntyre <mmcintyre@squiz.net>
11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 * @link      http://pear.php.net/package/PHP_CodeSniffer
14 */
15
16if (class_exists('PEAR_Sniffs_Commenting_FunctionCommentSniff', true) === false) {
17    throw new PHP_CodeSniffer_Exception('Class PEAR_Sniffs_Commenting_FunctionCommentSniff not found');
18}
19
20/**
21 * Parses and verifies the doc comments for functions.
22 *
23 * @category  PHP
24 * @package   PHP_CodeSniffer
25 * @author    Greg Sherwood <gsherwood@squiz.net>
26 * @author    Marc McIntyre <mmcintyre@squiz.net>
27 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
28 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
29 * @version   Release: @package_version@
30 * @link      http://pear.php.net/package/PHP_CodeSniffer
31 */
32class Squiz_Sniffs_Commenting_FunctionCommentSniff extends PEAR_Sniffs_Commenting_FunctionCommentSniff
33{
34
35    /**
36     * The current PHP version.
37     *
38     * @var integer
39     */
40    private $_phpVersion = null;
41
42
43    /**
44     * Process the return comment of this function comment.
45     *
46     * @param PHP_CodeSniffer_File $phpcsFile    The file being scanned.
47     * @param int                  $stackPtr     The position of the current token
48     *                                           in the stack passed in $tokens.
49     * @param int                  $commentStart The position in the stack where the comment started.
50     *
51     * @return void
52     */
53    protected function processReturn(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
54    {
55        $tokens = $phpcsFile->getTokens();
56
57        // Skip constructor and destructor.
58        $methodName      = $phpcsFile->getDeclarationName($stackPtr);
59        $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct');
60
61        $return = null;
62        foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
63            if ($tokens[$tag]['content'] === '@return') {
64                if ($return !== null) {
65                    $error = 'Only 1 @return tag is allowed in a function comment';
66                    $phpcsFile->addError($error, $tag, 'DuplicateReturn');
67                    return;
68                }
69
70                $return = $tag;
71            }
72        }
73
74        if ($isSpecialMethod === true) {
75            return;
76        }
77
78        if ($return !== null) {
79            $content = $tokens[($return + 2)]['content'];
80            if (empty($content) === true || $tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) {
81                $error = 'Return type missing for @return tag in function comment';
82                $phpcsFile->addError($error, $return, 'MissingReturnType');
83            } else {
84                // Support both a return type and a description.
85                $split = preg_match('`^((?:\|?(?:array\([^\)]*\)|[\\\\a-z0-9\[\]]+))*)( .*)?`i', $content, $returnParts);
86                if (isset($returnParts[1]) === false) {
87                    return;
88                }
89
90                $returnType = $returnParts[1];
91
92                // Check return type (can be multiple, separated by '|').
93                $typeNames      = explode('|', $returnType);
94                $suggestedNames = array();
95                foreach ($typeNames as $i => $typeName) {
96                    $suggestedName = PHP_CodeSniffer::suggestType($typeName);
97                    if (in_array($suggestedName, $suggestedNames) === false) {
98                        $suggestedNames[] = $suggestedName;
99                    }
100                }
101
102                $suggestedType = implode('|', $suggestedNames);
103                if ($returnType !== $suggestedType) {
104                    $error = 'Expected "%s" but found "%s" for function return type';
105                    $data  = array(
106                              $suggestedType,
107                              $returnType,
108                             );
109                    $fix   = $phpcsFile->addFixableError($error, $return, 'InvalidReturn', $data);
110                    if ($fix === true) {
111                        $replacement = $suggestedType;
112                        if (empty($returnParts[2]) === false) {
113                            $replacement .= $returnParts[2];
114                        }
115
116                        $phpcsFile->fixer->replaceToken(($return + 2), $replacement);
117                        unset($replacement);
118                    }
119                }
120
121                // If the return type is void, make sure there is
122                // no return statement in the function.
123                if ($returnType === 'void') {
124                    if (isset($tokens[$stackPtr]['scope_closer']) === true) {
125                        $endToken = $tokens[$stackPtr]['scope_closer'];
126                        for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) {
127                            if ($tokens[$returnToken]['code'] === T_CLOSURE
128                                || $tokens[$returnToken]['code'] === T_ANON_CLASS
129                            ) {
130                                $returnToken = $tokens[$returnToken]['scope_closer'];
131                                continue;
132                            }
133
134                            if ($tokens[$returnToken]['code'] === T_RETURN
135                                || $tokens[$returnToken]['code'] === T_YIELD
136                                || $tokens[$returnToken]['code'] === T_YIELD_FROM
137                            ) {
138                                break;
139                            }
140                        }
141
142                        if ($returnToken !== $endToken) {
143                            // If the function is not returning anything, just
144                            // exiting, then there is no problem.
145                            $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true);
146                            if ($tokens[$semicolon]['code'] !== T_SEMICOLON) {
147                                $error = 'Function return type is void, but function contains return statement';
148                                $phpcsFile->addError($error, $return, 'InvalidReturnVoid');
149                            }
150                        }
151                    }//end if
152                } else if ($returnType !== 'mixed' && in_array('void', $typeNames, true) === false) {
153                    // If return type is not void, there needs to be a return statement
154                    // somewhere in the function that returns something.
155                    if (isset($tokens[$stackPtr]['scope_closer']) === true) {
156                        $endToken    = $tokens[$stackPtr]['scope_closer'];
157                        $returnToken = $phpcsFile->findNext(array(T_RETURN, T_YIELD, T_YIELD_FROM), $stackPtr, $endToken);
158                        if ($returnToken === false) {
159                            $error = 'Function return type is not void, but function has no return statement';
160                            $phpcsFile->addError($error, $return, 'InvalidNoReturn');
161                        } else {
162                            $semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true);
163                            if ($tokens[$semicolon]['code'] === T_SEMICOLON) {
164                                $error = 'Function return type is not void, but function is returning void here';
165                                $phpcsFile->addError($error, $returnToken, 'InvalidReturnNotVoid');
166                            }
167                        }
168                    }
169                }//end if
170            }//end if
171        } else {
172            $error = 'Missing @return tag in function comment';
173            $phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn');
174        }//end if
175
176    }//end processReturn()
177
178
179    /**
180     * Process any throw tags that this function comment has.
181     *
182     * @param PHP_CodeSniffer_File $phpcsFile    The file being scanned.
183     * @param int                  $stackPtr     The position of the current token
184     *                                           in the stack passed in $tokens.
185     * @param int                  $commentStart The position in the stack where the comment started.
186     *
187     * @return void
188     */
189    protected function processThrows(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
190    {
191        $tokens = $phpcsFile->getTokens();
192
193        $throws = array();
194        foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
195            if ($tokens[$tag]['content'] !== '@throws') {
196                continue;
197            }
198
199            $exception = null;
200            $comment   = null;
201            if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) {
202                $matches = array();
203                preg_match('/([^\s]+)(?:\s+(.*))?/', $tokens[($tag + 2)]['content'], $matches);
204                $exception = $matches[1];
205                if (isset($matches[2]) === true && trim($matches[2]) !== '') {
206                    $comment = $matches[2];
207                }
208            }
209
210            if ($exception === null) {
211                $error = 'Exception type and comment missing for @throws tag in function comment';
212                $phpcsFile->addError($error, $tag, 'InvalidThrows');
213            } else if ($comment === null) {
214                $error = 'Comment missing for @throws tag in function comment';
215                $phpcsFile->addError($error, $tag, 'EmptyThrows');
216            } else {
217                // Any strings until the next tag belong to this comment.
218                if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) {
219                    $end = $tokens[$commentStart]['comment_tags'][($pos + 1)];
220                } else {
221                    $end = $tokens[$commentStart]['comment_closer'];
222                }
223
224                for ($i = ($tag + 3); $i < $end; $i++) {
225                    if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
226                        $comment .= ' '.$tokens[$i]['content'];
227                    }
228                }
229
230                // Starts with a capital letter and ends with a fullstop.
231                $firstChar = $comment{0};
232                if (strtoupper($firstChar) !== $firstChar) {
233                    $error = '@throws tag comment must start with a capital letter';
234                    $phpcsFile->addError($error, ($tag + 2), 'ThrowsNotCapital');
235                }
236
237                $lastChar = substr($comment, -1);
238                if ($lastChar !== '.') {
239                    $error = '@throws tag comment must end with a full stop';
240                    $phpcsFile->addError($error, ($tag + 2), 'ThrowsNoFullStop');
241                }
242            }//end if
243        }//end foreach
244
245    }//end processThrows()
246
247
248    /**
249     * Process the function parameter comments.
250     *
251     * @param PHP_CodeSniffer_File $phpcsFile    The file being scanned.
252     * @param int                  $stackPtr     The position of the current token
253     *                                           in the stack passed in $tokens.
254     * @param int                  $commentStart The position in the stack where the comment started.
255     *
256     * @return void
257     */
258    protected function processParams(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart)
259    {
260        if ($this->_phpVersion === null) {
261            $this->_phpVersion = PHP_CodeSniffer::getConfigData('php_version');
262            if ($this->_phpVersion === null) {
263                $this->_phpVersion = PHP_VERSION_ID;
264            }
265        }
266
267        $tokens = $phpcsFile->getTokens();
268
269        $params  = array();
270        $maxType = 0;
271        $maxVar  = 0;
272        foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
273            if ($tokens[$tag]['content'] !== '@param') {
274                continue;
275            }
276
277            $type         = '';
278            $typeSpace    = 0;
279            $var          = '';
280            $varSpace     = 0;
281            $comment      = '';
282            $commentLines = array();
283            if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) {
284                $matches = array();
285                preg_match('/([^$&.]+)(?:((?:\.\.\.)?(?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/', $tokens[($tag + 2)]['content'], $matches);
286
287                if (empty($matches) === false) {
288                    $typeLen   = strlen($matches[1]);
289                    $type      = trim($matches[1]);
290                    $typeSpace = ($typeLen - strlen($type));
291                    $typeLen   = strlen($type);
292                    if ($typeLen > $maxType) {
293                        $maxType = $typeLen;
294                    }
295                }
296
297                if (isset($matches[2]) === true) {
298                    $var    = $matches[2];
299                    $varLen = strlen($var);
300                    if ($varLen > $maxVar) {
301                        $maxVar = $varLen;
302                    }
303
304                    if (isset($matches[4]) === true) {
305                        $varSpace       = strlen($matches[3]);
306                        $comment        = $matches[4];
307                        $commentLines[] = array(
308                                           'comment' => $comment,
309                                           'token'   => ($tag + 2),
310                                           'indent'  => $varSpace,
311                                          );
312
313                        // Any strings until the next tag belong to this comment.
314                        if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) {
315                            $end = $tokens[$commentStart]['comment_tags'][($pos + 1)];
316                        } else {
317                            $end = $tokens[$commentStart]['comment_closer'];
318                        }
319
320                        for ($i = ($tag + 3); $i < $end; $i++) {
321                            if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
322                                $indent = 0;
323                                if ($tokens[($i - 1)]['code'] === T_DOC_COMMENT_WHITESPACE) {
324                                    $indent = strlen($tokens[($i - 1)]['content']);
325                                }
326
327                                $comment       .= ' '.$tokens[$i]['content'];
328                                $commentLines[] = array(
329                                                   'comment' => $tokens[$i]['content'],
330                                                   'token'   => $i,
331                                                   'indent'  => $indent,
332                                                  );
333                            }
334                        }
335                    } else {
336                        $error = 'Missing parameter comment';
337                        $phpcsFile->addError($error, $tag, 'MissingParamComment');
338                        $commentLines[] = array('comment' => '');
339                    }//end if
340                } else {
341                    $error = 'Missing parameter name';
342                    $phpcsFile->addError($error, $tag, 'MissingParamName');
343                }//end if
344            } else {
345                $error = 'Missing parameter type';
346                $phpcsFile->addError($error, $tag, 'MissingParamType');
347            }//end if
348
349            $params[] = array(
350                         'tag'          => $tag,
351                         'type'         => $type,
352                         'var'          => $var,
353                         'comment'      => $comment,
354                         'commentLines' => $commentLines,
355                         'type_space'   => $typeSpace,
356                         'var_space'    => $varSpace,
357                        );
358        }//end foreach
359
360        $realParams  = $phpcsFile->getMethodParameters($stackPtr);
361        $foundParams = array();
362
363        // We want to use ... for all variable length arguments, so added
364        // this prefix to the variable name so comparisons are easier.
365        foreach ($realParams as $pos => $param) {
366            if ($param['variable_length'] === true) {
367                $realParams[$pos]['name'] = '...'.$realParams[$pos]['name'];
368            }
369        }
370
371        foreach ($params as $pos => $param) {
372            // If the type is empty, the whole line is empty.
373            if ($param['type'] === '') {
374                continue;
375            }
376
377            // Check the param type value.
378            $typeNames          = explode('|', $param['type']);
379            $suggestedTypeNames = array();
380
381            foreach ($typeNames as $typeName) {
382                $suggestedName        = PHP_CodeSniffer::suggestType($typeName);
383                $suggestedTypeNames[] = $suggestedName;
384
385                if (count($typeNames) > 1) {
386                    continue;
387                }
388
389                // Check type hint for array and custom type.
390                $suggestedTypeHint = '';
391                if (strpos($suggestedName, 'array') !== false || substr($suggestedName, -2) === '[]') {
392                    $suggestedTypeHint = 'array';
393                } else if (strpos($suggestedName, 'callable') !== false) {
394                    $suggestedTypeHint = 'callable';
395                } else if (strpos($suggestedName, 'callback') !== false) {
396                    $suggestedTypeHint = 'callable';
397                } else if (in_array($suggestedName, PHP_CodeSniffer::$allowedTypes) === false) {
398                    $suggestedTypeHint = $suggestedName;
399                }
400
401                if ($this->_phpVersion >= 70000) {
402                    if ($suggestedName === 'string') {
403                        $suggestedTypeHint = 'string';
404                    } else if ($suggestedName === 'int' || $suggestedName === 'integer') {
405                        $suggestedTypeHint = 'int';
406                    } else if ($suggestedName === 'float') {
407                        $suggestedTypeHint = 'float';
408                    } else if ($suggestedName === 'bool' || $suggestedName === 'boolean') {
409                        $suggestedTypeHint = 'bool';
410                    }
411                }
412
413                if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true) {
414                    $typeHint = $realParams[$pos]['type_hint'];
415                    if ($typeHint === '') {
416                        $error = 'Type hint "%s" missing for %s';
417                        $data  = array(
418                                  $suggestedTypeHint,
419                                  $param['var'],
420                                 );
421
422                        $errorCode = 'TypeHintMissing';
423                        if ($suggestedTypeHint === 'string'
424                            || $suggestedTypeHint === 'int'
425                            || $suggestedTypeHint === 'float'
426                            || $suggestedTypeHint === 'bool'
427                        ) {
428                            $errorCode = 'Scalar'.$errorCode;
429                        }
430
431                        $phpcsFile->addError($error, $stackPtr, $errorCode, $data);
432                    } else if ($typeHint !== substr($suggestedTypeHint, (strlen($typeHint) * -1))) {
433                        $error = 'Expected type hint "%s"; found "%s" for %s';
434                        $data  = array(
435                                  $suggestedTypeHint,
436                                  $typeHint,
437                                  $param['var'],
438                                 );
439                        $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data);
440                    }//end if
441                } else if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) {
442                    $typeHint = $realParams[$pos]['type_hint'];
443                    if ($typeHint !== '') {
444                        $error = 'Unknown type hint "%s" found for %s';
445                        $data  = array(
446                                  $typeHint,
447                                  $param['var'],
448                                 );
449                        $phpcsFile->addError($error, $stackPtr, 'InvalidTypeHint', $data);
450                    }
451                }//end if
452            }//end foreach
453
454            $suggestedType = implode($suggestedTypeNames, '|');
455            if ($param['type'] !== $suggestedType) {
456                $error = 'Expected "%s" but found "%s" for parameter type';
457                $data  = array(
458                          $suggestedType,
459                          $param['type'],
460                         );
461
462                $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data);
463                if ($fix === true) {
464                    $phpcsFile->fixer->beginChangeset();
465
466                    $content  = $suggestedType;
467                    $content .= str_repeat(' ', $param['type_space']);
468                    $content .= $param['var'];
469                    $content .= str_repeat(' ', $param['var_space']);
470                    if (isset($param['commentLines'][0]) === true) {
471                        $content .= $param['commentLines'][0]['comment'];
472                    }
473
474                    $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
475
476                    // Fix up the indent of additional comment lines.
477                    foreach ($param['commentLines'] as $lineNum => $line) {
478                        if ($lineNum === 0
479                            || $param['commentLines'][$lineNum]['indent'] === 0
480                        ) {
481                            continue;
482                        }
483
484                        $diff      = (strlen($param['type']) - strlen($suggestedType));
485                        $newIndent = ($param['commentLines'][$lineNum]['indent'] - $diff);
486                        $phpcsFile->fixer->replaceToken(
487                            ($param['commentLines'][$lineNum]['token'] - 1),
488                            str_repeat(' ', $newIndent)
489                        );
490                    }
491
492                    $phpcsFile->fixer->endChangeset();
493                }//end if
494            }//end if
495
496            if ($param['var'] === '') {
497                continue;
498            }
499
500            $foundParams[] = $param['var'];
501
502            // Check number of spaces after the type.
503            $this->checkSpacingAfterParamType($phpcsFile, $param, $maxType);
504
505            // Make sure the param name is correct.
506            if (isset($realParams[$pos]) === true) {
507                $realName = $realParams[$pos]['name'];
508                if ($realName !== $param['var']) {
509                    $code = 'ParamNameNoMatch';
510                    $data = array(
511                             $param['var'],
512                             $realName,
513                            );
514
515                    $error = 'Doc comment for parameter %s does not match ';
516                    if (strtolower($param['var']) === strtolower($realName)) {
517                        $error .= 'case of ';
518                        $code   = 'ParamNameNoCaseMatch';
519                    }
520
521                    $error .= 'actual variable name %s';
522
523                    $phpcsFile->addError($error, $param['tag'], $code, $data);
524                }
525            } else if (substr($param['var'], -4) !== ',...') {
526                // We must have an extra parameter comment.
527                $error = 'Superfluous parameter comment';
528                $phpcsFile->addError($error, $param['tag'], 'ExtraParamComment');
529            }//end if
530
531            if ($param['comment'] === '') {
532                continue;
533            }
534
535            // Check number of spaces after the var name.
536            $this->checkSpacingAfterParamName($phpcsFile, $param, $maxVar);
537
538            // Param comments must start with a capital letter and end with the full stop.
539            if (preg_match('/^(\p{Ll}|\P{L})/u', $param['comment']) === 1) {
540                $error = 'Parameter comment must start with a capital letter';
541                $phpcsFile->addError($error, $param['tag'], 'ParamCommentNotCapital');
542            }
543
544            $lastChar = substr($param['comment'], -1);
545            if ($lastChar !== '.') {
546                $error = 'Parameter comment must end with a full stop';
547                $phpcsFile->addError($error, $param['tag'], 'ParamCommentFullStop');
548            }
549        }//end foreach
550
551        $realNames = array();
552        foreach ($realParams as $realParam) {
553            $realNames[] = $realParam['name'];
554        }
555
556        // Report missing comments.
557        $diff = array_diff($realNames, $foundParams);
558        foreach ($diff as $neededParam) {
559            $error = 'Doc comment for parameter "%s" missing';
560            $data  = array($neededParam);
561            $phpcsFile->addError($error, $commentStart, 'MissingParamTag', $data);
562        }
563
564    }//end processParams()
565
566
567    /**
568     * Check the spacing after the type of a parameter.
569     *
570     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
571     * @param array                $param     The parameter to be checked.
572     * @param int                  $maxType   The maxlength of the longest parameter type.
573     * @param int                  $spacing   The number of spaces to add after the type.
574     *
575     * @return void
576     */
577    protected function checkSpacingAfterParamType(PHP_CodeSniffer_File $phpcsFile, $param, $maxType, $spacing = 1)
578    {
579        // Check number of spaces after the type.
580        $spaces = ($maxType - strlen($param['type']) + $spacing);
581        if ($param['type_space'] !== $spaces) {
582            $error = 'Expected %s spaces after parameter type; %s found';
583            $data  = array(
584                      $spaces,
585                      $param['type_space'],
586                     );
587
588            $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamType', $data);
589            if ($fix === true) {
590                $phpcsFile->fixer->beginChangeset();
591
592                $content  = $param['type'];
593                $content .= str_repeat(' ', $spaces);
594                $content .= $param['var'];
595                $content .= str_repeat(' ', $param['var_space']);
596                $content .= $param['commentLines'][0]['comment'];
597                $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
598
599                // Fix up the indent of additional comment lines.
600                foreach ($param['commentLines'] as $lineNum => $line) {
601                    if ($lineNum === 0
602                        || $param['commentLines'][$lineNum]['indent'] === 0
603                    ) {
604                        continue;
605                    }
606
607                    $diff      = ($param['type_space'] - $spaces);
608                    $newIndent = ($param['commentLines'][$lineNum]['indent'] - $diff);
609                    $phpcsFile->fixer->replaceToken(
610                        ($param['commentLines'][$lineNum]['token'] - 1),
611                        str_repeat(' ', $newIndent)
612                    );
613                }
614
615                $phpcsFile->fixer->endChangeset();
616            }//end if
617        }//end if
618
619    }//end checkSpacingAfterParamType()
620
621
622    /**
623     * Check the spacing after the name of a parameter.
624     *
625     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
626     * @param array                $param     The parameter to be checked.
627     * @param int                  $maxVar    The maxlength of the longest parameter name.
628     * @param int                  $spacing   The number of spaces to add after the type.
629     *
630     * @return void
631     */
632    protected function checkSpacingAfterParamName(PHP_CodeSniffer_File $phpcsFile, $param, $maxVar, $spacing = 1)
633    {
634        // Check number of spaces after the var name.
635        $spaces = ($maxVar - strlen($param['var']) + $spacing);
636        if ($param['var_space'] !== $spaces) {
637            $error = 'Expected %s spaces after parameter name; %s found';
638            $data  = array(
639                      $spaces,
640                      $param['var_space'],
641                     );
642
643            $fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamName', $data);
644            if ($fix === true) {
645                $phpcsFile->fixer->beginChangeset();
646
647                $content  = $param['type'];
648                $content .= str_repeat(' ', $param['type_space']);
649                $content .= $param['var'];
650                $content .= str_repeat(' ', $spaces);
651                $content .= $param['commentLines'][0]['comment'];
652                $phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content);
653
654                // Fix up the indent of additional comment lines.
655                foreach ($param['commentLines'] as $lineNum => $line) {
656                    if ($lineNum === 0
657                        || $param['commentLines'][$lineNum]['indent'] === 0
658                    ) {
659                        continue;
660                    }
661
662                    $diff      = ($param['var_space'] - $spaces);
663                    $newIndent = ($param['commentLines'][$lineNum]['indent'] - $diff);
664                    $phpcsFile->fixer->replaceToken(
665                        ($param['commentLines'][$lineNum]['token'] - 1),
666                        str_repeat(' ', $newIndent)
667                    );
668                }
669
670                $phpcsFile->fixer->endChangeset();
671            }//end if
672        }//end if
673
674    }//end checkSpacingAfterParamName()
675
676
677}//end class
678