@param \PHP_CodeSniffer\Files\File $phpCsFile
@param int $docBlockStartIndex
@param int $docBlockEndIndex

@return array
766128
protected function readTags(File $phpCsFile, int $docBlockStartIndex, int $docBlockEndIndex): array
    {
        $tokens = $phpCsFile->getTokens();

        $tags = [];

        for ($i = $docBlockStartIndex; $i < $docBlockEndIndex; $i++) {
            if ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) {
                continue;
            }

            $start = $this->getFirstTokenOfLine($tokens, $i);
            $end = $this->getEndIndex($tokens, $i);
            $tagEnd = $this->getTagEndIndex($tokens, $start, $end);

            $tag = [
                'index' => $i,
                'tag' => $tokens[$i]['content'],
                'tagEnd' => $tagEnd,
                'start' => $start,
                'end' => $end,
                'content' => $this->getContent($tokens, $i, $tagEnd),
            ];
            $tags[] = $tag;
            $i = $end;
        }

        return $tags;
    }
231091
public function findLastOnLine(File $phpcsFile, $start)
    {
        $tokens = $phpcsFile->getTokens();

        for ($i = $start; $i <= $phpcsFile->numTokens; $i++) {
            if ($tokens[$i]['line'] === $tokens[$start]['line']) {
                continue;
            }

            return ($i - 1);
        }

        return false;
    }
1218856
protected function addAnnotationLine(File $phpCsFile, array $exception, int $docBlockEndIndex): void
    {
        $tokens = $phpCsFile->getTokens();

        $docBlockStartIndex = $tokens[$docBlockEndIndex]['comment_opener'];

        $throwAnnotationIndex = $this->getThrowAnnotationIndex($tokens, $docBlockStartIndex);
        if (!$throwAnnotationIndex) {
            throw new Exception('Should not happen');
        }

        $phpCsFile->fixer->beginChangeset();

        $phpCsFile->fixer->addNewlineBefore($throwAnnotationIndex);
        $phpCsFile->fixer->addContentBefore($throwAnnotationIndex, '     * @throws ' . $exception['fullClass']);

        $phpCsFile->fixer->endChangeset();
    }
822100
protected function parseImplements(File $phpcsFile, int $implementsStartIndex): array
    {
        $implementsEndIndex = $phpcsFile->findNext(T_OPEN_CURLY_BRACKET, $implementsStartIndex + 1);

        return $this->parse($phpcsFile, $implementsStartIndex, $implementsEndIndex);
    }
391402
protected function processCategory(PHP_CodeSniffer_File $phpcsFile, array $tags)
    {
        $tokens = $phpcsFile->getTokens();

        foreach ($tags as $tag)
        {
            if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING)
            {
                // No content.
                continue;
            }

            $content = $tokens[($tag + 2)]['content'];

            if (PHP_CodeSniffer::isUnderscoreName($content) !== true)
            {
                $newContent = str_replace(' ', '_', $content);
                $nameBits   = explode('_', $newContent);
                $firstBit   = array_shift($nameBits);
                $newName    = ucfirst($firstBit) . '_';

                foreach ($nameBits as $bit)
                {
                    if ($bit !== '')
                    {
                        $newName .= ucfirst($bit) . '_';
                    }
                }

                $error     = 'Category name "%s" is not valid; consider "%s" instead';
                $validName = trim($newName, '_');
                $data      = [
                              $content,
                              $validName,
                             ];
                $phpcsFile->addError($error, $tag, 'InvalidCategory', $data);
            }
        }//end foreach
    }
