Parse annotations out of a doc comment
@param  str   $comment Doc comment to parse
@return str[]
776905
private function parseDocComment($comment)
    {
        $data = array();
        preg_match_all('/^\s*\*[*\s]*(@.+)$/m', $comment, $items);
        if ($items && isset($items[1])) {
            foreach ($items[1] as $item) {
                $parts = explode(' ', $item);
                $key = array_shift($parts);
                $data[$key][] = trim(implode(' ', $parts));
            }
        }

        return $data;
    }
1282098
public static function parseComments($comments) {
        $ret = [];
        //trim(str_replace(array('/', '*', '**'), '', substr($comments, 0, strpos($comments, '@'))));
        $comments = str_replace(array('/*', '*', '**'), '', $comments);
        $array_comments = explode("\n", $comments);
        foreach ($array_comments as $k => $line) {
            $line = trim($line);
            if (self::startsWith($line, "@")) {
                $params = explode(" ", $line);
                $c = trim(str_replace("@", "", array_shift($params)));
                $ret[$c] = $params;
            }
        }
        return $ret;
    }
554617
public function parseComment($comment)
    {
        preg_match_all('/@([a-zA-Z]+) *(.*)\n/', $comment, $matches, PREG_SET_ORDER);
        $tags = new TagCollection();

        foreach ($matches as $match) {
            array_shift($match);
            list($tag, $body) = $match;

            $tags->addTag($this->getTagEntity($tag, $body));
        }

        return $tags;
    }
1108931
public function setText($comment)
    {
        list(,$short, $long) = $this->splitDocBlock($comment);
        $this->short_description = $short;
        $this->long_description = new DocBlock\Description($long, $this);
        return $this;
    }
698690
public function getElementsFromComment( $comment, $unique = TRUE )
	{
		$content = $this->getTaggedComment( $comment );
		if( !isset( $content ) )
			return NULL;

		$list	= array();
		$content = explode( "\n", $content );
		foreach( $content as $row )
		{
			if( preg_match( '/\s*@(\S+)?\s+(.*)/', $row, $out ) )
			{
				if( $unique )
					$list[$out[1]] = $out[2];
				else
					$list[$out[1]][] = $out[2];
			}
		}
		return $list;
	}
1181151
public function fromComment($comment, $context = null)
    {
        if ($context === null) {
            $context = new Context(['comment' => $comment]);
        } else {
            $context->comment = $comment;
        }
        try {
            self::$context = $context;
            if ($context->is('annotations') === false) {
                $context->annotations = [];
            }
            $comment = preg_replace_callback(
                '/^[\t ]*\*[\t ]+/m',
                function ($match) {
                    // Replace leading tabs with spaces.
                    // Workaround for http://www.doctrine-project.org/jira/browse/DCOM-255
                    return str_replace("\t", ' ', $match[0]);
                },
                $comment
            );
            $annotations = $this->docParser->parse($comment, $context);
            self::$context = null;
            return $annotations;
        } catch (Exception $e) {
            self::$context = null;
            if (preg_match('/^(.+) at position ([0-9]+) in ' . preg_quote((string)$context, '/') . '\.$/', $e->getMessage(), $matches)) {
                $errorMessage = $matches[1];
                $errorPos = (int)$matches[2];
                $atPos = strpos($comment, '@');
                $context->line += substr_count($comment, "\n", 0, $atPos + $errorPos);
                $lines = explode("\n", substr($comment, $atPos, $errorPos));
                $context->character = strlen(array_pop($lines)) + 1; // position starts at 0 character starts at 1
                Logger::warning(new Exception($errorMessage . ' in ' . $context, $e->getCode(), $e));
            } else {
                Logger::warning($e);
            }
            return [];
        }
    }
1050744
public static function create($comment) {
    $comment = trim($comment);
    $lines = array_map('trim', explode("\n", $comment));
    $text = "/**\n";
    foreach ($lines as $i => $line) {
      $text .= ' * ' . $line . "\n";
    }
    $text .= ' */';
    return new DocCommentNode(T_DOC_COMMENT, $text);
  }
701587
public static function ltrim ( $docComment ) {
        $regexp = array(
                        '|^[\s\t]*(/\*\*)|um' => '\\1',
                        '|^[\s\t]*([ ]\*/)|um' => '\\1',
                        '|^[\s\t]*([ ]\*[ ]*)|um' => '\\1',
                       );
        $patterns = array_keys($regexp);
        $replacements = array_values($regexp);
        $docComment = preg_replace($patterns, $replacements, $docComment);
        $docComment = trim($docComment);
        return $docComment;
    }
