Processes each required or optional tag.

@param int $commentStart Position in the stack where the comment started.
@param int $commentEnd   Position in the stack where the comment ended.

@return void
509589
protected function processTags($commentStart, $commentEnd) {
        $docBlock = (get_class($this) === 'PhOSCo_Sniffs_Commenting_FileCommentSniff') ? 'file' : 'class';
        $foundTags = $this->commentParser->getTagOrders();
        $orderIndex = 0;
        $indentation = array();
        $longestTag = 0;
        $errorPos = 0;

        foreach ($this->tags as $tag => $info) {

            // Required tag missing.
            if ($info['required'] === true && in_array($tag, $foundTags) === false) {
                $error = 'Missing @%s tag in %s comment';
                $data = array($tag, $docBlock,);
                $this->currentFile->addError($error, $commentEnd, 'MissingTag', $data);
                continue;
            }

            // Get the line number for current tag.
            $tagName = ucfirst($tag);
            if ($info['allow_multiple'] === true) {
                $tagName .= 's';
            }

            $getMethod = 'get' . $tagName;
            $tagElement = $this->commentParser->$getMethod();
            if (is_null($tagElement) === true || empty($tagElement) === true) {
                continue;
            }

            $errorPos = $commentStart;
            if (is_array($tagElement) === false) {
                $errorPos = ($commentStart + $tagElement->getLine());
            }

            // Get the tag order.
            $foundIndexes = array_keys($foundTags, $tag);

            if (count($foundIndexes) > 1) {
                // Multiple occurrence not allowed.
                if ($info['allow_multiple'] === false) {
                    $error = 'Only 1 @%s tag is allowed in a %s comment';
                    $data = array($tag, $docBlock,);
                    $this->currentFile->addError($error, $errorPos, 'DuplicateTag', $data);
                } else {
                    // Make sure same tags are grouped together.
                    $i = 0;
                    $count = $foundIndexes[0];
                    foreach ($foundIndexes as $index) {
                        if ($index !== $count) {
                            $errorPosIndex = ($errorPos + $tagElement[$i]->getLine());
                            $error = '@%s tags must be grouped together';
                            $data = array($tag);
                            $this->currentFile->addError($error, $errorPosIndex, 'TagsNotGrouped', $data);
                        }

                        $i++;
                        $count++;
                    }
                }
            }//end if

            // Check tag order.
            if ($foundIndexes[0] > $orderIndex) {
                $orderIndex = $foundIndexes[0];
            } else {
                if (is_array($tagElement) === true && empty($tagElement) === false) {
                    $errorPos += $tagElement[0]->getLine();
                }

                $error = 'The @%s tag is in the wrong order; the tag %s';
                $data = array($tag, $info['order_text'],);
                $this->currentFile->addError($error, $errorPos, 'WrongTagOrder', $data);
            }

            // Store the indentation for checking.
            $len = strlen($tag);
            if ($len > $longestTag) {
                $longestTag = $len;
            }

            if (is_array($tagElement) === true) {
                foreach ($tagElement as $key => $element) {
                    $indentation[] = array('tag' => $tag, 'space' => $this->getIndentation($tag, $element),
                                           'line' => $element->getLine(),);
                }
            } else {
                $indentation[] = array('tag' => $tag, 'space' => $this->getIndentation($tag, $tagElement),);
            }

            $method = 'process' . $tagName;
            if (method_exists($this, $method) === true) {
                // Process each tag if a method is defined.
                call_user_func(array($this, $method), $errorPos);
            } else {
                if (is_array($tagElement) === true) {
                    foreach ($tagElement as $key => $element) {
                        $element->process($this->currentFile, $commentStart, $docBlock);
                    }
                } else {
                    $tagElement->process($this->currentFile, $commentStart, $docBlock);
                }
            }
        }//end foreach

        foreach ($indentation as $indentInfo) {
            if ($indentInfo['space'] !== 0 && $indentInfo['space'] !== ($longestTag + 1)) {
                $expected = (($longestTag - strlen($indentInfo['tag'])) + 1);
                $space = ($indentInfo['space'] - strlen($indentInfo['tag']));
                $error = '@%s tag comment indented incorrectly; expected %s spaces but found %s';
                $data = array($indentInfo['tag'], $expected, $space,);

                $getTagMethod = 'get' . ucfirst($indentInfo['tag']);

                if ($this->tags[$indentInfo['tag']]['allow_multiple'] === true) {
                    $line = $indentInfo['line'];
                } else {
                    $tagElem = $this->commentParser->$getTagMethod();
                    $line = $tagElem->getLine();
                }

                $this->currentFile->addError($error, ($commentStart + $line), 'TagIndent', $data);
            }
        }

    }
