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

fix(InlineComment): Sync sniff with upstream code from PHPCS to prevent phpcbf loops (#3032222)

parent 6f6b654c
Loading
Loading
Loading
Loading
+204 −185
Original line number Diff line number Diff line
@@ -92,12 +92,16 @@ class InlineCommentSniff implements Sniff
                T_ABSTRACT,
                T_CONST,
                T_PROPERTY,
                T_INCLUDE,
                T_INCLUDE_ONCE,
                T_REQUIRE,
                T_REQUIRE_ONCE,
                T_VAR,
            ];

            // Also ignore all doc blocks defined in the outer scope (no scope
            // conditions are set).
            if (in_array($tokens[$nextToken]['code'], $ignore) === true
            if (in_array($tokens[$nextToken]['code'], $ignore, true) === true
                || empty($tokens[$stackPtr]['conditions']) === true
            ) {
                return;
@@ -152,8 +156,8 @@ class InlineCommentSniff implements Sniff
            }
        }

        // We don't want end of block comments. If the last comment is a closing
        // curly brace.
        // We don't want end of block comments. Check if the last token before the
        // comment is a closing curly brace.
        $previousContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
        if ($tokens[$previousContent]['line'] === $tokens[$stackPtr]['line']) {
            if ($tokens[$previousContent]['code'] === T_CLOSE_CURLY_BRACKET) {
@@ -171,10 +175,8 @@ class InlineCommentSniff implements Sniff
            }
        }

        $comment = rtrim($tokens[$stackPtr]['content']);

        // Only want inline comments.
        if (substr($comment, 0, 2) !== '//') {
        if (substr($tokens[$stackPtr]['content'], 0, 2) !== '//') {
            return;
        }

@@ -183,46 +185,148 @@ class InlineCommentSniff implements Sniff
            return;
        }

        // Verify the indentation of this comment line.
        $this->processIndentation($phpcsFile, $stackPtr);
        $commentTokens = [$stackPtr];

        // If the current line starts with a tag such as "@see" then the trailing dot
        // rules and upper case start rules don't apply.
        if (strpos(trim(substr($tokens[$stackPtr]['content'], 2)), '@') === 0) {
            return;
        $nextComment = $stackPtr;
        $lastComment = $stackPtr;
        while (($nextComment = $phpcsFile->findNext(T_COMMENT, ($nextComment + 1), null, false)) !== false) {
            if ($tokens[$nextComment]['line'] !== ($tokens[$lastComment]['line'] + 1)) {
                break;
            }

        // The below section determines if a comment block is correctly capitalised,
        // and ends in a full-stop. It will find the last comment in a block, and
        // work its way up.
        $nextComment = $phpcsFile->findNext([T_COMMENT], ($stackPtr + 1), null, false);
        if (($nextComment !== false)
            && (($tokens[$nextComment]['line']) === ($tokens[$stackPtr]['line'] + 1))
            // A tag such as @todo means a separate comment block.
            && strpos(trim(substr($tokens[$nextComment]['content'], 2)), '@') !== 0
        ) {
            return;
            // Only want inline comments.
            if (substr($tokens[$nextComment]['content'], 0, 2) !== '//') {
                break;
            }

        $topComment  = $stackPtr;
        $lastComment = $stackPtr;
        while (($topComment = $phpcsFile->findPrevious([T_COMMENT], ($lastComment - 1), null, false)) !== false) {
            if ($tokens[$topComment]['line'] !== ($tokens[$lastComment]['line'] - 1)) {
            // There is a comment on the very next line. If there is
            // no code between the comments, they are part of the same
            // comment block.
            $prevNonWhitespace = $phpcsFile->findPrevious(T_WHITESPACE, ($nextComment - 1), $lastComment, true);
            if ($prevNonWhitespace !== $lastComment) {
                break;
            }

            $lastComment = $topComment;
            // A comment starting with "@" means a new comment section.
            if (preg_match('|^//[\s]*@|', $tokens[$nextComment]['content']) === 1) {
                break;
            }

        $topComment  = $lastComment;
            $commentTokens[] = $nextComment;
            $lastComment     = $nextComment;
        }//end while

        $commentText = '';
        foreach ($commentTokens as $lastCommentToken) {
            $comment = rtrim($tokens[$lastCommentToken]['content']);

            if (trim(substr($comment, 2)) === '') {
                continue;
            }

            $spaceCount = 0;
            $tabFound   = false;

            $commentLength = strlen($comment);
            for ($i = 2; $i < $commentLength; $i++) {
                if ($comment[$i] === "\t") {
                    $tabFound = true;
                    break;
                }

        for ($i = $topComment; $i <= $stackPtr; $i++) {
            if ($tokens[$i]['code'] === T_COMMENT) {
                $commentText .= trim(substr($tokens[$i]['content'], 2));
                if ($comment[$i] !== ' ') {
                    break;
                }

                $spaceCount++;
            }

            $fix = false;
            if ($tabFound === true) {
                $error = 'Tab found before comment text; expected "// %s" but found "%s"';
                $data  = [
                    ltrim(substr($comment, 2)),
                    $comment,
                ];
                $fix   = $phpcsFile->addFixableError($error, $lastCommentToken, 'TabBefore', $data);
            } else if ($spaceCount === 0) {
                $error = 'No space found before comment text; expected "// %s" but found "%s"';
                $data  = [
                    substr($comment, 2),
                    $comment,
                ];
                $fix   = $phpcsFile->addFixableError($error, $lastCommentToken, 'NoSpaceBefore', $data);
            }//end if

            if ($fix === true) {
                $newComment = '// '.ltrim($tokens[$lastCommentToken]['content'], "/\t ");
                $phpcsFile->fixer->replaceToken($lastCommentToken, $newComment);
            }

            if ($spaceCount > 1) {
                // Check if there is a comment on the previous line that justifies the
                // indentation.
                $prevComment = $phpcsFile->findPrevious([T_COMMENT], ($lastCommentToken - 1), null, false);
                if (($prevComment !== false) && (($tokens[$prevComment]['line']) === ($tokens[$lastCommentToken]['line'] - 1))) {
                    $prevCommentText = rtrim($tokens[$prevComment]['content']);
                    $prevSpaceCount  = 0;
                    for ($i = 2; $i < strlen($prevCommentText); $i++) {
                        if ($prevCommentText[$i] !== ' ') {
                            break;
                        }

                        $prevSpaceCount++;
                    }

                    if ($spaceCount > $prevSpaceCount && $prevSpaceCount > 0) {
                        // A previous comment could be a list item or @todo.
                        $indentationStarters = [
                            '-',
                            '@todo',
                        ];
                        $words        = preg_split('/\s+/', $prevCommentText);
                        $numberedList = (bool) preg_match('/^[0-9]+\./', $words[1]);
                        if (in_array($words[1], $indentationStarters) === true) {
                            if ($spaceCount !== ($prevSpaceCount + 2)) {
                                $error = 'Comment indentation error after %s element, expected %s spaces';
                                $fix   = $phpcsFile->addFixableError($error, $lastCommentToken, 'SpacingBefore', [$words[1], ($prevSpaceCount + 2)]);
                                if ($fix === true) {
                                    $newComment = '//'.str_repeat(' ', ($prevSpaceCount + 2)).ltrim($tokens[$lastCommentToken]['content'], "/\t ");
                                    $phpcsFile->fixer->replaceToken($lastCommentToken, $newComment);
                                }
                            }
                        } else if ($numberedList === true) {
                            $expectedSpaceCount = ($prevSpaceCount + strlen($words[1]) + 1);
                            if ($spaceCount !== $expectedSpaceCount) {
                                $error = 'Comment indentation error, expected %s spaces';
                                $fix   = $phpcsFile->addFixableError($error, $lastCommentToken, 'SpacingBefore', [$expectedSpaceCount]);
                                if ($fix === true) {
                                    $newComment = '//'.str_repeat(' ', $expectedSpaceCount).ltrim($tokens[$lastCommentToken]['content'], "/\t ");
                                    $phpcsFile->fixer->replaceToken($lastCommentToken, $newComment);
                                }
                            }
                        } else {
                            $error = 'Comment indentation error, expected only %s spaces';
                            $phpcsFile->addError($error, $lastCommentToken, 'SpacingBefore', [$prevSpaceCount]);
                        }//end if
                    }//end if
                } else {
                    $error = '%s spaces found before inline comment; expected "// %s" but found "%s"';
                    $data  = [
                        $spaceCount,
                        substr($comment, (2 + $spaceCount)),
                        $comment,
                    ];
                    $fix   = $phpcsFile->addFixableError($error, $lastCommentToken, 'SpacingBefore', $data);
                    if ($fix === true) {
                        $phpcsFile->fixer->replaceToken($lastCommentToken, '// '.substr($comment, (2 + $spaceCount)).$phpcsFile->eolChar);
                    }
                }//end if
            }//end if

            $commentText .= trim(substr($tokens[$lastCommentToken]['content'], 2));
        }//end foreach

        if ($commentText === '') {
            $error = 'Blank comments are not allowed';
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'Empty');
@@ -230,36 +334,37 @@ class InlineCommentSniff implements Sniff
                $phpcsFile->fixer->replaceToken($stackPtr, '');
            }

            return;
            return ($lastCommentToken + 1);
        }

        $words = preg_split('/\s+/', $commentText);
        if (preg_match('|\p{Lu}|u', $commentText[0]) === 0 && $commentText[0] !== '@') {
        if (preg_match('/^\p{Ll}/u', $commentText) === 1) {
            // Allow special lower cased words that contain non-alpha characters
            // (function references, machine names with underscores etc.).
            $matches = [];
            preg_match('/[a-z]+/', $words[0], $matches);
            if (isset($matches[0]) === true && $matches[0] === $words[0]) {
                $error = 'Inline comments must start with a capital letter';
                $fix   = $phpcsFile->addFixableError($error, $topComment, 'NotCapital');
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NotCapital');
                if ($fix === true) {
                    $newComment = preg_replace("/$words[0]/", ucfirst($words[0]), $tokens[$topComment]['content'], 1);
                    $phpcsFile->fixer->replaceToken($topComment, $newComment);
                    $newComment = preg_replace("/$words[0]/", ucfirst($words[0]), $tokens[$stackPtr]['content'], 1);
                    $phpcsFile->fixer->replaceToken($stackPtr, $newComment);
                }
            }
        }

        // Only check the end of comment character if the start of the comment
        // is a letter, indicating that the comment is just standard text.
        if (preg_match('/^\p{L}/u', $commentText) === 1) {
            $commentCloser   = $commentText[(strlen($commentText) - 1)];
            $acceptedClosers = [
                'full-stops'             => '.',
                'exclamation marks'      => '!',
            'colons'                 => ':',
                'question marks'         => '?',
                'colons'                 => ':',
                'or closing parentheses' => ')',
            ];

        // Allow @tag style comments without punctuation.
        if (in_array($commentCloser, $acceptedClosers) === false && $commentText[0] !== '@') {
            // Allow special last words like URLs or function references
            // without punctuation.
            $lastWord = $words[(count($words) - 1)];
@@ -272,7 +377,9 @@ class InlineCommentSniff implements Sniff
            // Also allow closing tags like @endlink or @endcode.
            $isEndTag = $lastWord[0] === '@';

            if ($isUrl === false && $isFunction === false && $isEndTag === false) {
            if (in_array($commentCloser, $acceptedClosers, true) === false
                && $isUrl === false && $isFunction === false && $isEndTag === false
            ) {
                $error = 'Inline comments must end in %s';
                $ender = '';
                foreach ($acceptedClosers as $closerName => $symbol) {
@@ -281,37 +388,59 @@ class InlineCommentSniff implements Sniff

                $ender = trim($ender, ' ,');
                $data  = [$ender];
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'InvalidEndChar', $data);
                $fix   = $phpcsFile->addFixableError($error, $lastCommentToken, 'InvalidEndChar', $data);
                if ($fix === true) {
                    $newContent = preg_replace('/(\s+)$/', '.$1', $tokens[$stackPtr]['content']);
                    $phpcsFile->fixer->replaceToken($stackPtr, $newContent);
                    $newContent = preg_replace('/(\s+)$/', '.$1', $tokens[$lastCommentToken]['content']);
                    $phpcsFile->fixer->replaceToken($lastCommentToken, $newContent);
                }
            }
        }//end if

        // Finally, the line below the last comment cannot be empty if this inline
        // comment is on a line by itself.
        if ($tokens[$previousContent]['line'] < $tokens[$stackPtr]['line'] && ($stackPtr + 1) < $phpcsFile->numTokens) {
            for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) {
                if ($tokens[$i]['line'] === ($tokens[$stackPtr]['line'] + 1)) {
                    if ($tokens[$i]['code'] !== T_WHITESPACE || $i === ($phpcsFile->numTokens - 1)) {
                        return;
        if ($tokens[$previousContent]['line'] < $tokens[$stackPtr]['line']) {
            $next = $phpcsFile->findNext(T_WHITESPACE, ($lastCommentToken + 1), null, true);
            if ($next === false) {
                // Ignore if the comment is the last non-whitespace token in a file.
                return ($lastCommentToken + 1);
            }
                } else if ($tokens[$i]['line'] > ($tokens[$stackPtr]['line'] + 1)) {
                    break;

            if ($tokens[$next]['code'] === T_DOC_COMMENT_OPEN_TAG) {
                // If this inline comment is followed by a docblock,
                // ignore spacing as docblock/function etc spacing rules
                // are likely to conflict with our rules.
                return ($lastCommentToken + 1);
            }

            $errorCode = 'SpacingAfter';

            if (isset($tokens[$stackPtr]['conditions']) === true) {
                $conditions   = $tokens[$stackPtr]['conditions'];
                $type         = end($conditions);
                $conditionPtr = key($conditions);

                if (($type === T_FUNCTION || $type === T_CLOSURE)
                    && $tokens[$conditionPtr]['scope_closer'] === $next
                ) {
                    $errorCode = 'SpacingAfterAtFunctionEnd';
                }
            }

            $warning = 'There must be no blank line following an inline comment';
            $fix     = $phpcsFile->addFixableWarning($warning, $stackPtr, 'SpacingAfter');
            if ($fix === true) {
                $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
                if ($next === ($phpcsFile->numTokens - 1)) {
                    return;
            for ($i = ($lastCommentToken + 1); $i < $phpcsFile->numTokens; $i++) {
                if ($tokens[$i]['line'] === ($tokens[$lastCommentToken]['line'] + 1)) {
                    if ($tokens[$i]['code'] !== T_WHITESPACE) {
                        return ($lastCommentToken + 1);
                    }
                } else if ($tokens[$i]['line'] > ($tokens[$lastCommentToken]['line'] + 1)) {
                    break;
                }
            }

            $error = 'There must be no blank line following an inline comment';
            $fix   = $phpcsFile->addFixableWarning($error, $lastCommentToken, $errorCode);
            if ($fix === true) {
                $phpcsFile->fixer->beginChangeset();
                for ($i = ($stackPtr + 1); $i < $next; $i++) {
                for ($i = ($lastCommentToken + 1); $i < $next; $i++) {
                    if ($tokens[$i]['line'] === $tokens[$next]['line']) {
                        break;
                    }
@@ -323,6 +452,8 @@ class InlineCommentSniff implements Sniff
            }
        }//end if

        return ($lastCommentToken + 1);

    }//end process()


@@ -339,6 +470,10 @@ class InlineCommentSniff implements Sniff
    protected function isInCodeExample(File $phpcsFile, $stackPtr)
    {
        $tokens = $phpcsFile->getTokens();
        if ($tokens[$stackPtr]['content'] === '// @code'.$phpcsFile->eolChar) {
            return true;
        }

        $prevComment = $stackPtr;
        $lastComment = $stackPtr;
        while (($prevComment = $phpcsFile->findPrevious([T_COMMENT], ($lastComment - 1), null, false)) !== false) {
@@ -362,120 +497,4 @@ class InlineCommentSniff implements Sniff
    }//end isInCodeExample()


    /**
     * Checks the indentation level of the comment contents.
     *
     * @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.
     *
     * @return void
     */
    protected function processIndentation(File $phpcsFile, $stackPtr)
    {
        $tokens     = $phpcsFile->getTokens();
        $comment    = rtrim($tokens[$stackPtr]['content']);
        $spaceCount = 0;
        $tabFound   = false;

        $commentLength = strlen($comment);
        for ($i = 2; $i < $commentLength; $i++) {
            if ($comment[$i] === "\t") {
                $tabFound = true;
                break;
            }

            if ($comment[$i] !== ' ') {
                break;
            }

            $spaceCount++;
        }

        $fix = false;
        if ($tabFound === true) {
            $error = 'Tab found before comment text; expected "// %s" but found "%s"';
            $data  = [
                ltrim(substr($comment, 2)),
                $comment,
            ];
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'TabBefore', $data);
        } else if ($spaceCount === 0 && strlen($comment) > 2) {
            $error = 'No space found before comment text; expected "// %s" but found "%s"';
            $data  = [
                substr($comment, 2),
                $comment,
            ];
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBefore', $data);
        }//end if

        if ($fix === true) {
            $newComment = '// '.ltrim($tokens[$stackPtr]['content'], "/\t ");
            $phpcsFile->fixer->replaceToken($stackPtr, $newComment);
        }

        if ($spaceCount > 1) {
            // Check if there is a comment on the previous line that justifies the
            // indentation.
            $prevComment = $phpcsFile->findPrevious([T_COMMENT], ($stackPtr - 1), null, false);
            if (($prevComment !== false) && (($tokens[$prevComment]['line']) === ($tokens[$stackPtr]['line'] - 1))) {
                $prevCommentText = rtrim($tokens[$prevComment]['content']);
                $prevSpaceCount  = 0;
                for ($i = 2; $i < strlen($prevCommentText); $i++) {
                    if ($prevCommentText[$i] !== ' ') {
                        break;
                    }

                    $prevSpaceCount++;
                }

                if ($spaceCount > $prevSpaceCount && $prevSpaceCount > 0) {
                    // A previous comment could be a list item or @todo.
                    $indentationStarters = [
                        '-',
                        '@todo',
                    ];
                    $words        = preg_split('/\s+/', $prevCommentText);
                    $numberedList = (bool) preg_match('/^[0-9]+\./', $words[1]);
                    if (in_array($words[1], $indentationStarters) === true) {
                        if ($spaceCount !== ($prevSpaceCount + 2)) {
                            $error = 'Comment indentation error after %s element, expected %s spaces';
                            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', [$words[1], ($prevSpaceCount + 2)]);
                            if ($fix === true) {
                                $newComment = '//'.str_repeat(' ', ($prevSpaceCount + 2)).ltrim($tokens[$stackPtr]['content'], "/\t ");
                                $phpcsFile->fixer->replaceToken($stackPtr, $newComment);
                            }
                        }
                    } else if ($numberedList === true) {
                        $expectedSpaceCount = ($prevSpaceCount + strlen($words[1]) + 1);
                        if ($spaceCount !== $expectedSpaceCount) {
                            $error = 'Comment indentation error, expected %s spaces';
                            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', [$expectedSpaceCount]);
                            if ($fix === true) {
                                $newComment = '//'.str_repeat(' ', $expectedSpaceCount).ltrim($tokens[$stackPtr]['content'], "/\t ");
                                $phpcsFile->fixer->replaceToken($stackPtr, $newComment);
                            }
                        }
                    } else {
                        $error = 'Comment indentation error, expected only %s spaces';
                        $phpcsFile->addError($error, $stackPtr, 'SpacingBefore', [$prevSpaceCount]);
                    }//end if
                }//end if
            } else {
                $error = '%s spaces found before inline comment; expected "// %s" but found "%s"';
                $data  = [
                    $spaceCount,
                    substr($comment, (2 + $spaceCount)),
                    $comment,
                ];
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', $data);
                if ($fix === true) {
                    $phpcsFile->fixer->replaceToken($stackPtr, '// '.substr($comment, (2 + $spaceCount)).$phpcsFile->eolChar);
                }
            }//end if
        }//end if

    }//end processIndentation()


}//end class
+1 −1
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ function example_custom_block_view($delta = '') {

  switch ($delta) {
    case 'my_block':
      // $block['subject'] = t('custom twitter feed');.
      // $block['subject'] = t('custom twitter feed');
      $block['content'] = test();;
      break;
  }
+1 −1
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ class InlineCommentUnitTest extends CoderSniffUnitTest
            24 => 1,
            44 => 1,
            47 => 1,
            59 => 2,
            59 => 1,
            81 => 1,
            83 => 1,
        ];
+1 −0
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ class BadUnitTest extends CoderSniffUnitTest
                10  => 1,
                12  => 1,
                16  => 1,
                18  => 1,
                19  => 2,
                20  => 1,
                21  => 1,
+1 −1
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@
//   - should be only 2 additional spaces.
//   - this one is correct.
// Blank comments are not allowed outside of a comment block.
// PHP Constants should be written in CAPITAL letters
// PHP Constants should be written in CAPITAL letters.
// Comments should be on a separate line.
TRUE;
FALSE;