1141869
protected static function parseAnnotations($docblock)
    {
        $annotations = array();
        // Strip away the docblock header and footer
        // to ease parsing of one line annotations
        $docblock = substr($docblock, 3, -2);

        $re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
        if (preg_match_all($re, $docblock, $matches)) {
            $numMatches = count($matches[0]);

            for ($i = 0; $i < $numMatches; ++$i) {
                $annotations[$matches['name'][$i]][] = $matches['value'][$i];
            }
        }

        return $annotations;
    }
30192
public static function getTags(string $comment, array $ignored = ['param', 'return']): array
    {
        $comment = str_replace("\r\n", "\n", trim($comment, "/ \n"));
        $comment = "@Description \n" . str_replace("\r", '',
                trim(preg_replace('/^\s*\**( |\t)?/m', '', $comment))
            );

        $tags = [];
        $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);

        foreach ($parts as $part) {
            if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
                $name = $matches[1];
                if (\in_array($name, $ignored, true)) {
                    continue;
                }

                if (!isset($tags[$name])) {
                    $tags[$name] = trim($matches[2]);
                } elseif (\is_array($tags[$name])) {
                    $tags[$name][] = trim($matches[2]);
                } else {
                    $tags[$name] = [$tags[$name], trim($matches[2])];
                }
            }
        }

        return $tags;
    }
1374950
public function extractDoctrineTypeFromComment($comment, $currentType)
    {
        if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match)) {
            return $match[1];
        }

        return $currentType;
    }
933725
private function loadArgumentTokenOfTag(): array
    {
        // Give me the other tags of this doc block before this one.
        $tagPosBeforeThis = TokenHelper::findNextAll(
            $this->file->getBaseFile(),
            $this->register(),
            $this->file->findPrevious([T_DOC_COMMENT_OPEN_TAG], $this->stackPos),
            $this->stackPos - 1
        );

        $tagPosBeforeThis = array_filter($tagPosBeforeThis, function (int $position) {
            return $this->tokens[$position]['content'] === '@param';
        });

        return (array_values($this->varTokens)[count($tagPosBeforeThis)]) ?? [];
    }
1275977
protected function extractParamAnnotations(string $docComment): array
    {
        $pattern = '/@param(?:\s+([^$"]\S+))?(?:\s+\$(\w+))?(?:\s+"([^"]++)")?/';

        if (!(bool)preg_match_all($pattern, $docComment, $matches, PREG_SET_ORDER)) {
            return [];
        }

        $annotations = [];

        foreach ($matches as $index => $match) {
            $annotations[$index] = isset($match[3]) && $match[3] !== '' ? $match[3] : null;
        }

        return $annotations;
    }
682207
protected function _parse($comment) {
		$summary = null;
		$description = null;
		$tags = array();

		$comment = trim(preg_replace('/^(\s*\/\*\*|\s*\*{1,2}\/|\s*\* ?)/m', '', $comment));
		$comment = str_replace("\r\n", "\n", $comment);

		if ($items = preg_split('/\n@/ms', $comment, 2)) {
			list($summary, $tags) = $items + array('', '');
			$this->_tags = $tags ? $this->_parseTags("@{$tags}") : array();
		}
		if (strpos($summary, "\n\n")) {
			list($summary, $description) = explode("\n\n", $summary, 2);
		}
		$this->_summary = $this->_clean($summary);
		$this->_description = $this->_clean($description);
	}
930885
public static function parseSimpleAnnotationValue($annotation, $docComment)
    {
        $matches = [];
        preg_match("#@$annotation\\s+([^\\s]+)#", $docComment, $matches);
        return !empty($matches) ? $matches[1] : null;
    }