119272
protected function fixDocCommentOpenTags(File $phpCsFile, int $startIndex, int $endIndex): void
    {
        $tokens = $phpCsFile->getTokens();

        for ($i = $startIndex + 1; $i < $endIndex; $i++) {
            if ($tokens[$i]['code'] !== T_COMMENT) {
                continue;
            }

            if (!preg_match('|^\/\*\s*@\w+ (.+)|', $tokens[$i]['content'])) {
                continue;
            }

            $fix = $phpCsFile->addFixableError('Inline Doc Block comment should be using `/** ... */`', $i, 'InlineDocBlock');
            if ($fix) {
                $phpCsFile->fixer->beginChangeset();

                $comment = $tokens[$i]['content'];
                $comment = str_replace('/*', '/**', $comment);

                $phpCsFile->fixer->replaceToken($i, $comment);

                $phpCsFile->fixer->endChangeset();
            }
        }
    }
814047
public function setComment(string $comment): void
    {
        $this->desc = '';
        $this->tags = [];
        $this->comment = $comment;

        $this->parseComment($comment);
    }
1072358
protected function processEndTag(Tag $tag)
	{
		$tagName = $tag->getName();

		if (empty($this->cntOpen[$tagName]))
		{
			// This is an end tag with no start tag
			return;
		}

		/**
		* @var array List of tags need to be closed before given tag
		*/
		$closeTags = [];

		// Iterate through all open tags from last to first to find a match for our tag
		$i = count($this->openTags);
		while (--$i >= 0)
		{
			$openTag = $this->openTags[$i];

			if ($tag->canClose($openTag))
			{
				break;
			}

			$closeTags[] = $openTag;
			++$this->currentFixingCost;
		}

		if ($i < 0)
		{
			// Did not find a matching tag
			$this->logger->debug('Skipping end tag with no start tag', ['tag' => $tag]);

			return;
		}

		// Accumulate flags to determine whether whitespace should be trimmed
		$flags = $tag->getFlags();
		foreach ($closeTags as $openTag)
		{
			$flags |= $openTag->getFlags();
		}
		$ignoreWhitespace = (bool) ($flags & self::RULE_IGNORE_WHITESPACE);

		// Only reopen tags if we haven't exceeded our "fixing" budget
		$keepReopening = (bool) ($this->currentFixingCost < $this->maxFixingCost);

		// Iterate over tags that are being closed, output their end tag and collect tags to be
		// reopened
		$reopenTags = [];
		foreach ($closeTags as $openTag)
		{
			$openTagName = $openTag->getName();

			// Test whether this tag should be reopened automatically
			if ($keepReopening)
			{
				if ($openTag->getFlags() & self::RULE_AUTO_REOPEN)
				{
					$reopenTags[] = $openTag;
				}
				else
				{
					$keepReopening = false;
				}
			}

			// Find the earliest position we can close this open tag
			$tagPos = $tag->getPos();
			if ($ignoreWhitespace)
			{
				$tagPos = $this->getMagicEndPos($tagPos);
			}

			// Output an end tag to close this start tag, then update the context
			$endTag = new Tag(Tag::END_TAG, $openTagName, $tagPos, 0);
			$endTag->setFlags($openTag->getFlags());
			$this->outputTag($endTag);
			$this->popContext();
		}

		// Output our tag, moving the cursor past it, then update the context
		$this->outputTag($tag);
		$this->popContext();

		// If our fixing budget allows it, peek at upcoming tags and remove end tags that would
		// close tags that are already being closed now. Also, filter our list of tags being
		// reopened by removing those that would immediately be closed
		if (!empty($closeTags) && $this->currentFixingCost < $this->maxFixingCost)
		{
			/**
			* @var integer Rightmost position of the portion of text to ignore
			*/
			$ignorePos = $this->pos;

			$i = count($this->tagStack);
			while (--$i >= 0 && ++$this->currentFixingCost < $this->maxFixingCost)
			{
				$upcomingTag = $this->tagStack[$i];

				// Test whether the upcoming tag is positioned at current "ignore" position and it's
				// strictly an end tag (not a start tag or a self-closing tag)
				if ($upcomingTag->getPos() > $ignorePos
				 || $upcomingTag->isStartTag())
				{
					break;
				}

				// Test whether this tag would close any of the tags we're about to reopen
				$j = count($closeTags);

				while (--$j >= 0 && ++$this->currentFixingCost < $this->maxFixingCost)
				{
					if ($upcomingTag->canClose($closeTags[$j]))
					{
						// Remove the tag from the lists and reset the keys
						array_splice($closeTags, $j, 1);

						if (isset($reopenTags[$j]))
						{
							array_splice($reopenTags, $j, 1);
						}

						// Extend the ignored text to cover this tag
						$ignorePos = max(
							$ignorePos,
							$upcomingTag->getPos() + $upcomingTag->getLen()
						);

						break;
					}
				}
			}

			if ($ignorePos > $this->pos)
			{
				/**
				* @todo have a method that takes (pos,len) rather than a Tag
				*/
				$this->outputIgnoreTag(new Tag(Tag::SELF_CLOSING_TAG, 'i', $this->pos, $ignorePos - $this->pos));
			}
		}

		// Re-add tags that need to be reopened, at current cursor position
		foreach ($reopenTags as $startTag)
		{
			$newTag = $this->addCopyTag($startTag, $this->pos, 0);

			// Re-pair the new tag
			$endTag = $startTag->getEndTag();
			if ($endTag)
			{
				$newTag->pairWith($endTag);
			}
		}
	}
