Unverified Commit 50475f0f authored by Klaus Purer's avatar Klaus Purer Committed by GitHub
Browse files

fix(DocComment): Allow @code tags in parameter and return docs (#2060925)

parent aaa69893
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -137,6 +137,9 @@ class DocCommentAlignmentSniff implements Sniff
                }
            } else if ($tokens[($i + 2)]['code'] === T_DOC_COMMENT_TAG
                && $tokens[($i + 1)]['content'] !== ' '
                // Special @code/@endcode tags can have more than 1 space.
                && $tokens[($i + 2)]['content'] !== '@code'
                && $tokens[($i + 2)]['content'] !== '@endcode'
            ) {
                $error = 'Expected 1 space after asterisk; %s found';
                $data  = [strlen($tokens[($i + 1)]['content'])];
+15 −3
Original line number Diff line number Diff line
@@ -369,6 +369,10 @@ class DocCommentSniff implements Sniff
        $currentTag   = null;
        $previousTag  = null;
        $isNewGroup   = null;
        $ignoreTags   = [
            '@code',
            '@endcode',
        ];
        foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
            if ($pos > 0) {
                $prev = $phpcsFile->findPrevious(
@@ -408,6 +412,9 @@ class DocCommentSniff implements Sniff
                && (in_array($currentTag, ['@param', '@return', '@throws']) === true
                || in_array($previousTag, ['@param', '@return', '@throws']) === true)
                && $previousTag !== $currentTag
                // Ignore code blocks in comments, they can be anywhere.
                && in_array($previousTag, $ignoreTags) === false
                && in_array($currentTag, $ignoreTags) === false
            ) {
                $error = 'Separate the %s and %s sections by a blank line.';
                $fix   = $phpcsFile->addFixableError($error, $tag, 'TagGroupSpacing', [$previousTag, $currentTag]);
@@ -489,19 +496,24 @@ class DocCommentSniff implements Sniff
        }

        $foundTags = [];
        $lastPos   = 0;
        foreach ($tokens[$stackPtr]['comment_tags'] as $pos => $tag) {
            $tagName = $tokens[$tag]['content'];
            // Skip code tags, they can be anywhere.
            if (in_array($tagName, $ignoreTags) === true) {
                continue;
            }

            if (isset($foundTags[$tagName]) === true) {
                $lastTag = $tokens[$stackPtr]['comment_tags'][($pos - 1)];
                $lastTag = $tokens[$stackPtr]['comment_tags'][$lastPos];
                if ($tokens[$lastTag]['content'] !== $tagName) {
                    $error = 'Tags must be grouped together in a doc comment';
                    $phpcsFile->addError($error, $tag, 'TagsNotGrouped');
                }

                continue;
            }

            $foundTags[$tagName] = true;
            $lastPos = $pos;
        }

    }//end process()
+73 −9
Original line number Diff line number Diff line
@@ -205,12 +205,23 @@ class FunctionCommentSniff implements Sniff
                $return = $tag;
                // Any strings until the next tag belong to this comment.
                if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) {
                    $end = $tokens[$commentStart]['comment_tags'][($pos + 1)];
                    $skipTags = [
                        '@code',
                        '@endcode',
                    ];
                    $skipPos  = ($pos + 1);
                    while (isset($tokens[$commentStart]['comment_tags'][$skipPos]) === true
                        && in_array($tokens[$commentStart]['comment_tags'][$skipPos], $skipTags) === true
                    ) {
                        $skipPos++;
                    }

                    $end = $tokens[$commentStart]['comment_tags'][$skipPos];
                } else {
                    $end = $tokens[$commentStart]['comment_closer'];
                }
            }
        }
            }//end if
        }//end foreach

        $type = null;
        if ($isSpecialMethod === false && $methodName !== $className) {
@@ -531,10 +542,26 @@ class FunctionCommentSniff implements Sniff

                // Any strings until the next tag belong to this comment.
                if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) {
                    $end = $tokens[$commentStart]['comment_tags'][($pos + 1)];
                    // Ignore code tags and include them within this comment.
                    $skipTags = [
                        '@code',
                        '@endcode',
                    ];
                    $skipPos  = $pos;
                    while (isset($tokens[$commentStart]['comment_tags'][($skipPos + 1)]) === true
                        && in_array($tokens[$tokens[$commentStart]['comment_tags'][($skipPos + 1)]]['content'], $skipTags) === true
                    ) {
                        $skipPos++;
                    }

                    if ($skipPos === $pos) {
                        $skipPos++;
                    }

                    $end = $tokens[$commentStart]['comment_tags'][$skipPos];
                } else {
                    $end = $tokens[$commentStart]['comment_closer'];
                }
                }//end if

                for ($i = ($tag + 3); $i < $end; $i++) {
                    if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
@@ -847,12 +874,16 @@ class FunctionCommentSniff implements Sniff
                    $commentToken = $lastLine['token'];
                }

                // Don't show an error if the end of the comment is in a code
                // example.
                if ($this->isInCodeExample($phpcsFile, $commentToken, $param['tag']) === false) {
                    $fix = $phpcsFile->addFixableError($error, $commentToken, 'ParamCommentFullStop');
                    if ($fix === true) {
                        // Add a full stop as the last character of the comment.
                        $phpcsFile->fixer->addContent($commentToken, '.');
                    }
                }
            }
        }//end foreach

        // Missing parameters only apply to methods and not function because on