202622
private function _processComment($token) {
		// Count the lines of comment
		if ($token->id === T_COMMENT) {
			$this->_ncssTotalSingleComment ++;
			$this->_ncssFileSingleComment ++;
		} else if ($token->id === T_ML_COMMENT) {
			$this->_ncssTotalMultiComment ++;
			$this->_ncssFileMultiComment ++;
		} else if ($token->id === T_DOC_COMMENT) {
			$this->_ncssTotalPhpdoc ++;
			$this->_ncssFilePhpdoc ++;
			$this->_ncssTotalLinesPhpdoc ++;
			$this->_ncssFileLinesPhpdoc ++;
			if ($this->_isFileDocComment()) {
				$this->_processAnnotation(T_FILE, $token->text);
			}
		}

		// Count the @params, @returns and @throw
		if (stripos($token->text, '/**') !== false) {
			// Reset the count of elements if it's a new docblock
			$this->statementStack->getCurrentStackItem()->docblocNbParams = 0;
			$this->statementStack->getCurrentStackItem()->docblocNbReturns = 0;
			$this->statementStack->getCurrentStackItem()->docblocNbThrows = 0;
		}
		if (stripos($token->text, '@param') !== false) {
			$this->statementStack->getCurrentStackItem()->docblocNbParams ++;
		}
		if (stripos($token->text, '@return') !== false) {
			$this->statementStack->getCurrentStackItem()->docblocNbReturns ++;
		}
		if (stripos($token->text, '@throw') !== false) {
			$this->statementStack->getCurrentStackItem()->docblocNbThrows ++;
		}
		if (stripos($token->text, '@inheritdoc') !== false) {
			$this->statementStack->getCurrentStackItem()->docblocInheritDoc = true;
		}

		// Check if the comment starts with '#'
		if ($this->_isActive('noShellComments')) {
			$todoStr = strpos($token->text, '#');
			if ($todoStr === 0) {
				$this->_writeError('noShellComments', $this->_getMessage('NO_SHELL_COMMENTS'));
			}
		}

		// Check if the comment contains a TO DO
		$this->_processTODO($token);
	}