1266938
protected function processTags($comment)
    {
        $tags = preg_split('/^\s*@/m',$comment,-1,PREG_SPLIT_NO_EMPTY);
        foreach($tags as $tag){
            $segs   = preg_split('/\s+/',trim($tag),2);
            $tagName= $segs[0];
            $param  = isset($segs[1]) ? trim($segs[1]) : '';
            $this->tags[$tagName][] = $param;
        }
    }
551051
protected function _commentNode()
    {
        $this->_codeNode();
        $token = $this->_stream->current(true);
        $this->_states['body'] = $token[1];
        $node = new NodeDef($this->_states['body'], 'comment');
        return $this->_contextualize($node);
    }
496883
public void onComment (@Nullable final String sComment)
  {
    if (StringHelper.hasText (sComment))
    {
      if (isThrowExceptionOnNestedComments ())
        if (sComment.contains (COMMENT_START) || sComment.contains (COMMENT_END))
          throw new IllegalArgumentException ("XML comment contains nested XML comment: " + sComment);

      _append (COMMENT_START)._append (sComment)._append (COMMENT_END);
    }
  }
1147782
protected function processTagContent(?string $tagContent = null): void
    {
        $currentNamespace = NamespaceHelper::findCurrentNamespaceName($this->file, $this->stackPos);

        if (((int) $this->file->findPrevious([T_NAMESPACE], $this->stackPos) > 0) && $currentNamespace &&
            $tagContent !== $currentNamespace) {
            $isFixing = $this->file->addFixableError(
                static::MESSAGE_CODE_TAG_WRONG_PACKAGE,
                $this->stackPos,
                static::CODE_TAG_WRONG_PACKAGE,
                [
                    $currentNamespace
                ]
            );

            if ($isFixing) {
                $this->fixWrongPackage($currentNamespace);
            }
        }
    }
263121
private function getCommentDescriptionEndPointer(): int
    {
        $descriptionStartPtr = $this->getCommentDescriptionStartPointer();

        $commentStartToken = $this->docHelper->getBlockStartToken();
        $commentEndPtr = $this->docHelper->getBlockEndPosition();

        //If no tags found, possible end of search is the starting tag of the doc comment.
        if (count($commentStartToken['comment_tags']) === 0) {
            return $this->file->findPrevious(
                [T_DOC_COMMENT_STRING],
                $commentEndPtr - 1,
                $descriptionStartPtr
            );
        }

        //else its the pointer of the first comment tag found.
        $firstTagPtr = array_shift($commentStartToken['comment_tags']);

        return $this->file->findPrevious(
            [T_DOC_COMMENT_STRING],
            $firstTagPtr - 1,
            $descriptionStartPtr
        );
    }
698860
public void addComment(String tag, String comment) {
        String nt = normaliseTag(tag);
        if(! comments.containsKey(nt)) {
            comments.put(nt, new ArrayList<String>());
        }
        comments.get(nt).add(comment);
    }
