You are here

public function InlineCommentSniff::process in Coder 8.2

Same name and namespace in other branches
  1. 8.3 coder_sniffer/Drupal/Sniffs/Commenting/InlineCommentSniff.php \Drupal\Sniffs\Commenting\InlineCommentSniff::process()
  2. 8.3.x coder_sniffer/Drupal/Sniffs/Commenting/InlineCommentSniff.php \Drupal\Sniffs\Commenting\InlineCommentSniff::process()

Processes this test, when one of its tokens is encountered.

Parameters

\PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.:

int $stackPtr The position of the current token in the: stack passed in $tokens.

Return value

void

File

coder_sniffer/Drupal/Sniffs/Commenting/InlineCommentSniff.php, line 66

Class

InlineCommentSniff
\Drupal\Sniffs\Commenting\InlineCommentSniff.

Namespace

Drupal\Sniffs\Commenting

Code

public function process(File $phpcsFile, $stackPtr) {
  $tokens = $phpcsFile
    ->getTokens();

  // If this is a function/class/interface doc block comment, skip it.
  // We are only interested in inline doc block comments, which are
  // not allowed.
  if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) {
    $nextToken = $phpcsFile
      ->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
    $ignore = array(
      T_CLASS,
      T_INTERFACE,
      T_TRAIT,
      T_FUNCTION,
      T_CLOSURE,
      T_PUBLIC,
      T_PRIVATE,
      T_PROTECTED,
      T_FINAL,
      T_STATIC,
      T_ABSTRACT,
      T_CONST,
      T_PROPERTY,
      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 || empty($tokens[$stackPtr]['conditions']) === true) {
      return;
    }
    if ($phpcsFile->tokenizerType === 'JS') {

      // We allow block comments if a function or object
      // is being assigned to a variable.
      $ignore = Tokens::$emptyTokens;
      $ignore[] = T_EQUAL;
      $ignore[] = T_STRING;
      $ignore[] = T_OBJECT_OPERATOR;
      $nextToken = $phpcsFile
        ->findNext($ignore, $nextToken + 1, null, true);
      if ($tokens[$nextToken]['code'] === T_FUNCTION || $tokens[$nextToken]['code'] === T_CLOSURE || $tokens[$nextToken]['code'] === T_OBJECT || $tokens[$nextToken]['code'] === T_PROTOTYPE) {
        return;
      }
    }
    $prevToken = $phpcsFile
      ->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true);
    if ($tokens[$prevToken]['code'] === T_OPEN_TAG) {
      return;
    }

    // Inline doc blocks are allowed in JSDoc.
    if ($tokens[$stackPtr]['content'] === '/**' && $phpcsFile->tokenizerType !== 'JS') {

      // The only exception to inline doc blocks is the /** @var */
      // declaration.
      $content = $phpcsFile
        ->getTokensAsString($stackPtr, $tokens[$stackPtr]['comment_closer'] - $stackPtr + 1);
      if (preg_match('#^/\\*\\* @var [a-zA-Z0-9_\\\\\\[\\]|]+ \\$[a-zA-Z0-9_]+ \\*/$#', $content) !== 1) {
        $error = 'Inline doc block comments are not allowed; use "/* Comment */" or "// Comment" instead';
        $phpcsFile
          ->addError($error, $stackPtr, 'DocBlock');
      }
    }
  }

  //end if
  if ($tokens[$stackPtr]['content'][0] === '#') {
    $error = 'Perl-style comments are not allowed; use "// Comment" instead';
    $fix = $phpcsFile
      ->addFixableError($error, $stackPtr, 'WrongStyle');
    if ($fix === true) {
      $comment = ltrim($tokens[$stackPtr]['content'], "# \t");
      $phpcsFile->fixer
        ->replaceToken($stackPtr, "// {$comment}");
    }
  }

  // We don't want end of block comments. If the last 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) {
      return;
    }

    // Special case for JS files.
    if ($tokens[$previousContent]['code'] === T_COMMA || $tokens[$previousContent]['code'] === T_SEMICOLON) {
      $lastContent = $phpcsFile
        ->findPrevious(T_WHITESPACE, $previousContent - 1, null, true);
      if ($tokens[$lastContent]['code'] === T_CLOSE_CURLY_BRACKET) {
        return;
      }
    }
  }
  $comment = rtrim($tokens[$stackPtr]['content']);

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

  // Ignore code example lines.
  if ($this
    ->isInCodeExample($phpcsFile, $stackPtr) === true) {
    return;
  }

  // Verify the indentation of this comment line.
  $this
    ->processIndentation($phpcsFile, $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;
  }

  // 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(array(
    T_COMMENT,
  ), $stackPtr + 1, null, false);
  if ($nextComment !== false && $tokens[$nextComment]['line'] === $tokens[$stackPtr]['line'] + 1 && strpos(trim(substr($tokens[$nextComment]['content'], 2)), '@') !== 0) {
    return;
  }
  $topComment = $stackPtr;
  $lastComment = $stackPtr;
  while (($topComment = $phpcsFile
    ->findPrevious(array(
    T_COMMENT,
  ), $lastComment - 1, null, false)) !== false) {
    if ($tokens[$topComment]['line'] !== $tokens[$lastComment]['line'] - 1) {
      break;
    }
    $lastComment = $topComment;
  }
  $topComment = $lastComment;
  $commentText = '';
  for ($i = $topComment; $i <= $stackPtr; $i++) {
    if ($tokens[$i]['code'] === T_COMMENT) {
      $commentText .= trim(substr($tokens[$i]['content'], 2));
    }
  }
  if ($commentText === '') {
    $error = 'Blank comments are not allowed';
    $fix = $phpcsFile
      ->addFixableError($error, $stackPtr, 'Empty');
    if ($fix === true) {
      $phpcsFile->fixer
        ->replaceToken($stackPtr, '');
    }
    return;
  }
  $words = preg_split('/\\s+/', $commentText);
  if (preg_match('|\\p{Lu}|u', $commentText[0]) === 0 && $commentText[0] !== '@') {

    // Allow special lower cased words that contain non-alpha characters
    // (function references, machine names with underscores etc.).
    $matches = array();
    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');
      if ($fix === true) {
        $newComment = preg_replace("/{$words[0]}/", ucfirst($words[0]), $tokens[$topComment]['content'], 1);
        $phpcsFile->fixer
          ->replaceToken($topComment, $newComment);
      }
    }
  }
  $commentCloser = $commentText[strlen($commentText) - 1];
  $acceptedClosers = array(
    'full-stops' => '.',
    'exclamation marks' => '!',
    'colons' => ':',
    'question marks' => '?',
    '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];
    $matches = array();
    preg_match('/https?:\\/\\/.+/', $lastWord, $matches);
    $isUrl = isset($matches[0]) === true;
    preg_match('/[$a-zA-Z_]+\\([$a-zA-Z_]*\\)/', $lastWord, $matches);
    $isFunction = isset($matches[0]) === true;

    // Also allow closing tags like @endlink or @endcode.
    $isEndTag = $lastWord[0] === '@';
    if ($isUrl === false && $isFunction === false && $isEndTag === false) {
      $error = 'Inline comments must end in %s';
      $ender = '';
      foreach ($acceptedClosers as $closerName => $symbol) {
        $ender .= ' ' . $closerName . ',';
      }
      $ender = trim($ender, ' ,');
      $data = array(
        $ender,
      );
      $fix = $phpcsFile
        ->addFixableError($error, $stackPtr, 'InvalidEndChar', $data);
      if ($fix === true) {
        $newContent = preg_replace('/(\\s+)$/', '.$1', $tokens[$stackPtr]['content']);
        $phpcsFile->fixer
          ->replaceToken($stackPtr, $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;
        }
      }
      else {
        if ($tokens[$i]['line'] > $tokens[$stackPtr]['line'] + 1) {
          break;
        }
      }
    }
    $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;
      }
      $phpcsFile->fixer
        ->beginChangeset();
      for ($i = $stackPtr + 1; $i < $next; $i++) {
        if ($tokens[$i]['line'] === $tokens[$next]['line']) {
          break;
        }
        $phpcsFile->fixer
          ->replaceToken($i, '');
      }
      $phpcsFile->fixer
        ->endChangeset();
    }
  }

  //end if
}