1328333
public static function extractFunctionDocblockInfo($comment, $line_number)
    {
        $comments = DocComment::parse($comment, $line_number);

        $info = new FunctionDocblockComment();

        if (isset($comments['specials']['return']) || isset($comments['specials']['psalm-return'])) {
            /** @var array<int, string> */
            $return_specials = isset($comments['specials']['psalm-return'])
                ? $comments['specials']['psalm-return']
                : $comments['specials']['return'];

            self::extractReturnType((string) reset($return_specials), array_keys($return_specials)[0], $info);
        }

        if (isset($comments['specials']['param']) || isset($comments['specials']['psalm-param'])) {
            $all_params = (isset($comments['specials']['param']) ? $comments['specials']['param'] : [])
                + (isset($comments['specials']['psalm-param']) ? $comments['specials']['psalm-param'] : []);

            /** @var string $param */
            foreach ($all_params as $line_number => $param) {
                $line_parts = self::splitDocLine($param);

                if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') {
                    continue;
                }

                if (count($line_parts) > 1) {
                    if (!preg_match('/\[[^\]]+\]/', $line_parts[0])
                        && preg_match('/^&?(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1])
                        && $line_parts[0][0] !== '{'
                    ) {
                        $line_parts[1] = str_replace('&', '', $line_parts[1]);

                        if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) {
                            throw new IncorrectDocblockException('Misplaced variable');
                        }

                        $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]);

                        $info->params[] = [
                            'name' => $line_parts[1],
                            'type' => $line_parts[0],
                            'line_number' => (int)$line_number,
                        ];
                    }
                } else {
                    throw new DocblockParseException('Badly-formatted @param');
                }
            }
        }

        if (isset($comments['specials']['param-out'])) {
            /** @var string $param */
            foreach ($comments['specials']['param-out'] as $line_number => $param) {
                $line_parts = self::splitDocLine($param);

                if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') {
                    continue;
                }

                if (count($line_parts) > 1) {
                    if (!preg_match('/\[[^\]]+\]/', $line_parts[0])
                        && preg_match('/^(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1])
                        && $line_parts[0][0] !== '{'
                    ) {
                        if ($line_parts[1][0] === '&') {
                            $line_parts[1] = substr($line_parts[1], 1);
                        }

                        if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) {
                            throw new IncorrectDocblockException('Misplaced variable');
                        }

                        $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]);

                        $info->params_out[] = [
                            'name' => $line_parts[1],
                            'type' => $line_parts[0],
                            'line_number' => (int)$line_number,
                        ];
                    }
                } else {
                    throw new DocblockParseException('Badly-formatted @param');
                }
            }
        }

        if (isset($comments['specials']['global'])) {
            foreach ($comments['specials']['global'] as $line_number => $global) {
                $line_parts = self::splitDocLine($global);

                if (count($line_parts) === 1 && isset($line_parts[0][0]) && $line_parts[0][0] === '$') {
                    continue;
                }

                if (count($line_parts) > 1) {
                    if (!preg_match('/\[[^\]]+\]/', $line_parts[0])
                        && preg_match('/^(\.\.\.)?&?\$[A-Za-z0-9_]+,?$/', $line_parts[1])
                        && $line_parts[0][0] !== '{'
                    ) {
                        if ($line_parts[1][0] === '&') {
                            $line_parts[1] = substr($line_parts[1], 1);
                        }

                        if ($line_parts[0][0] === '$' && !preg_match('/^\$this(\||$)/', $line_parts[0])) {
                            throw new IncorrectDocblockException('Misplaced variable');
                        }

                        $line_parts[1] = preg_replace('/,$/', '', $line_parts[1]);

                        $info->globals[] = [
                            'name' => $line_parts[1],
                            'type' => $line_parts[0],
                            'line_number' => (int)$line_number,
                        ];
                    }
                } else {
                    throw new DocblockParseException('Badly-formatted @param');
                }
            }
        }

        if (isset($comments['specials']['deprecated'])) {
            $info->deprecated = true;
        }

        if (isset($comments['specials']['internal'])) {
            $info->internal = true;
        }

        if (isset($comments['specials']['psalm-suppress'])) {
            foreach ($comments['specials']['psalm-suppress'] as $suppress_entry) {
                $info->suppress[] = preg_split('/[\s]+/', $suppress_entry)[0];
            }
        }

        if (isset($comments['specials']['throws'])) {
            foreach ($comments['specials']['throws'] as $throws_entry) {
                $throws_class = preg_split('/[\s]+/', $throws_entry)[0];

                if (!$throws_class) {
                    throw new IncorrectDocblockException('Unexpectedly empty @throws');
                }

                $info->throws[] = $throws_class;
            }
        }

        if (strpos(strtolower($comments['description']), '@inheritdoc') !== false
            || isset($comments['specials']['inheritdoc']) || isset($comments['specials']['inheritDoc'])) {
            $info->inheritdoc = true;
        }

        if (isset($comments['specials']['template']) || isset($comments['specials']['psalm-template'])) {
            $all_templates = (isset($comments['specials']['template']) ? $comments['specials']['template'] : [])
                + (isset($comments['specials']['psalm-template']) ? $comments['specials']['psalm-template'] : []);

            foreach ($all_templates as $template_line) {
                $template_type = preg_split('/[\s]+/', $template_line);

                if (count($template_type) > 2
                    && in_array(strtolower($template_type[1]), ['as', 'super', 'of'], true)
                ) {
                    $info->templates[] = [
                        $template_type[0],
                        strtolower($template_type[1]), $template_type[2]
                    ];
                } else {
                    $info->templates[] = [$template_type[0]];
                }
            }
        }

        if (isset($comments['specials']['template-typeof'])) {
            foreach ($comments['specials']['template-typeof'] as $template_typeof) {
                $typeof_parts = preg_split('/[\s]+/', $template_typeof);

                if (count($typeof_parts) < 2 || $typeof_parts[1][0] !== '$') {
                    throw new IncorrectDocblockException('Misplaced variable');
                }

                $info->template_typeofs[] = [
                    'template_type' => $typeof_parts[0],
                    'param_name' => substr($typeof_parts[1], 1),
                ];
            }
        }

        if (isset($comments['specials']['psalm-assert'])) {
            foreach ($comments['specials']['psalm-assert'] as $assertion) {
                $assertion_parts = preg_split('/[\s]+/', $assertion);

                if (count($assertion_parts) < 2 || $assertion_parts[1][0] !== '$') {
                    throw new IncorrectDocblockException('Misplaced variable');
                }

                $info->assertions[] = [
                    'type' => $assertion_parts[0],
                    'param_name' => substr($assertion_parts[1], 1),
                ];
            }
        }

        if (isset($comments['specials']['psalm-assert-if-true'])) {
            foreach ($comments['specials']['psalm-assert-if-true'] as $assertion) {
                $assertion_parts = preg_split('/[\s]+/', $assertion);

                if (count($assertion_parts) < 2 || $assertion_parts[1][0] !== '$') {
                    throw new IncorrectDocblockException('Misplaced variable');
                }

                $info->if_true_assertions[] = [
                    'type' => $assertion_parts[0],
                    'param_name' => substr($assertion_parts[1], 1),
                ];
            }
        }

        if (isset($comments['specials']['psalm-assert-if-false'])) {
            foreach ($comments['specials']['psalm-assert-if-false'] as $assertion) {
                $assertion_parts = preg_split('/[\s]+/', $assertion);

                if (count($assertion_parts) < 2 || $assertion_parts[1][0] !== '$') {
                    throw new IncorrectDocblockException('Misplaced variable');
                }

                $info->if_false_assertions[] = [
                    'type' => $assertion_parts[0],
                    'param_name' => substr($assertion_parts[1], 1),
                ];
            }
        }

        $info->variadic = isset($comments['specials']['psalm-variadic']);
        $info->ignore_nullable_return = isset($comments['specials']['psalm-ignore-nullable-return']);
        $info->ignore_falsable_return = isset($comments['specials']['psalm-ignore-falsable-return']);

        return $info;
    }