545160
private function get_comment($start_pos)
    {
        // this is will have very poor perf, but will work for now.
        $comment = '';
        $delimiter = '>';
        $matched = false;

        $this->pos = $start_pos;
        $input_char = $this->input[$this->pos];
        $this->pos++;

        while ($this->pos <= $this->input_length) {
            $comment .= $input_char;

            // only need to check for the delimiter if the last chars match
            if ($comment[strlen($comment) - 1] === $delimiter[strlen($delimiter) - 1] &&
                strpos($comment, $delimiter) !== false) {
                break;
            }

            // only need to search for custom delimiter for the first few characters
            if (!$matched && strlen($comment) < 10) {
                if (strpos($comment, '<![if') === 0) { //peek for <![if conditional comment
                    $delimiter = '<![endif]>';
                    $matched = true;
                } else if (strpos($comment, '<![cdata[') === 0) { //if it's a <[cdata[ comment...
                    $delimiter = ']]>';
                    $matched = true;
                } else if (strpos($comment, '<![') === 0) { // some other ![ comment? ...
                    $delimiter = ']>';
                    $matched = true;
                } else if (strpos($comment, '<!--') === 0) { // <!-- comment ...
                    $delimiter = '-->';
                    $matched = true;
                }
            }

            $input_char = $this->input[$this->pos];
            $this->pos++;
        }

        return $comment;
    }
1048863
private function getCommentDescriptionStartPointer(): int
    {
        $commentStartToken = $this->docHelper->getBlockStartToken();
        $commentEndPtr = $this->docHelper->getBlockEndPosition();

        $summaryPtr = $this->summaryHelper->getCommentSummaryPosition();

        //If no tags the possible end of search is the closing tag of the doc comment.
        if (count($commentStartToken['comment_tags']) === 0) {
            return $this->file->findNext(
                [T_DOC_COMMENT_STRING],
                $summaryPtr + 1,
                $commentEndPtr
            );
        }

        //else its the pointer of the first comment tag found.
        $firstTagPtr = array_shift($commentStartToken['comment_tags']);

        return $this->file->findNext(
            [T_DOC_COMMENT_STRING],
            $summaryPtr + 1,
            $firstTagPtr - 1
        );
    }
206985
protected function validateInheritdoc(File $phpcsFile, $stackPtr, $commentStart, $commentEnd)
    {
        $commentString = $phpcsFile->getTokensAsString($commentStart, $commentEnd - $commentStart + 1);

        if (\preg_match('/\@inheritdoc/', $commentString)) {
            // Ignore anonymous class for now
            $tokens = $phpcsFile->getTokens();
            if (\in_array(T_ANON_CLASS, $tokens[$commentStart]['conditions'] ?? [], true)) {
                return true;
            }

            $classes = $this->getClassParentsAndInterfaces();

            if (false !== $classes) {
                $method = $phpcsFile->getDeclarationName($stackPtr);
                foreach ($classes as $class) {
                    if (\method_exists($class, $method)) {
                        return true;
                    }
                }
                $error = 'No override method found for {@inheritdoc} annotation';
                $phpcsFile->addError($error, $commentStart, 'InvalidInheritdoc');
            } else {
                return true;
            }
        }

        return false;
    }
227794
private function validateUCFirstDocComment(int $position, array $token): void
    {
        $commentText = $token['content'];

        if (ucfirst($commentText) !== $commentText && $this->isPrevLineEmpty($position)) {
            $isFixing = $this->file->addFixableWarning(
                self::MESSAGE_DOC_COMMENT_UC_FIRST,
                $position,
                static::CODE_DOC_COMMENT_UC_FIRST
            );

            if ($isFixing) {
                $this->fixDocCommentUcFirst($position, $token);
            }
        }
    }
96366
public function init() {

		parent::init();

		$commentProperties = CommentProperties::getInstance();

		$this->limit = $commentProperties->getCommentsLimit();
	}
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');
		}
	}
985468
public function _setTokenStart($delimiter)
    {
        if (
            !($delimiter instanceof Stringable) &&
            !is_string($delimiter) &&
            !is_null($delimiter)
        ) {
            throw $this->_createInvalidArgumentException($this->__('Invalid token start delimiter'), null, null, $delimiter);
        }

        $this->tokenStart = $delimiter;
    }