forked from modelcontextprotocol/php-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDocBlockParser.php
More file actions
127 lines (111 loc) · 3.53 KB
/
Copy pathDocBlockParser.php
File metadata and controls
127 lines (111 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?php
/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Capability\Discovery;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
/**
* Parses DocBlocks using phpdocumentor/reflection-docblock.
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
class DocBlockParser
{
private DocBlockFactoryInterface $docBlockFactory;
public function __construct(
?DocBlockFactoryInterface $docBlockFactory = null,
private readonly LoggerInterface $logger = new NullLogger(),
) {
$this->docBlockFactory = $docBlockFactory ?? DocBlockFactory::createInstance();
}
/**
* Safely parses a DocComment string into a DocBlock object.
*/
public function parseDocBlock(string|false|null $docComment): ?DocBlock
{
if (false === $docComment || null === $docComment || empty($docComment)) {
return null;
}
try {
return $this->docBlockFactory->create($docComment);
} catch (\Throwable $e) {
// Log error or handle gracefully if invalid DocBlock syntax is encountered
$this->logger->warning('Failed to parse DocBlock', [
'error' => $e->getMessage(),
'exception' => $e,
'exception_trace' => $e->getTraceAsString(),
]);
return null;
}
}
/**
* Gets the description from a DocBlock (summary + description body).
*/
public function getDescription(?DocBlock $docBlock): ?string
{
if (!$docBlock) {
return null;
}
$summary = trim($docBlock->getSummary());
$descriptionBody = trim((string) $docBlock->getDescription());
if ($summary && $descriptionBody) {
return $summary."\n\n".$descriptionBody;
}
if ($summary) {
return $summary;
}
if ($descriptionBody) {
return $descriptionBody;
}
return null;
}
/**
* Extracts "@param" tag information from a DocBlock, keyed by variable name (e.g., '$paramName').
*
* @return array<string, Param>
*/
public function getParamTags(?DocBlock $docBlock): array
{
if (!$docBlock) {
return [];
}
/** @var array<string, Param> $paramTags */
$paramTags = [];
foreach ($docBlock->getTagsByName('param') as $tag) {
if ($tag instanceof Param && $tag->getVariableName()) {
$paramTags['$'.$tag->getVariableName()] = $tag;
}
}
return $paramTags;
}
/**
* Gets the description string from a Param tag.
*/
public function getParamDescription(?Param $paramTag): ?string
{
return $paramTag ? (trim((string) $paramTag->getDescription()) ?: null) : null;
}
/**
* Gets the type string from a Param tag.
*/
public function getParamTypeString(?Param $paramTag): ?string
{
if ($paramTag && $paramTag->getType()) {
$typeFromTag = trim((string) $paramTag->getType());
if (!empty($typeFromTag)) {
return ltrim($typeFromTag, '\\');
}
}
return null;
}
}