You are here

protected function FunctionCommentSniff::processReturn in Coder 8.2

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

Process the return comment of this function comment.

Parameters

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

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

int $commentStart The position in the stack where the comment started.:

Return value

void

1 call to FunctionCommentSniff::processReturn()
FunctionCommentSniff::process in coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php
Processes this test, when one of its tokens is encountered.

File

coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php, line 178

Class

FunctionCommentSniff
Parses and verifies the doc comments for functions. Largely copied from PHP_CodeSniffer\Standards\Squiz\Sniffs\Commenting\FunctionCommentSniff.

Namespace

Drupal\Sniffs\Commenting

Code

protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) {
  $tokens = $phpcsFile
    ->getTokens();

  // Skip constructor and destructor.
  $className = '';
  foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condition) {
    if ($condition === T_CLASS || $condition === T_INTERFACE) {
      $className = $phpcsFile
        ->getDeclarationName($condPtr);
      $className = strtolower(ltrim($className, '_'));
    }
  }
  $methodName = $phpcsFile
    ->getDeclarationName($stackPtr);
  $isSpecialMethod = $methodName === '__construct' || $methodName === '__destruct';
  $methodName = strtolower(ltrim($methodName, '_'));
  $return = null;
  foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) {
    if ($tokens[$tag]['content'] === '@return') {
      if ($return !== null) {
        $error = 'Only 1 @return tag is allowed in a function comment';
        $phpcsFile
          ->addError($error, $tag, 'DuplicateReturn');
        return;
      }
      $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];
      }
      else {
        $end = $tokens[$commentStart]['comment_closer'];
      }
    }
  }
  $type = null;
  if ($isSpecialMethod === false && $methodName !== $className) {
    if ($return !== null) {
      $type = trim($tokens[$return + 2]['content']);
      if (empty($type) === true || $tokens[$return + 2]['code'] !== T_DOC_COMMENT_STRING) {
        $error = 'Return type missing for @return tag in function comment';
        $phpcsFile
          ->addError($error, $return, 'MissingReturnType');
      }
      else {
        if (strpos($type, ' ') === false) {

          // Check return type (can be multiple, separated by '|').
          $typeNames = explode('|', $type);
          $suggestedNames = array();
          $hasNull = false;
          $hasMultiple = false;
          if (count($typeNames) > 0) {
            $hasMultiple = true;
          }
          foreach ($typeNames as $i => $typeName) {
            if (strtolower($typeName) === 'null') {
              $hasNull = true;
            }
            $suggestedName = static::suggestType($typeName);
            if (in_array($suggestedName, $suggestedNames) === false) {
              $suggestedNames[] = $suggestedName;
            }
          }
          $suggestedType = implode('|', $suggestedNames);
          if ($type !== $suggestedType) {
            $error = 'Expected "%s" but found "%s" for function return type';
            $data = array(
              $suggestedType,
              $type,
            );
            $fix = $phpcsFile
              ->addFixableError($error, $return, 'InvalidReturn', $data);
            if ($fix === true) {
              $content = $suggestedType;
              $phpcsFile->fixer
                ->replaceToken($return + 2, $content);
            }
          }

          //end if
          if ($type === 'void') {
            $error = 'If there is no return value for a function, there must not be a @return tag.';
            $phpcsFile
              ->addError($error, $return, 'VoidReturn');
          }
          else {
            if ($type !== 'mixed') {

              // If return type is not void, there needs to be a return statement
              // somewhere in the function that returns something.
              if (isset($tokens[$stackPtr]['scope_closer']) === true) {
                $endToken = $tokens[$stackPtr]['scope_closer'];
                $foundReturnToken = false;
                $searchStart = $stackPtr;
                $foundNonVoidReturn = false;
                do {
                  $returnToken = $phpcsFile
                    ->findNext(T_RETURN, $searchStart, $endToken);
                  if ($returnToken === false && $foundReturnToken === false) {
                    $error = '@return doc comment specified, but function has no return statement';
                    $phpcsFile
                      ->addError($error, $return, 'InvalidNoReturn');
                  }
                  else {

                    // Check for return token as the last loop after the last return
                    // in the function will enter this else condition
                    // but without the returnToken.
                    if ($returnToken !== false) {
                      $foundReturnToken = true;
                      $semicolon = $phpcsFile
                        ->findNext(T_WHITESPACE, $returnToken + 1, null, true);
                      if ($tokens[$semicolon]['code'] === T_SEMICOLON) {

                        // Void return is allowed if the @return type has null in it.
                        if ($hasNull === false) {
                          $error = 'Function return type is not void, but function is returning void here';
                          $phpcsFile
                            ->addError($error, $returnToken, 'InvalidReturnNotVoid');
                        }
                      }
                      else {
                        $foundNonVoidReturn = true;
                      }

                      //end if
                      $searchStart = $returnToken + 1;
                    }

                    //end if
                  }

                  //end if
                } while ($returnToken !== false);
                if ($foundNonVoidReturn === false && $foundReturnToken === true) {
                  $error = 'Function return type is not void, but function does not have a non-void return statement';
                  $phpcsFile
                    ->addError($error, $return, 'InvalidReturnNotVoid');
                }
              }

              //end if
            }
          }

          //end if
        }
      }

      //end if
      $comment = '';
      for ($i = $return + 3; $i < $end; $i++) {
        if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) {
          $indent = 0;
          if ($tokens[$i - 1]['code'] === T_DOC_COMMENT_WHITESPACE) {
            $indent = strlen($tokens[$i - 1]['content']);
          }
          $comment .= ' ' . $tokens[$i]['content'];
          $commentLines[] = array(
            'comment' => $tokens[$i]['content'],
            'token' => $i,
            'indent' => $indent,
          );
          if ($indent < 3) {
            $error = 'Return comment indentation must be 3 spaces, found %s spaces';
            $fix = $phpcsFile
              ->addFixableError($error, $i, 'ReturnCommentIndentation', array(
              $indent,
            ));
            if ($fix === true) {
              $phpcsFile->fixer
                ->replaceToken($i - 1, '   ');
            }
          }
        }
      }

      //end for

      // The first line of the comment must be indented no more than 3
      // spaces, the following lines can be more so we only check the first
      // line.
      if (empty($commentLines[0]['indent']) === false && $commentLines[0]['indent'] > 3) {
        $error = 'Return comment indentation must be 3 spaces, found %s spaces';
        $fix = $phpcsFile
          ->addFixableError($error, $commentLines[0]['token'] - 1, 'ReturnCommentIndentation', array(
          $commentLines[0]['indent'],
        ));
        if ($fix === true) {
          $phpcsFile->fixer
            ->replaceToken($commentLines[0]['token'] - 1, '   ');
        }
      }
      if ($comment === '' && $type !== '$this' && $type !== 'static') {
        if (strpos($type, ' ') !== false) {
          $error = 'Description for the @return value must be on the next line';
        }
        else {
          $error = 'Description for the @return value is missing';
        }
        $phpcsFile
          ->addError($error, $return, 'MissingReturnComment');
      }
      else {
        if (strpos($type, ' ') !== false) {
          if (preg_match('/^([^\\s]+)[\\s]+(\\$[^\\s]+)[\\s]*$/', $type, $matches) === 1) {
            $error = 'Return type must not contain variable name "%s"';
            $data = array(
              $matches[2],
            );
            $fix = $phpcsFile
              ->addFixableError($error, $return + 2, 'ReturnVarName', $data);
            if ($fix === true) {
              $phpcsFile->fixer
                ->replaceToken($return + 2, $matches[1]);
            }
          }
          else {
            $error = 'Return type "%s" must not contain spaces';
            $data = array(
              $type,
            );
            $phpcsFile
              ->addError($error, $return, 'ReturnTypeSpaces', $data);
          }
        }
      }

      //end if
    }

    //end if
  }
  else {

    // No return tag for constructor and destructor.
    if ($return !== null) {
      $error = '@return tag is not required for constructor and destructor';
      $phpcsFile
        ->addError($error, $return, 'ReturnNotRequired');
    }
  }

  //end if
}