|
15 | 15 |
|
16 | 16 | use Doctum\Reflection\Reflection; |
17 | 17 | use Doctum\Reflection\ClassReflection; |
| 18 | +use Doctum\Reflection\FunctionReflection; |
18 | 19 | use Doctum\Reflection\MethodReflection; |
19 | 20 | use Doctum\Reflection\PropertyReflection; |
20 | 21 | use Doctum\Tree; |
@@ -94,15 +95,15 @@ public function pathForNamespace(array $context, string $namespace): string |
94 | 95 |
|
95 | 96 | public function pathForMethod(array $context, MethodReflection $method) |
96 | 97 | { |
97 | | - /** @var Reflection */ |
| 98 | + /** @var Reflection $class */ |
98 | 99 | $class = $method->getClass(); |
99 | 100 |
|
100 | 101 | return $this->relativeUri($this->currentDepth) . str_replace('\\', '/', $class->getName()) . '.html#method_' . $method->getName(); |
101 | 102 | } |
102 | 103 |
|
103 | 104 | public function pathForProperty(array $context, PropertyReflection $property) |
104 | 105 | { |
105 | | - /** @var Reflection */ |
| 106 | + /** @var Reflection $class */ |
106 | 107 | $class = $property->getClass(); |
107 | 108 |
|
108 | 109 | return $this->relativeUri($this->currentDepth) . str_replace('\\', '/', $class->getName()) . '.html#property_' . $property->getName(); |
@@ -152,18 +153,88 @@ public function parseDesc(array $context, ?string $desc, Reflection $classOrFunc |
152 | 153 |
|
153 | 154 | $desc = str_replace(['<code>', '</code>'], ['```', '```'], $desc); |
154 | 155 |
|
155 | | - // FIXME: the @see argument is more complex than just a class (Class::Method, local method directly, ...) |
156 | | - $desc = preg_replace_callback( |
| 156 | + $desc = (string) preg_replace_callback( |
157 | 157 | '/@see ([^ ]+)/', |
158 | | - static function ($match) { |
159 | | - return 'see ' . $match[1]; |
| 158 | + function ($match) use (&$classOrFunctionRefl): string { |
| 159 | + return $this->transformContentsIntoLinks($match[1], $classOrFunctionRefl); |
160 | 160 | }, |
161 | 161 | $desc |
162 | 162 | ); |
163 | 163 |
|
| 164 | + $desc = (string) preg_replace_callback( |
| 165 | + '/\{@link ((?!\})(?<contents>[^ ]+))/', |
| 166 | + function (array $match) use (&$classOrFunctionRefl): string { |
| 167 | + $data = rtrim($match['contents'], '}'); |
| 168 | + return $this->transformContentsIntoLinks($data, $classOrFunctionRefl); |
| 169 | + }, |
| 170 | + $desc |
| 171 | + ); |
| 172 | + |
| 173 | + |
164 | 174 | return $this->markdown->text($desc); |
165 | 175 | } |
166 | 176 |
|
| 177 | + public function transformContentsIntoLinks(string $data, Reflection $classOrFunctionRefl): string |
| 178 | + { |
| 179 | + $isClassReflection = $classOrFunctionRefl instanceof ClassReflection; |
| 180 | + $isFunctionReflection = $classOrFunctionRefl instanceof FunctionReflection; |
| 181 | + if (! $isClassReflection && ! $isFunctionReflection) { |
| 182 | + return $data; |
| 183 | + } |
| 184 | + |
| 185 | + /** @var ClassReflection|FunctionReflection $class */ |
| 186 | + $class = $classOrFunctionRefl; |
| 187 | + |
| 188 | + // Example: Foo::bar_function_on_foo_class |
| 189 | + $classMethod = explode('::', trim($data, " \t\n\r"), 2); |
| 190 | + |
| 191 | + // Found "bar_function_on_foo_class", from example: bar_function_on_foo_class |
| 192 | + if (count($classMethod) === 1 && $class instanceof ClassReflection) { |
| 193 | + // In this case we resolve a link to a method name in the current class |
| 194 | + $method = $class->getMethod($classMethod[0]); |
| 195 | + if ($method !== false) { |
| 196 | + $short = $this->pathForMethod([], $method); |
| 197 | + return '[' . $data . '](' . $short . ')'; |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + /** @var \Doctum\Project|null $project Original one is not realistic */ |
| 202 | + $project = $class->getProject(); |
| 203 | + if ($project === null) { |
| 204 | + // This should never happen |
| 205 | + return $data; |
| 206 | + } |
| 207 | + |
| 208 | + $cr = $project->getClass($classMethod[0]); |
| 209 | + if ($cr->isPhpClass()) { |
| 210 | + $className = $cr->getName(); |
| 211 | + return '[' . $className . '](https://www.php.net/' . $className . ')'; |
| 212 | + } |
| 213 | + |
| 214 | + if (! $cr->isProjectClass()) { |
| 215 | + return $data; |
| 216 | + } |
| 217 | + |
| 218 | + // Found "bar_function_on_foo_class", from example: Foo::bar_function_on_foo_class |
| 219 | + if (count($classMethod) === 2) { |
| 220 | + // In this case we have a function name to resolve on the previously found class |
| 221 | + $method = $cr->getMethod($classMethod[1]); |
| 222 | + if ($method !== false) { |
| 223 | + $short = $this->pathForMethod([], $method); |
| 224 | + return '[' . $data . '](' . $short . ')'; |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + // Final case, we link the found class |
| 229 | + $short = $this->pathForClass([], $cr->getName()); |
| 230 | + return '[' . $data . '](' . $short . ')'; |
| 231 | + } |
| 232 | + |
| 233 | + /** |
| 234 | + * Seems not to be used |
| 235 | + * |
| 236 | + * @return string |
| 237 | + */ |
167 | 238 | public function getSnippet(string $string) |
168 | 239 | { |
169 | 240 | if (preg_match('/^(.{50,}?)\s.*/m', $string, $matches)) { |
|
0 commit comments