950546
private function processNewLine(PHP_CodeSniffer_File $phpcsFile, $commentStart)
	{
		$tokens = $phpcsFile->getTokens();

		$prevTag = array();
		$firstTagLine = array('line' => -1);
		foreach ($tokens[$commentStart]['comment_tags'] as $tagIndex) {
			$tag = $tokens[$tagIndex];

			if (!empty($prevTag)) {
				$lineDiff = $tag['line'] - $prevTag['line'];

				// New tag type? And they're not seperated by one empty line? That's our error
				if ($prevTag['type'] != $tag['content']) {
					$lineDiffShouldBe = 2;

					if ($prevTag['type'] == '@param') {
						$nextIndex = $phpcsFile->findNext(
							array(T_DOC_COMMENT_TAG, T_DOC_COMMENT_STRING),
							$prevTag['index'] + 1
						);

						while ($tokens[$nextIndex]['code'] != T_DOC_COMMENT_TAG) {
							$lineDiffShouldBe = $lineDiffShouldBe + $tokens[$nextIndex]['line'] - $prevTag['line'];

							$nextIndex = $phpcsFile->findNext(
								array(T_DOC_COMMENT_TAG, T_DOC_COMMENT_STRING),
								$nextIndex + 1
							);
						}
					}

					if ($lineDiff != $lineDiffShouldBe) {
						$error = 'There should be exactly one new line between different tag types';
						$phpcsFile->addError($error, $tagIndex, 'MissingNewLine');
					}
				}
			}

			if ($firstTagLine['line'] == -1 || $tag['line'] < $firstTagLine['line']) {
				$firstTagLine = array(
					'line' => $tag['line'],
					'index' => $tagIndex
				);
			}

			$prevTag = array(
				'type' => $tag['content'],
				'line' => $tag['line'],
				'index' => $tagIndex
			);
		}

		// No tag? Only comment then, nothing to do
		if ($firstTagLine['line'] == -1) {
			return;
		}

		$internalFirstLine = $firstTagLine['line'] - $tokens[$commentStart]['line'];

		// The first tag is on the first comment line so we don't need to check other things
		if ($internalFirstLine == 1) {
			return;
		}

		$previousStringIndex = $phpcsFile->findPrevious(T_DOC_COMMENT_STRING, $firstTagLine['index']);

		// The comment is somewhere but not two lines above the firsttag
		if ($tokens[$previousStringIndex]['line'] != $firstTagLine['line'] - 2) {
			$error = 'There should be exactly one new line between different tag types';
			$phpcsFile->addError($error, $tagIndex, 'MissingNewLine');
		}
	}
944193
public function process(File $phpcsFile, $stackPtr)
    {
        $tokens = $phpcsFile->getTokens();

        // If its an inline comment (//) return.
        if (substr($tokens[$stackPtr]['content'], 0, 2) !== '/*') {
            return;
        }

        // If this is a function/class/interface doc block comment, skip it.
        // We are only interested in inline doc block comments.
        if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT) {
            $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);

            $ignore    = array(
                          T_CLASS,
                          T_INTERFACE,
                          T_FUNCTION,
                          T_PUBLIC,
                          T_PRIVATE,
                          T_PROTECTED,
                          T_STATIC,
                          T_ABSTRACT,
                          T_CONST,
                         );

            if (in_array($tokens[$nextToken]['code'], $ignore) === true) {
                return;
            }

            $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
            if ($tokens[$prevToken]['code'] === T_OPEN_TAG) {
                return;
            }
        }

        $commentLines = array($stackPtr);
        $nextComment  = $stackPtr;
        $lastLine     = $tokens[$stackPtr]['line'];

        // Construct the comment into an array.
        while (($nextComment = $phpcsFile->findNext($tokens[$stackPtr]['code'], ($nextComment + 1), null, false)) !== false) {
            if (($tokens[$nextComment]['line'] - 1) !== $lastLine) {
                // Not part of the block.
                break;
            }

            $lastLine       = $tokens[$nextComment]['line'];
            $commentLines[] = $nextComment;
        }

        // Small Comments
        if (count($commentLines) <= 2) {
            if (count($commentLines) === 1 && $tokens[$stackPtr]['content'] !== '/**#@-*/') {
                if (strpos($tokens[$stackPtr]['content'], '{{{') !== false ||
                    strpos($tokens[$stackPtr]['content'], '}}}') !== false) {
                    $error = 'No editor-specific turds allowed.';
                    $phpcsFile->addError($error, $stackPtr, 'EditorSpecificIndicators');
                } else {
                    $error = 'Single line block comment not allowed; use inline ("// text") comment instead';
                    $phpcsFile->addError($error, $stackPtr, 'NoSingleLineBlockComment');
                }

                return;
            }

            if (isset($commentLines[1]) && isset($tokens[$commentLines[1]])) {
                if (trim($tokens[$commentLines[1]]['content']) === '*/') {
                    if (trim($tokens[$stackPtr]['content']) === '/*') {
                        $error = 'Empty block comment not allowed';
                        $phpcsFile->addError($error, $stackPtr, 'NoEmptyBlockComments');

                        return;
                    }
                }
            }
        }

        $content = trim($tokens[$stackPtr]['content']);
        if ($content !== '/*' && $content !== '/**' && $content !== '/**#@+' && $content !== '/**#@-*/') {
            $error = 'Block comment text must start on a new line';
            $phpcsFile->addError($error, $stackPtr, 'BlockQuoteOwnLine');

            return;
        }
    }
