@mintlify/scraping 4.0.906 → 4.0.908
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__test__/sdk-docfx.test.ts +204 -0
- package/__test__/sdk-javadoc.test.ts +150 -0
- package/__test__/sdk-phpdoc.test.ts +134 -0
- package/__test__/sdk-sphinx.test.ts +84 -0
- package/__test__/sdk-typedoc.test.ts +138 -0
- package/bin/index.d.ts +3 -0
- package/bin/index.js +2 -0
- package/bin/index.js.map +1 -1
- package/bin/sdk/convert.d.ts +6 -0
- package/bin/sdk/convert.js +90 -0
- package/bin/sdk/convert.js.map +1 -0
- package/bin/sdk/converters/docfx.d.ts +2 -0
- package/bin/sdk/converters/docfx.js +402 -0
- package/bin/sdk/converters/docfx.js.map +1 -0
- package/bin/sdk/converters/javadoc.d.ts +2 -0
- package/bin/sdk/converters/javadoc.js +318 -0
- package/bin/sdk/converters/javadoc.js.map +1 -0
- package/bin/sdk/converters/phpdoc.d.ts +2 -0
- package/bin/sdk/converters/phpdoc.js +351 -0
- package/bin/sdk/converters/phpdoc.js.map +1 -0
- package/bin/sdk/converters/sphinx.d.ts +2 -0
- package/bin/sdk/converters/sphinx.js +267 -0
- package/bin/sdk/converters/sphinx.js.map +1 -0
- package/bin/sdk/converters/typedoc.d.ts +2 -0
- package/bin/sdk/converters/typedoc.js +318 -0
- package/bin/sdk/converters/typedoc.js.map +1 -0
- package/bin/sdk/generateSdkReference.d.ts +2 -0
- package/bin/sdk/generateSdkReference.js +20 -0
- package/bin/sdk/generateSdkReference.js.map +1 -0
- package/bin/sdk/types.d.ts +19 -0
- package/bin/sdk/types.js +2 -0
- package/bin/sdk/types.js.map +1 -0
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/index.ts +3 -0
- package/src/sdk/convert.ts +101 -0
- package/src/sdk/converters/docfx.ts +491 -0
- package/src/sdk/converters/javadoc.ts +330 -0
- package/src/sdk/converters/phpdoc.ts +446 -0
- package/src/sdk/converters/sphinx.ts +277 -0
- package/src/sdk/converters/typedoc.ts +423 -0
- package/src/sdk/generateSdkReference.ts +25 -0
- package/src/sdk/types.ts +24 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { markdownToMdx } from '../convert.js';
|
|
6
|
+
import type { SdkNavGroup, SdkPage, SdkReference } from '../types.js';
|
|
7
|
+
|
|
8
|
+
type DocTag = {
|
|
9
|
+
'@_name'?: string;
|
|
10
|
+
'@_description'?: string;
|
|
11
|
+
'@_type'?: string;
|
|
12
|
+
'@_variable'?: string;
|
|
13
|
+
'@_link'?: string;
|
|
14
|
+
};
|
|
15
|
+
type Docblock = { description?: string; 'long-description'?: string; tag?: DocTag[] };
|
|
16
|
+
type PhpArgument = { name?: string; default?: string; type?: string };
|
|
17
|
+
type PhpMethod = {
|
|
18
|
+
name?: string;
|
|
19
|
+
argument?: PhpArgument[];
|
|
20
|
+
docblock?: Docblock;
|
|
21
|
+
'@_visibility'?: string;
|
|
22
|
+
'@_static'?: string;
|
|
23
|
+
'@_abstract'?: string;
|
|
24
|
+
};
|
|
25
|
+
type PhpProperty = {
|
|
26
|
+
name?: string;
|
|
27
|
+
default?: string;
|
|
28
|
+
docblock?: Docblock;
|
|
29
|
+
'@_visibility'?: string;
|
|
30
|
+
'@_static'?: string;
|
|
31
|
+
};
|
|
32
|
+
type PhpConstant = { name?: string; value?: string; docblock?: Docblock; '@_visibility'?: string };
|
|
33
|
+
type PhpElement = {
|
|
34
|
+
name?: string;
|
|
35
|
+
full_name?: string;
|
|
36
|
+
extends?: string;
|
|
37
|
+
implements?: string[];
|
|
38
|
+
docblock?: Docblock;
|
|
39
|
+
constant?: PhpConstant[];
|
|
40
|
+
property?: PhpProperty[];
|
|
41
|
+
method?: PhpMethod[];
|
|
42
|
+
'@_namespace'?: string;
|
|
43
|
+
'@_final'?: string;
|
|
44
|
+
'@_abstract'?: string;
|
|
45
|
+
};
|
|
46
|
+
type PhpFile = {
|
|
47
|
+
class?: PhpElement[];
|
|
48
|
+
interface?: PhpElement[];
|
|
49
|
+
trait?: PhpElement[];
|
|
50
|
+
enum?: PhpElement[];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type Entry = {
|
|
54
|
+
element: PhpElement;
|
|
55
|
+
keyword: string;
|
|
56
|
+
tag: string;
|
|
57
|
+
name: string;
|
|
58
|
+
fullName: string;
|
|
59
|
+
namespace: string[];
|
|
60
|
+
slug: string;
|
|
61
|
+
group: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const ARRAY_TAGS = new Set([
|
|
65
|
+
'file',
|
|
66
|
+
'class',
|
|
67
|
+
'interface',
|
|
68
|
+
'trait',
|
|
69
|
+
'enum',
|
|
70
|
+
'method',
|
|
71
|
+
'property',
|
|
72
|
+
'constant',
|
|
73
|
+
'argument',
|
|
74
|
+
'tag',
|
|
75
|
+
'implements',
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const KINDS = [
|
|
79
|
+
{ key: 'class', tag: 'CLASS' },
|
|
80
|
+
{ key: 'interface', tag: 'INTERFACE' },
|
|
81
|
+
{ key: 'trait', tag: 'TRAIT' },
|
|
82
|
+
{ key: 'enum', tag: 'ENUM' },
|
|
83
|
+
] as const;
|
|
84
|
+
|
|
85
|
+
export async function convertPhpdoc(sourcePath: string): Promise<SdkReference> {
|
|
86
|
+
const file = (await fse.stat(sourcePath)).isDirectory()
|
|
87
|
+
? path.join(sourcePath, 'structure.xml')
|
|
88
|
+
: sourcePath;
|
|
89
|
+
const xml = await fse.readFile(file, 'utf8');
|
|
90
|
+
const parser = new XMLParser({
|
|
91
|
+
ignoreAttributes: false,
|
|
92
|
+
attributeNamePrefix: '@_',
|
|
93
|
+
parseTagValue: false,
|
|
94
|
+
parseAttributeValue: false,
|
|
95
|
+
htmlEntities: true,
|
|
96
|
+
isArray: (name) => ARRAY_TAGS.has(name),
|
|
97
|
+
});
|
|
98
|
+
const parsed = parser.parse(xml) as { project?: { file?: PhpFile[] } };
|
|
99
|
+
const entries = collectEntries(parsed.project?.file ?? []);
|
|
100
|
+
if (entries.length === 0) {
|
|
101
|
+
throw new Error(`No phpDocumentor elements found in ${file}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const slugByName = new Map(entries.map((entry) => [entry.fullName, entry.slug]));
|
|
105
|
+
const renderer = new PhpdocRenderer(slugByName);
|
|
106
|
+
|
|
107
|
+
const byGroup = new Map<string, Entry[]>();
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const members = byGroup.get(entry.group) ?? [];
|
|
110
|
+
members.push(entry);
|
|
111
|
+
byGroup.set(entry.group, members);
|
|
112
|
+
}
|
|
113
|
+
const rootGroup = entries.find((entry) => entry.namespace.length === 0)?.group;
|
|
114
|
+
const groupNames = [...byGroup.keys()].sort((left, right) => {
|
|
115
|
+
if (left === rootGroup) return -1;
|
|
116
|
+
if (right === rootGroup) return 1;
|
|
117
|
+
return left.localeCompare(right);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const pages: SdkPage[] = [];
|
|
121
|
+
const groups: SdkNavGroup[] = [];
|
|
122
|
+
for (const groupName of groupNames) {
|
|
123
|
+
const members = (byGroup.get(groupName) ?? []).sort((left, right) =>
|
|
124
|
+
left.name.localeCompare(right.name)
|
|
125
|
+
);
|
|
126
|
+
const groupPages = members.map((entry) => renderer.renderPage(entry));
|
|
127
|
+
pages.push(...groupPages);
|
|
128
|
+
groups.push({ group: groupName, pages: groupPages.map((page) => page.slug) });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { pages, groups };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function collectEntries(files: PhpFile[]): Entry[] {
|
|
135
|
+
const raw: Omit<Entry, 'slug' | 'group' | 'namespace'>[] = [];
|
|
136
|
+
const namespaces: string[][] = [];
|
|
137
|
+
for (const file of files) {
|
|
138
|
+
for (const kind of KINDS) {
|
|
139
|
+
for (const element of file[kind.key] ?? []) {
|
|
140
|
+
const name = element.name ?? '';
|
|
141
|
+
if (!name) continue;
|
|
142
|
+
const fullName = (element.full_name ?? name).replace(/^\\/, '');
|
|
143
|
+
raw.push({ element, keyword: kind.key, tag: kind.tag, name, fullName });
|
|
144
|
+
namespaces.push(namespaceSegments(element, name, fullName));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const prefix = commonPrefix(namespaces);
|
|
149
|
+
const taken = new Set<string>();
|
|
150
|
+
return raw.map((entry, index) => {
|
|
151
|
+
const namespace = (namespaces[index] ?? []).slice(prefix.length);
|
|
152
|
+
const group = namespace.length ? namespace.join('\\') : prefix.join('\\') || 'Reference';
|
|
153
|
+
const base = [...namespace.map(slugify), slugify(entry.name)].join('/');
|
|
154
|
+
let slug = base;
|
|
155
|
+
let counter = 2;
|
|
156
|
+
while (taken.has(slug)) slug = `${base}-${counter++}`;
|
|
157
|
+
taken.add(slug);
|
|
158
|
+
return { ...entry, namespace, group, slug };
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function namespaceSegments(element: PhpElement, name: string, fullName: string): string[] {
|
|
163
|
+
const namespace = element['@_namespace']?.replace(/^\\/, '');
|
|
164
|
+
if (namespace) return namespace.split('\\');
|
|
165
|
+
const segments = fullName.split('\\');
|
|
166
|
+
return segments.at(-1) === name ? segments.slice(0, -1) : segments;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function commonPrefix(lists: string[][]): string[] {
|
|
170
|
+
let prefix = lists[0] ?? [];
|
|
171
|
+
for (const list of lists.slice(1)) {
|
|
172
|
+
let index = 0;
|
|
173
|
+
while (index < prefix.length && prefix[index] === list[index]) index++;
|
|
174
|
+
prefix = prefix.slice(0, index);
|
|
175
|
+
}
|
|
176
|
+
return prefix;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function slugify(name: string): string {
|
|
180
|
+
return (
|
|
181
|
+
name
|
|
182
|
+
.toLowerCase()
|
|
183
|
+
.replace(/[^a-z0-9-_.]+/g, '-')
|
|
184
|
+
.replace(/^-+|-+$/g, '') || 'item'
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function tagsOf(docblock: Docblock | undefined, name: string): DocTag[] {
|
|
189
|
+
return docblock?.tag?.filter((tag) => tag['@_name'] === name) ?? [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function publicOnly<Member extends { '@_visibility'?: string }>(members?: Member[]): Member[] {
|
|
193
|
+
return members?.filter((member) => (member['@_visibility'] ?? 'public') === 'public') ?? [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isNullable(type: string): boolean {
|
|
197
|
+
return type.startsWith('?') || /(^|\|)null(\||$)/i.test(type);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
class PhpdocRenderer {
|
|
201
|
+
constructor(private slugByName: Map<string, string>) {}
|
|
202
|
+
|
|
203
|
+
renderPage(entry: Entry): SdkPage {
|
|
204
|
+
const { element } = entry;
|
|
205
|
+
const lines: string[] = [];
|
|
206
|
+
const summary = this.docblock(element.docblock);
|
|
207
|
+
if (summary) lines.push(summary);
|
|
208
|
+
lines.push(...this.renderHeritage(entry));
|
|
209
|
+
|
|
210
|
+
const constants = publicOnly(element.constant).sort((left, right) =>
|
|
211
|
+
(left.name ?? '').localeCompare(right.name ?? '')
|
|
212
|
+
);
|
|
213
|
+
const properties = publicOnly(element.property).sort((left, right) =>
|
|
214
|
+
(left.name ?? '').localeCompare(right.name ?? '')
|
|
215
|
+
);
|
|
216
|
+
const methods = publicOnly(element.method);
|
|
217
|
+
const constructors = methods.filter((method) => method.name === '__construct');
|
|
218
|
+
const others = methods
|
|
219
|
+
.filter((method) => method.name !== '__construct')
|
|
220
|
+
.sort((left, right) => (left.name ?? '').localeCompare(right.name ?? ''));
|
|
221
|
+
|
|
222
|
+
if (constants.length) {
|
|
223
|
+
lines.push('## Constants');
|
|
224
|
+
for (const constant of constants) {
|
|
225
|
+
const type = tagsOf(constant.docblock, 'var')[0]?.['@_type'] ?? '';
|
|
226
|
+
const body = [
|
|
227
|
+
this.docblock(constant.docblock),
|
|
228
|
+
constant.value ? `Value: \`${escapeInlineCode(constant.value)}\`` : '',
|
|
229
|
+
]
|
|
230
|
+
.filter(Boolean)
|
|
231
|
+
.join('\n\n');
|
|
232
|
+
lines.push(this.responseField(constant.name ?? '', type, false, body));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const ctor of constructors) {
|
|
237
|
+
lines.push('## Constructor');
|
|
238
|
+
lines.push(...this.renderMethodBody(ctor, 3));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (properties.length) {
|
|
242
|
+
lines.push('## Properties');
|
|
243
|
+
for (const property of properties) {
|
|
244
|
+
const varTag = tagsOf(property.docblock, 'var')[0];
|
|
245
|
+
const type = varTag?.['@_type'] ?? '';
|
|
246
|
+
const description =
|
|
247
|
+
this.docblock(property.docblock) ||
|
|
248
|
+
(varTag?.['@_description']
|
|
249
|
+
? markdownToMdx(this.resolveInline(varTag['@_description']))
|
|
250
|
+
: '');
|
|
251
|
+
const body = [
|
|
252
|
+
description,
|
|
253
|
+
property.default ? `Default: \`${escapeInlineCode(property.default)}\`` : '',
|
|
254
|
+
]
|
|
255
|
+
.filter(Boolean)
|
|
256
|
+
.join('\n\n');
|
|
257
|
+
lines.push(this.responseField(property.name ?? '', type, isNullable(type), body));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (others.length) {
|
|
262
|
+
lines.push('## Methods');
|
|
263
|
+
for (const method of others) {
|
|
264
|
+
lines.push(`### ${method.name}()`);
|
|
265
|
+
lines.push(...this.renderMethodBody(method, 4));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
slug: entry.slug,
|
|
271
|
+
title: entry.name,
|
|
272
|
+
description: this.firstSentence(element.docblock),
|
|
273
|
+
tag: entry.tag,
|
|
274
|
+
content: lines.filter(Boolean).join('\n\n'),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private renderHeritage(entry: Entry): string[] {
|
|
279
|
+
const { element } = entry;
|
|
280
|
+
const heritage = [
|
|
281
|
+
element.extends ? `extends ${element.extends}` : '',
|
|
282
|
+
element.implements?.length ? `implements ${element.implements.join(', ')}` : '',
|
|
283
|
+
].filter(Boolean);
|
|
284
|
+
if (!heritage.length) return [];
|
|
285
|
+
const modifiers = [
|
|
286
|
+
element['@_abstract'] === 'true' ? 'abstract' : '',
|
|
287
|
+
element['@_final'] === 'true' ? 'final' : '',
|
|
288
|
+
].filter(Boolean);
|
|
289
|
+
return [this.codeBlock([...modifiers, entry.keyword, entry.name, ...heritage].join(' '))];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private renderMethodBody(method: PhpMethod, depth: number): string[] {
|
|
293
|
+
const lines = [this.codeBlock(this.signature(method))];
|
|
294
|
+
const comment = this.docblock(method.docblock);
|
|
295
|
+
if (comment) lines.push(comment);
|
|
296
|
+
|
|
297
|
+
const args = method.argument ?? [];
|
|
298
|
+
const paramTags = tagsOf(method.docblock, 'param');
|
|
299
|
+
if (args.length) {
|
|
300
|
+
lines.push(`${'#'.repeat(depth)} Parameters`);
|
|
301
|
+
for (const arg of args) {
|
|
302
|
+
const paramTag = paramTags.find((tag) => tag['@_variable'] === arg.name);
|
|
303
|
+
const type = arg.type || paramTag?.['@_type'] || '';
|
|
304
|
+
const optional = Boolean(arg.default) || isNullable(type);
|
|
305
|
+
const body = paramTag?.['@_description']
|
|
306
|
+
? markdownToMdx(this.resolveInline(paramTag['@_description']))
|
|
307
|
+
: '';
|
|
308
|
+
lines.push(this.responseField(arg.name ?? '', type, optional, body));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const returnTag = tagsOf(method.docblock, 'return')[0];
|
|
313
|
+
if (returnTag?.['@_type'] && returnTag['@_type'] !== 'void') {
|
|
314
|
+
lines.push(`${'#'.repeat(depth)} Returns`);
|
|
315
|
+
const description = returnTag['@_description']
|
|
316
|
+
? `\n\n${markdownToMdx(this.resolveInline(returnTag['@_description']))}`
|
|
317
|
+
: '';
|
|
318
|
+
lines.push(`\`${escapeInlineCode(returnTag['@_type'])}\`${description}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const throwsTags = tagsOf(method.docblock, 'throws');
|
|
322
|
+
if (throwsTags.length) {
|
|
323
|
+
lines.push(`${'#'.repeat(depth)} Throws`);
|
|
324
|
+
lines.push(
|
|
325
|
+
markdownToMdx(
|
|
326
|
+
throwsTags
|
|
327
|
+
.map((tag) => {
|
|
328
|
+
const description = tag['@_description'] ? ` ${tag['@_description']}` : '';
|
|
329
|
+
return `- \`${escapeInlineCode(tag['@_type'] ?? 'Exception')}\`${description}`;
|
|
330
|
+
})
|
|
331
|
+
.join('\n')
|
|
332
|
+
)
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return lines;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private signature(method: PhpMethod): string {
|
|
340
|
+
const params = (method.argument ?? [])
|
|
341
|
+
.map((arg) => {
|
|
342
|
+
const type = arg.type ? `${arg.type} ` : '';
|
|
343
|
+
const fallback = arg.default ? ` = ${arg.default}` : '';
|
|
344
|
+
return `${type}$${arg.name}${fallback}`;
|
|
345
|
+
})
|
|
346
|
+
.join(', ');
|
|
347
|
+
const modifiers = [
|
|
348
|
+
method['@_abstract'] === 'true' ? 'abstract' : '',
|
|
349
|
+
'public',
|
|
350
|
+
method['@_static'] === 'true' ? 'static' : '',
|
|
351
|
+
].filter(Boolean);
|
|
352
|
+
const returnType = tagsOf(method.docblock, 'return')[0]?.['@_type'];
|
|
353
|
+
const returns = returnType && method.name !== '__construct' ? `: ${returnType}` : '';
|
|
354
|
+
return `${modifiers.join(' ')} function ${method.name}(${params})${returns}`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private docblock(docblock?: Docblock): string {
|
|
358
|
+
if (!docblock) return '';
|
|
359
|
+
const chunks: string[] = [];
|
|
360
|
+
const sees = [...tagsOf(docblock, 'see'), ...tagsOf(docblock, 'link')]
|
|
361
|
+
.map((tag) => this.seeLink(tag))
|
|
362
|
+
.filter(Boolean);
|
|
363
|
+
const markdown = [
|
|
364
|
+
docblock.description ?? '',
|
|
365
|
+
docblock['long-description'] ?? '',
|
|
366
|
+
sees.length ? `See ${sees.join(', ')}.` : '',
|
|
367
|
+
]
|
|
368
|
+
.filter(Boolean)
|
|
369
|
+
.join('\n\n');
|
|
370
|
+
if (markdown) chunks.push(markdownToMdx(this.resolveInline(markdown)));
|
|
371
|
+
const deprecated = tagsOf(docblock, 'deprecated')[0];
|
|
372
|
+
if (deprecated) {
|
|
373
|
+
const reason = deprecated['@_description']
|
|
374
|
+
? `: ${markdownToMdx(this.resolveInline(deprecated['@_description']))}`
|
|
375
|
+
: '';
|
|
376
|
+
chunks.push(`<Warning>Deprecated${reason}</Warning>`);
|
|
377
|
+
}
|
|
378
|
+
return chunks.filter(Boolean).join('\n\n');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private seeLink(tag: DocTag): string {
|
|
382
|
+
const target = tag['@_link'] || tag['@_description'] || '';
|
|
383
|
+
if (!target) return '';
|
|
384
|
+
if (/^https?:/.test(target)) return `[${tag['@_description'] || target}](${target})`;
|
|
385
|
+
const slug = this.resolve(target);
|
|
386
|
+
if (slug) return `[${target.replace(/^\\/, '')}](/${slug})`;
|
|
387
|
+
return `\`${target}\``;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private resolveInline(text: string): string {
|
|
391
|
+
return text.replace(
|
|
392
|
+
/\{@(?:see|link)\s+([^\s}]+)\s*([^}]*)\}/g,
|
|
393
|
+
(_match, target: string, label: string) => {
|
|
394
|
+
const name = label.trim() || target.replace(/^\\/, '');
|
|
395
|
+
if (/^https?:/.test(target)) return `[${name}](${target})`;
|
|
396
|
+
const slug = this.resolve(target);
|
|
397
|
+
return slug ? `[${name}](/${slug})` : `\`${target}\``;
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private resolve(target: string): string | undefined {
|
|
403
|
+
const normalized = target.replace(/^\\/, '').split('::')[0] ?? '';
|
|
404
|
+
return this.slugByName.get(normalized);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private firstSentence(docblock?: Docblock): string | undefined {
|
|
408
|
+
const text = [docblock?.description ?? '', docblock?.['long-description'] ?? '']
|
|
409
|
+
.join(' ')
|
|
410
|
+
.replace(/\{@(?:see|link)\s+([^\s}]+)[^}]*\}/g, '$1')
|
|
411
|
+
.replace(/\s+/g, ' ')
|
|
412
|
+
.trim();
|
|
413
|
+
if (!text) return undefined;
|
|
414
|
+
const sentence = text.split(/(?<=\.)\s/)[0] ?? text;
|
|
415
|
+
return truncate(sentence, 160);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private responseField(name: string, type: string, optional: boolean, body?: string): string {
|
|
419
|
+
const typeAttr = type ? ` type=${this.jsxString(truncate(type, 80))}` : '';
|
|
420
|
+
const requiredAttr = optional ? '' : ' required';
|
|
421
|
+
return `<ResponseField name=${this.jsxString(name)}${typeAttr}${requiredAttr}>\n${indent(body || '')}\n</ResponseField>`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private jsxString(value: string): string {
|
|
425
|
+
return `{${JSON.stringify(value)}}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private codeBlock(code: string): string {
|
|
429
|
+
return `\`\`\`php\n${code}\n\`\`\``;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function indent(text: string): string {
|
|
434
|
+
return text
|
|
435
|
+
.split('\n')
|
|
436
|
+
.map((line) => (line ? ` ${line}` : line))
|
|
437
|
+
.join('\n');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function truncate(text: string, max: number): string {
|
|
441
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function escapeInlineCode(text: string): string {
|
|
445
|
+
return text.replace(/`/g, '');
|
|
446
|
+
}
|