@@ -1012,4 +1043,37 @@ class FunctionCommentSniff implements Sniff
    }//end isAliasedType()


    /**
     * Determines if a comment line is part of an @code/@endcode example.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile    The file being scanned.
     * @param int                         $stackPtr     The position of the current token
     *                                                  in the stack passed in $tokens.
     * @param int                         $commentStart The position of the start of the comment
     *                                                  in the stack passed in $tokens.
     *
     * @return boolean Returns true if the comment line is within a @code block,
     *                 false otherwise.
     */
    protected function isInCodeExample(File $phpcsFile, $stackPtr, $commentStart)
    {
        $tokens = $phpcsFile->getTokens();
        if (strpos($tokens[$stackPtr]['content'], '@code') !== false) {
            return true;
        }

        $prevTag = $phpcsFile->findPrevious([T_DOC_COMMENT_TAG], ($stackPtr - 1), $commentStart);
        if ($prevTag === false) {
            return false;
        }

        if ($tokens[$prevTag]['content'] === '@code') {
            return true;
        }

        return false;

    }//end isInCodeExample()


}//end class
+111 −0
Original line number Diff line number Diff line
@@ -1652,3 +1652,114 @@ class Test4 {
  }

}

/**
 * Foo.
 */
interface Test5Interface {

  /**
   * Orders the result set by a given field.
   *
   * If called multiple times, the query will order by each specified field in
   * the order this method is called.
   *
   * If the query uses DISTINCT or GROUP BY conditions, fields or expressions
   * that are used for the order must be selected to be compatible with some
   * databases like PostgreSQL. The PostgreSQL driver can handle simple cases
   * automatically but it is suggested to explicitly specify them. Additionally,
   * when ordering on an alias, the alias must be added before orderBy() is
   * called.
   *
   * @param string $field
   *   The field on which to order. The field is escaped for security so only
   *   valid field and alias names are possible. To order by an expression, add
   *   the expression with addExpression() first and then use the alias to order
   *   on.
   *
   *   Example:
   *   @code
   *   $query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'order_field');
   *   $query->orderBy('order_field', 'ASC');
   *   @endcode
   * @param string $direction
   *   The direction to sort. Legal values are "ASC" and "DESC". Any other value
   *   will be converted to "ASC".
   *
   * @return \Drupal\Core\Database\Query\SelectInterface
   *   The called object.
   */
  public function orderBy($field, $direction = 'ASC');

  /**
   * Example with multiple code blocks in param docs.
   *
   * @param string $param1
   *   Just some Example param.
   * @param ...
   *   Any additional arguments are passed on to the functions called by
   *   drupal_form_submit(), including the unique form constructor function.
   *   For example, the node_edit form requires that a node object be passed
   *   in here when it is called. Arguments that need to be passed by reference
   *   should not be included here, but rather placed directly in the $form
   *   build info array so that the reference can be preserved. For example, a
   *   form builder function with the following signature:
   *   @code
   *   function mymodule_form($form, &$form_state, &$object) {
   *   }
   *   @endcode
   *   would be called via drupal_form_submit() as follows:
   *   @code
   *   $form_state['values'] = $my_form_values;
   *   $form_state['build_info']['args'] = array(&$object);
   *   drupal_form_submit('mymodule_form', $form_state);
   *   @endcode
   */
  public function test1($param1);

  /**
   * This is an example of a doc block that is good.
   *
   * We want to show some example code:
   * @code
   *   if ($something) {
   *     $x = $y;
   *   }
   * @endcode
   * Some more example code:
   * @code
   *   if ($something) {
   *     $x = $y;
   *   }
   * @endcode
   * And one more piece of example code:
   * @code
   *   if ($something) {
   *     $x = $y;
   *   }
   * @endcode
   * Followed by some summary text.
   */
  public function test2();

  /**
   * This is good.
   *
   * @return string
   *   Here is a comment, let's explain the return value with an example:
   *   @code
   *     if ($something) {
   *       $x = $y;
   *     }
   *   @endcode
   *   And then the comment goes on here. You want more code? Here you go:
   *   @code
   *     if ($something) {
   *       $x = $y;
   *     }
   *   @endcode
   *   And this is the end.
   */
  public function test3();

}