232318
protected function assertCorrectDocBlockParts(File $phpCsFile, int $classNameIndex, array $parts, string $appendix): void
    {
        $result = [];
        foreach ($parts as $key => $part) {
            if ($part !== 'self') {
                continue;
            }

            $parts[$key] = '$this';
            $result[$part] = '$this';
        }

        if (!$result) {
            return;
        }

        $message = [];
        foreach ($result as $part => $useStatement) {
            $message[] = $part . ' => ' . $useStatement;
        }

        $fix = $phpCsFile->addFixableError(implode(', ', $message), $classNameIndex, 'SelfVsThis');
        if ($fix) {
            $newContent = implode('|', $parts);
            $phpCsFile->fixer->replaceToken($classNameIndex, $newContent . $appendix);
        }
    }
455605
protected function isApiMethod(File $phpCsFile, int $docBlockStartIndex): bool
    {
        $tokens = $phpCsFile->getTokens();

        foreach ($tokens[$docBlockStartIndex]['comment_tags'] as $index) {
            if ($tokens[$index]['content'] === '@api') {
                return true;
            }
        }

        return false;
    }
830333
protected function getDocBlockReturnTypesToken(File $phpCsFile, int $stackPointer): array
    {
        $tokens = $phpCsFile->getTokens();

        $docBlockStartIndex = DocCommentHelper::findDocCommentOpenToken($phpCsFile, $stackPointer);
        $docBlockEndIndex = $this->findRelatedDocBlock($phpCsFile, $stackPointer);

        for ($i = $docBlockEndIndex; $i >= $docBlockStartIndex; $i--) {
            if ($tokens[$i]['content'] !== '@return') {
                continue;
            }

            $returnTypesTokenIndex = $phpCsFile->findNext(
                [T_DOC_COMMENT_WHITESPACE],
                $i + 1,
                null,
                true
            );

            return [
                'index' => $returnTypesTokenIndex,
                'token' => $tokens[$returnTypesTokenIndex],
            ];
        }
    }
332659
protected function extractExceptionAnnotations(File $phpCsFile, int $docBlockStartIndex): array
    {
        $tokens = $phpCsFile->getTokens();

        $throwTags = [];
        foreach ($tokens[$docBlockStartIndex]['comment_tags'] as $index) {
            if ($tokens[$index]['content'] !== '@throws') {
                continue;
            }

            if ($tokens[($index + 2)]['code'] !== T_DOC_COMMENT_STRING) {
                continue;
            }

            $fullClass = $tokens[($index + 2)]['content'];
            $space = strpos($fullClass, ' ');
            if ($space !== false) {
                $fullClass = substr($fullClass, 0, $space);
            }

            $class = $fullClass;
            $lastSeparator = strrpos($class, '\\');
            if ($lastSeparator !== false) {
                $class = substr($class, $lastSeparator + 1);
            }

            $throwTags[] = [
                'index' => $index,
                'fullClass' => $fullClass,
                'class' => $class,
            ];
        }

        return $throwTags;
    }
257081
protected function isInheritDoc(File $phpcsFile, $commentStart)
    {
        $tokens = $phpcsFile->getTokens();

        $commentEnd  = $tokens[$commentStart]['comment_closer'];
        $commentText = $phpcsFile->getTokensAsString($commentStart, ($commentEnd - $commentStart + 1));

        return stripos($commentText, '{@inheritdoc}') !== false;
    }
381417
protected function checkExistingDocBlock(File $phpCsFile, int $docBlockEndPosition): void
    {
        if ($this->hasCorrectContent($phpCsFile, $docBlockEndPosition)) {
            return;
        }

        $fix = $phpCsFile->addFixableError('Constants interface doc block outdated.', $docBlockEndPosition, 'DocBlockOutdated');
        if (!$fix) {
            return;
        }

        $tokens = $phpCsFile->getTokens();
        $docBlockStartPosition = $tokens[$docBlockEndPosition]['comment_opener'];

        $phpCsFile->fixer->beginChangeset();
        for ($i = $docBlockStartPosition; $i <= $docBlockEndPosition; $i++) {
            $phpCsFile->fixer->replaceToken($i, '');
        }

        $docBlockStartPosition--;
        $this->insertDocBlock($phpCsFile, $docBlockStartPosition);
        $phpCsFile->fixer->endChangeset();
    }
767891
protected function checkBeginningOfDocBlock(File $phpCsFile, int $docBlockStartIndex, int $nextIndex): void
    {
        $tokens = $phpCsFile->getTokens();

        $diff = $tokens[$nextIndex]['line'] - $tokens[$docBlockStartIndex]['line'];
        if ($diff < 2) {
            return;
        }

        $fix = $phpCsFile->addFixableError('Expected no extra blank line before tags, got ' . ($diff - 1), $nextIndex, 'NoExtraNewlineBeforeTags');
        if ($fix) {
            return;
        }

        $phpCsFile->fixer->beginChangeset();

        for ($i = $docBlockStartIndex; $i < $nextIndex; $i++) {
            if ($tokens[$i]['line'] <= $tokens[$docBlockStartIndex]['line'] || $tokens[$i]['line'] >= $tokens[$nextIndex]['line']) {
                continue;
            }
            $phpCsFile->fixer->replaceToken($i, '');
        }

        $phpCsFile->fixer->endChangeset();
    }
424516
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
    {
        $this->currentFile = $phpcsFile;

        $tokens    = $phpcsFile->getTokens();
        $type      = strtolower($tokens[$stackPtr]['content']);
        $errorData = array($type);

        $find   = PHP_CodeSniffer_Tokens::$methodPrefixes;
        $find[] = T_WHITESPACE;

        $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);
        if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG
            && $tokens[$commentEnd]['code'] !== T_COMMENT
        ) {
            $phpcsFile->addError('Missing class doc comment', $stackPtr, 'Missing');
            $phpcsFile->recordMetric($stackPtr, 'Class has doc comment', 'no');
            return;
        } else {
            $phpcsFile->recordMetric($stackPtr, 'Class has doc comment', 'yes');
        }

        // Try and determine if this is a file comment instead of a class comment.
        // We assume that if this is the first comment after the open PHP tag, then
        // it is most likely a file comment instead of a class comment.
        if ($tokens[$commentEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) {
            $start = ($tokens[$commentEnd]['comment_opener'] - 1);
        } else {
            $start = $phpcsFile->findPrevious(T_COMMENT, ($commentEnd - 1), null, true);
        }

        if ($tokens[$commentEnd]['code'] === T_COMMENT) {
            $phpcsFile->addError('You must use "/**" style comments for a class comment', $stackPtr, 'WrongStyle');
            return;
        }

        // Check each tag.
        $this->processTags($phpcsFile, $stackPtr, $tokens[$commentEnd]['comment_opener']);

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

		// Find the next non whitespace token.
		$commentStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);

		// Allow declare() statements at the top of the file.
		if ($tokens[$commentStart]['code'] === T_DECLARE)
		{
			$semicolon    = $phpcsFile->findNext(T_SEMICOLON, ($commentStart + 1));
			$commentStart = $phpcsFile->findNext(T_WHITESPACE, ($semicolon + 1), null, true);
		}

		// Ignore vim header.
		if ($tokens[$commentStart]['code'] === T_COMMENT)
		{
			if (strstr($tokens[$commentStart]['content'], 'vim:') !== false)
			{
				$commentStart = $phpcsFile->findNext(
					T_WHITESPACE,
					($commentStart + 1),
					null,
					true
				);
			}
		}

		$errorToken = ($stackPtr + 1);

		if (isset($tokens[$errorToken]) === false)
		{
			$errorToken--;
		}

		if ($tokens[$commentStart]['code'] === T_CLOSE_TAG)
		{
			// We are only interested if this is the first open tag.
			return ($phpcsFile->numTokens + 1);
		}
		elseif ($tokens[$commentStart]['code'] === T_COMMENT)
		{
			$error = 'You must use "/**" style comments for a file comment';
			$phpcsFile->addError($error, $errorToken, 'WrongStyle');
			$phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes');

			return ($phpcsFile->numTokens + 1);
		}
		elseif ($commentStart === false
			|| $tokens[$commentStart]['code'] !== T_DOC_COMMENT_OPEN_TAG
		)
		{
			$phpcsFile->addError('Missing file doc comment', $errorToken, 'Missing');
			$phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'no');

			return ($phpcsFile->numTokens + 1);
		}
		else
		{
			$phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes');
		}

		// Check each tag.
		$this->processTags($phpcsFile, $stackPtr, $commentStart);

		// Ignore the rest of the file.
		return ($phpcsFile->numTokens + 1);
	}
828080
protected function getNamespaceAsString(File $phpCsFile, $startIndex, $endIndex) {
		$tokens = $phpCsFile->getTokens();

		$namespace = '';
		for ($i = $startIndex; $i <= $endIndex; $i++) {
			$namespace .= $tokens[$i]['content'];
		}

		return trim($namespace);
	}