@mintlify/scraping 4.0.1160 → 4.0.1162

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.
@@ -0,0 +1,828 @@
1
+ import fse from 'fs-extra';
2
+ import path from 'node:path';
3
+ import { visit } from 'unist-util-visit';
4
+ import { z } from 'zod';
5
+ import { isHtmlParagraph, isProseParagraph, parseMarkdown, plainText, rewriteMdastLinks, stringifyMdast, } from '../convert.js';
6
+ import { encodeHref, indent, inlineCode, linkedCode, responseField, safeHref, truncate, } from '../mdx.js';
7
+ const SUPPORTED_SCHEMA_VERSION = 1;
8
+ const PACKAGE_PAGE = 'package-overview';
9
+ const MEMBER_HEADING_DEPTH = 3;
10
+ const MIN_COMMENT_HEADING_DEPTH = 4;
11
+ const MAX_TYPE_ATTR = 80;
12
+ const MIN_README_HEADING_DEPTH = 2;
13
+ const GITHUB_MODULE = /^github\.com\/([^/]+)\/([^/]+)((?:\/[^/]+)*?)(?:\/v(?:[2-9]|[1-9]\d+))?$/;
14
+ const MAX_TEXT = 1 << 20;
15
+ const MAX_CODE = 1 << 18;
16
+ const identifierSchema = z
17
+ .string()
18
+ .max(512)
19
+ .regex(/^[\p{L}_][\p{L}\p{N}_]*$/u, 'expected a Go identifier');
20
+ const importPathSchema = z
21
+ .string()
22
+ .max(512)
23
+ .regex(/^[\p{L}\p{N}._~-]+(?:\/[\p{L}\p{N}._~-]+)*$/u, 'expected a Go import path');
24
+ const DIR_SEGMENT = /^[\p{L}\p{N}._~-]+$/u;
25
+ const dirSchema = z
26
+ .string()
27
+ .max(512)
28
+ .refine((dir) => dir === '.' ||
29
+ dir.split('/').every((segment) => DIR_SEGMENT.test(segment) && !/^\.+$/.test(segment)), 'expected "." or a relative directory of import-path segments, none made only of dots');
30
+ const textSchema = z.string().max(MAX_TEXT);
31
+ const codeTextSchema = z.string().max(MAX_CODE);
32
+ const refSchema = z.object({
33
+ pkg: importPathSchema.optional(),
34
+ name: identifierSchema,
35
+ private: z.boolean().optional(),
36
+ });
37
+ const codeSchema = z.array(z.object({ t: codeTextSchema, ref: refSchema.optional() }));
38
+ const exampleSchema = z.object({
39
+ suffix: identifierSchema.optional(),
40
+ doc: textSchema.optional(),
41
+ code: codeTextSchema,
42
+ output: codeTextSchema.optional(),
43
+ });
44
+ const typeParamSchema = z.object({ name: identifierSchema, constraint: codeSchema });
45
+ const paramSchema = z.object({ name: identifierSchema.optional(), type: codeSchema });
46
+ const funcSchema = z.object({
47
+ name: identifierSchema,
48
+ recv: codeTextSchema.optional(),
49
+ decl: codeSchema,
50
+ doc: textSchema.optional(),
51
+ deprecated: textSchema.optional(),
52
+ typeParams: z.array(typeParamSchema).optional(),
53
+ params: z.array(paramSchema).optional(),
54
+ results: z.array(paramSchema).optional(),
55
+ examples: z.array(exampleSchema).optional(),
56
+ });
57
+ const valueSchema = z.object({
58
+ names: z.array(identifierSchema),
59
+ decl: codeSchema,
60
+ doc: textSchema.optional(),
61
+ deprecated: textSchema.optional(),
62
+ });
63
+ const fieldSchema = z.object({
64
+ name: identifierSchema,
65
+ type: codeSchema,
66
+ doc: textSchema.optional(),
67
+ deprecated: textSchema.optional(),
68
+ embedded: z.boolean().optional(),
69
+ tag: codeTextSchema.optional(),
70
+ });
71
+ const typeSchema = z.object({
72
+ name: identifierSchema,
73
+ kind: z.enum(['struct', 'interface', 'defined', 'alias']),
74
+ decl: codeSchema,
75
+ doc: textSchema.optional(),
76
+ deprecated: textSchema.optional(),
77
+ typeParams: z.array(typeParamSchema).optional(),
78
+ fields: z.array(fieldSchema).optional(),
79
+ methods: z.array(funcSchema).optional(),
80
+ funcs: z.array(funcSchema).optional(),
81
+ consts: z.array(valueSchema).optional(),
82
+ vars: z.array(valueSchema).optional(),
83
+ examples: z.array(exampleSchema).optional(),
84
+ });
85
+ const packageSchema = z.object({
86
+ importPath: importPathSchema,
87
+ name: identifierSchema,
88
+ dir: dirSchema,
89
+ synopsis: textSchema.optional(),
90
+ doc: textSchema.optional(),
91
+ readme: textSchema.optional(),
92
+ deprecated: textSchema.optional(),
93
+ consts: z.array(valueSchema).optional(),
94
+ vars: z.array(valueSchema).optional(),
95
+ funcs: z.array(funcSchema).optional(),
96
+ types: z.array(typeSchema).optional(),
97
+ examples: z.array(exampleSchema).optional(),
98
+ });
99
+ const goArtifactSchema = z.object({
100
+ schemaVersion: z.literal(SUPPORTED_SCHEMA_VERSION),
101
+ generator: z.object({
102
+ name: z.string().max(512),
103
+ version: z.string().max(512),
104
+ go: z.string().max(512),
105
+ }),
106
+ module: importPathSchema,
107
+ goos: z.string(),
108
+ goarch: z.string(),
109
+ commentSyntax: z.enum(['markdown', 'godoc']),
110
+ packages: z.array(packageSchema).superRefine((packages, ctx) => {
111
+ const importPaths = new Set();
112
+ const dirs = new Set();
113
+ const namespaces = new Map();
114
+ const root = packages.find((pkg) => pkg.dir === '.');
115
+ packages.forEach((pkg, index) => {
116
+ if (importPaths.has(pkg.importPath)) {
117
+ ctx.addIssue({
118
+ code: z.ZodIssueCode.custom,
119
+ path: [index, 'importPath'],
120
+ message: `duplicate package ${pkg.importPath}`,
121
+ });
122
+ }
123
+ importPaths.add(pkg.importPath);
124
+ if (dirs.has(pkg.dir)) {
125
+ ctx.addIssue({
126
+ code: z.ZodIssueCode.custom,
127
+ path: [index, 'dir'],
128
+ message: `duplicate package directory ${pkg.dir}`,
129
+ });
130
+ }
131
+ dirs.add(pkg.dir);
132
+ const namespace = packageNamespace(pkg, root);
133
+ const other = namespaces.get(namespace);
134
+ if (other !== undefined) {
135
+ ctx.addIssue({
136
+ code: z.ZodIssueCode.custom,
137
+ path: [index, 'dir'],
138
+ message: `packages ${other} and ${pkg.dir} would both be addressed as ${namespace}`,
139
+ });
140
+ }
141
+ namespaces.set(namespace, pkg.dir);
142
+ });
143
+ }),
144
+ });
145
+ const TYPE_TAGS = {
146
+ struct: 'STRUCT',
147
+ interface: 'INTERFACE',
148
+ defined: 'TYPE',
149
+ alias: 'TYPE',
150
+ };
151
+ export async function convertGo(sourcePath) {
152
+ const artifact = parseArtifact(await fse.readJson(sourcePath), sourcePath);
153
+ const index = new GoIndex(artifact);
154
+ const renderers = artifact.packages.map((pkg) => new GoRenderer(index, pkg));
155
+ const pages = [];
156
+ const groups = [];
157
+ for (const renderer of renderers) {
158
+ const packagePages = renderer.packagePages();
159
+ pages.push(...packagePages, ...renderer.memberPages());
160
+ groups.push({ group: renderer.group, pages: packagePages.map((page) => page.slug) });
161
+ }
162
+ return { pages, groups };
163
+ }
164
+ function parseArtifact(raw, sourcePath) {
165
+ const version = typeof raw === 'object' && raw !== null && 'schemaVersion' in raw
166
+ ? raw.schemaVersion
167
+ : undefined;
168
+ if (typeof version === 'number' && version !== SUPPORTED_SCHEMA_VERSION) {
169
+ throw new Error(`Go SDK artifact ${sourcePath} uses schema version ${version}, but Mintlify reads schema version ${SUPPORTED_SCHEMA_VERSION}. Regenerate it with a compatible mintlify-go-extractor release.`);
170
+ }
171
+ const result = goArtifactSchema.safeParse(raw);
172
+ if (!result.success) {
173
+ const issue = result.error.issues[0];
174
+ const detail = issue ? `${issue.path.join('.') || 'artifact'}: ${issue.message}` : 'invalid';
175
+ throw new Error(`Invalid Go SDK artifact ${sourcePath}: ${detail}`);
176
+ }
177
+ return result.data;
178
+ }
179
+ function slugify(name) {
180
+ return name.replace(/[^\p{L}\p{N}\-_.]+/gu, '-').replace(/^-+|-+$/g, '') || 'item';
181
+ }
182
+ function packageBase(pkg) {
183
+ return pkg.dir === '.' ? '' : pkg.dir.split('/').map(slugify).join('/');
184
+ }
185
+ // Packages are addressed as a tree headed by the root package's name and
186
+ // continued by directory path, dotted: the root of openai-go is `package
187
+ // openai` and its symbols `type openai.Client`; audio/client is `package
188
+ // openai.audio.client` (or any trailing part, `package client`) and its
189
+ // symbols `type openai.audio.client.Foo`. api/v1 (package api) is `package
190
+ // api.v1`, not `package api`. The root prefix keeps a root package and a
191
+ // top-level directory of the same name apart; a module without a root package
192
+ // has nothing to keep apart and uses the directory path alone.
193
+ function packageNamespace(pkg, root) {
194
+ if (pkg.dir === '.')
195
+ return pkg.name;
196
+ const dotted = pkg.dir.replaceAll('/', '.');
197
+ return root ? `${root.name}.${dotted}` : dotted;
198
+ }
199
+ function packageTargetName(pkg) {
200
+ return pkg.dir === '.' ? pkg.name : path.posix.basename(pkg.dir);
201
+ }
202
+ function packageParent(pkg, root) {
203
+ if (pkg.dir === '.')
204
+ return undefined;
205
+ const parent = path.posix.dirname(pkg.dir);
206
+ const dotted = parent === '.' ? undefined : parent.replaceAll('/', '.');
207
+ if (!root)
208
+ return dotted;
209
+ return dotted ? `${root.name}.${dotted}` : root.name;
210
+ }
211
+ function memberHeading(name) {
212
+ return `${name}()`;
213
+ }
214
+ // Method headings carry an explicit id, pkg.go.dev style, so links to them
215
+ // do not depend on how many same-named headings precede them on the page.
216
+ function methodAnchor(type, method) {
217
+ return `${type.name}.${method.name}`;
218
+ }
219
+ function packageFuncs(pkg) {
220
+ return [...(pkg.funcs ?? []), ...(pkg.types ?? []).flatMap((type) => type.funcs ?? [])].sort((left, right) => left.name.localeCompare(right.name));
221
+ }
222
+ class GoIndex {
223
+ constructor(artifact) {
224
+ this.packages = new Map();
225
+ this.symbols = new Map();
226
+ this.routes = new Map();
227
+ this.module = artifact.module;
228
+ this.root = artifact.packages.find((pkg) => pkg.dir === '.');
229
+ for (const pkg of artifact.packages) {
230
+ const base = packageBase(pkg);
231
+ const overview = { slug: this.route(base, PACKAGE_PAGE) };
232
+ this.packages.set(pkg.importPath, overview.slug);
233
+ for (const value of [...(pkg.consts ?? []), ...(pkg.vars ?? [])]) {
234
+ for (const name of value.names)
235
+ this.add(pkg, name, overview);
236
+ }
237
+ for (const fn of packageFuncs(pkg)) {
238
+ this.add(pkg, fn.name, { slug: this.route(base, fn.name) });
239
+ }
240
+ for (const type of pkg.types ?? []) {
241
+ const slug = this.route(base, type.name);
242
+ this.add(pkg, type.name, { slug });
243
+ for (const value of [...(type.consts ?? []), ...(type.vars ?? [])]) {
244
+ for (const name of value.names)
245
+ this.add(pkg, name, { slug });
246
+ }
247
+ }
248
+ }
249
+ for (const pkg of artifact.packages) {
250
+ for (const type of pkg.types ?? []) {
251
+ const slug = this.slugOf(pkg, type.name);
252
+ for (const method of type.methods ?? []) {
253
+ this.add(pkg, `${type.name}.${method.name}`, {
254
+ slug,
255
+ anchor: methodAnchor(type, method),
256
+ page: this.route(slug, method.name),
257
+ });
258
+ }
259
+ for (const field of type.fields ?? []) {
260
+ this.add(pkg, `${type.name}.${field.name}`, {
261
+ slug,
262
+ anchor: field.name,
263
+ page: this.route(slug, field.name),
264
+ });
265
+ }
266
+ }
267
+ }
268
+ }
269
+ add(pkg, name, location) {
270
+ this.symbols.set(`${pkg.importPath}#${name}`, location);
271
+ }
272
+ slugOf(pkg, name) {
273
+ return this.symbols.get(`${pkg.importPath}#${name}`)?.slug ?? packageBase(pkg);
274
+ }
275
+ route(base, name) {
276
+ const slug = path.posix.join(base, slugify(name));
277
+ // Same key prebuild uses for route collisions (getGeneratedRouteKey).
278
+ const key = slug.normalize('NFC').toLowerCase();
279
+ const taken = this.routes.get(key);
280
+ if (taken === undefined) {
281
+ this.routes.set(key, 1);
282
+ return slug;
283
+ }
284
+ // A Go identifier cannot contain "-", so `${key}-${attempt}` is only ever a
285
+ // route this function reserved itself and the next number is free.
286
+ const attempt = taken + 1;
287
+ this.routes.set(key, attempt);
288
+ this.routes.set(`${key}-${attempt}`, 1);
289
+ return `${slug}-${attempt}`;
290
+ }
291
+ packageSlug(importPath) {
292
+ return this.packages.get(importPath);
293
+ }
294
+ symbolSlug(importPath, name) {
295
+ return this.symbols.get(`${importPath}#${name}`)?.slug;
296
+ }
297
+ memberPageSlug(importPath, name) {
298
+ return this.symbols.get(`${importPath}#${name}`)?.page;
299
+ }
300
+ href(importPath, name, isPrivate) {
301
+ if (name) {
302
+ const dot = name.indexOf('.');
303
+ const location = this.symbols.get(`${importPath}#${name}`) ??
304
+ (dot > 0 ? this.symbols.get(`${importPath}#${name.slice(0, dot)}`) : undefined);
305
+ if (location)
306
+ return `/${location.slug}${location.anchor ? `#${location.anchor}` : ''}`;
307
+ }
308
+ const packageSlug = this.packages.get(importPath);
309
+ if (packageSlug)
310
+ return `/${packageSlug}`;
311
+ if (isPrivate)
312
+ return undefined;
313
+ return `https://pkg.go.dev/${encodeHref(importPath)}${name ? `#${encodeHref(name)}` : ''}`;
314
+ }
315
+ }
316
+ class GoRenderer {
317
+ constructor(index, pkg) {
318
+ this.index = index;
319
+ this.pkg = pkg;
320
+ this.rendered = new Map();
321
+ this.group = pkg.dir === '.' ? pkg.name : pkg.dir;
322
+ this.base = packageBase(pkg);
323
+ this.funcs = packageFuncs(pkg);
324
+ }
325
+ packagePages() {
326
+ return [
327
+ this.packagePage(),
328
+ ...(this.pkg.types ?? []).map((type) => this.typePage(type)),
329
+ ...this.funcs.map((fn) => this.funcPage(fn)),
330
+ ];
331
+ }
332
+ memberPages() {
333
+ return (this.pkg.types ?? []).flatMap((type) => this.typeMemberPages(type));
334
+ }
335
+ packagePage() {
336
+ const slug = this.index.packageSlug(this.pkg.importPath) ?? path.posix.join(this.base, PACKAGE_PAGE);
337
+ const lines = [this.prose(this.pkg.doc), this.deprecated(this.pkg.deprecated)];
338
+ const types = this.pkg.types ?? [];
339
+ if (types.length)
340
+ lines.push('## Types', this.indexList(types.map((type) => type.name)));
341
+ if (this.funcs.length) {
342
+ lines.push('## Functions', this.indexList(this.funcs.map((fn) => fn.name)));
343
+ }
344
+ lines.push(...this.valueSection('Constants', this.pkg.consts, 2));
345
+ lines.push(...this.valueSection('Variables', this.pkg.vars, 2));
346
+ lines.push(...this.exampleSection(this.pkg.examples, 2));
347
+ const parent = packageParent(this.pkg, this.index.root);
348
+ // The synopsis is the package doc's first sentence, so it is read from the
349
+ // doc's parse, which rendering the page needs anyway.
350
+ const synopsis = this.description(this.pkg.doc);
351
+ const readme = this.pkg.readme ? this.readme(this.pkg.readme, synopsis) : undefined;
352
+ const description = synopsis ?? readme?.description;
353
+ return {
354
+ slug,
355
+ title: `package ${this.pkg.name}`,
356
+ ...(description ? { description } : {}),
357
+ tag: 'PACKAGE',
358
+ ...(readme?.intro ? { intro: readme.intro } : {}),
359
+ content: join(lines),
360
+ target: {
361
+ kind: 'package',
362
+ name: packageTargetName(this.pkg),
363
+ ...(parent ? { namespace: parent } : {}),
364
+ },
365
+ };
366
+ }
367
+ // Renders a README the way pkg.go.dev shows it, minus the sentence the page
368
+ // shows as its description (see takeLeadingSentence).
369
+ readme(markdown, synopsis) {
370
+ if (unparseable(markdown))
371
+ return { intro: codeBlock(markdown, 'text') };
372
+ const tree = parseMarkdown(markdown);
373
+ const first = tree.children[0];
374
+ if (first?.type === 'heading' && first.depth === 1)
375
+ tree.children.shift();
376
+ visit(tree, 'heading', (node) => {
377
+ node.depth = Math.max(node.depth, MIN_README_HEADING_DEPTH);
378
+ });
379
+ const imageDefinitions = new Set();
380
+ const linkDefinitions = new Set();
381
+ visit(tree, 'imageReference', (node) => {
382
+ imageDefinitions.add(node.identifier);
383
+ });
384
+ visit(tree, 'linkReference', (node) => {
385
+ linkDefinitions.add(node.identifier);
386
+ });
387
+ rewriteMdastLinks(tree, (node) => this.repoUrl(node.url, node.type === 'image' ||
388
+ (node.type === 'definition' &&
389
+ imageDefinitions.has(node.identifier) &&
390
+ !linkDefinitions.has(node.identifier))));
391
+ const description = takeLeadingSentence(tree, synopsis);
392
+ return { intro: stringifyMdast(tree), ...(description ? { description } : {}) };
393
+ }
394
+ // The first sentence of a doc's opening paragraph, read from the parsed tree
395
+ // rather than from the Markdown source, so link syntax and escapes are
396
+ // already gone and the cost is linear in the doc's length.
397
+ describe(markdown) {
398
+ const description = this.description(markdown);
399
+ return description ? { description } : {};
400
+ }
401
+ description(markdown) {
402
+ if (!markdown)
403
+ return undefined;
404
+ const paragraph = this.doc(markdown).tree.children.find((node) => node.type === 'paragraph');
405
+ if (!paragraph)
406
+ return undefined;
407
+ return firstSentence(plainText(paragraph).replace(/\s+/g, ' ').trim());
408
+ }
409
+ // Relative README links resolve against the package directory in the
410
+ // repository; a leading slash means the repository root, as on GitHub.
411
+ repoUrl(url, raw) {
412
+ const href = safeHref(url);
413
+ if (href === undefined || !/^[^#]/.test(href) || /^[a-z][a-z0-9+.-]*:/i.test(href)) {
414
+ return href;
415
+ }
416
+ const match = GITHUB_MODULE.exec(this.index.module);
417
+ if (!match)
418
+ return href;
419
+ const [, owner, repo, moduleDir = ''] = match;
420
+ const [file = '', hash] = href.split('#');
421
+ const resolved = file.startsWith('/')
422
+ ? path.posix.normalize(file.slice(1))
423
+ : path.posix.normalize(path.posix.join(moduleDir.slice(1), this.pkg.dir === '.' ? '' : this.pkg.dir, file));
424
+ if (resolved.startsWith('..'))
425
+ return href;
426
+ const view = raw ? 'raw' : 'blob';
427
+ return `https://github.com/${owner}/${repo}/${view}/HEAD/${resolved}${hash ? `#${hash}` : ''}`;
428
+ }
429
+ typePage(type) {
430
+ const lines = [
431
+ this.prose(type.doc),
432
+ this.deprecated(type.deprecated),
433
+ codeBlock(declarationHeader(type)),
434
+ ];
435
+ if (type.typeParams?.length) {
436
+ lines.push('## Type Parameters', ...type.typeParams.map((param) => this.field(param.name, param.constraint)));
437
+ }
438
+ if (type.fields?.length) {
439
+ lines.push('## Fields', ...type.fields.map((field) => this.fieldEntry(field)));
440
+ }
441
+ lines.push(...this.memberSection('Constructors', type.funcs));
442
+ lines.push(...this.memberSection('Methods', type.methods, type));
443
+ lines.push(...this.valueSection('Constants', type.consts, 2));
444
+ lines.push(...this.valueSection('Variables', type.vars, 2));
445
+ lines.push(...this.exampleSection(type.examples, 2));
446
+ return {
447
+ slug: this.slug(type.name),
448
+ title: type.name,
449
+ ...this.describe(type.doc),
450
+ tag: TYPE_TAGS[type.kind],
451
+ content: join(lines),
452
+ target: this.target(type.kind === 'interface' ? 'interface' : 'type', type.name),
453
+ };
454
+ }
455
+ funcPage(fn) {
456
+ const lines = [
457
+ this.prose(fn.doc),
458
+ this.deprecated(fn.deprecated),
459
+ codeBlock(codeText(fn.decl)),
460
+ ...this.signatureDetails(fn, 2),
461
+ ];
462
+ return {
463
+ slug: this.slug(fn.name),
464
+ title: fn.name,
465
+ ...this.describe(fn.doc),
466
+ tag: 'FUNCTION',
467
+ content: join(lines),
468
+ target: this.target('function', fn.name),
469
+ };
470
+ }
471
+ typeMemberPages(type) {
472
+ const methods = (type.methods ?? []).map((method) => ({
473
+ slug: this.memberSlug(type, method.name),
474
+ title: `${type.name}.${method.name}`,
475
+ ...this.describe(method.doc),
476
+ tag: 'METHOD',
477
+ content: join(this.funcBody(method, 2)),
478
+ target: this.target('method', method.name, type.name),
479
+ }));
480
+ const fields = (type.fields ?? []).map((field) => ({
481
+ slug: this.memberSlug(type, field.name),
482
+ title: `${type.name}.${field.name}`,
483
+ ...this.describe(field.doc),
484
+ tag: 'FIELD',
485
+ content: this.fieldEntry(field),
486
+ target: this.target('property', field.name, type.name),
487
+ }));
488
+ return [...methods, ...fields];
489
+ }
490
+ target(kind, name, parent) {
491
+ return {
492
+ kind,
493
+ name,
494
+ ...(parent ? { parent } : {}),
495
+ namespace: packageNamespace(this.pkg, this.index.root),
496
+ };
497
+ }
498
+ slug(name) {
499
+ return (this.index.symbolSlug(this.pkg.importPath, name) ?? path.posix.join(this.base, slugify(name)));
500
+ }
501
+ memberSlug(type, name) {
502
+ return (this.index.memberPageSlug(this.pkg.importPath, `${type.name}.${name}`) ??
503
+ path.posix.join(this.slug(type.name), slugify(name)));
504
+ }
505
+ indexList(names) {
506
+ return names.map((name) => `- [${name}](/${this.slug(name)})`).join('\n');
507
+ }
508
+ memberSection(heading, fns, owner) {
509
+ if (!fns?.length)
510
+ return [];
511
+ const marker = '#'.repeat(MEMBER_HEADING_DEPTH);
512
+ return [
513
+ `## ${heading}`,
514
+ ...fns.flatMap((fn) => [
515
+ `${marker} ${memberHeading(fn.name)}${owner ? ` {#${methodAnchor(owner, fn)}}` : ''}`,
516
+ ...this.funcBody(fn, 4),
517
+ ]),
518
+ ];
519
+ }
520
+ funcBody(fn, depth) {
521
+ return [
522
+ codeBlock(codeText(fn.decl)),
523
+ this.prose(fn.doc),
524
+ this.deprecated(fn.deprecated),
525
+ ...this.signatureDetails(fn, depth),
526
+ ];
527
+ }
528
+ signatureDetails(fn, depth) {
529
+ const heading = '#'.repeat(depth);
530
+ const lines = [];
531
+ if (fn.typeParams?.length) {
532
+ lines.push(`${heading} Type Parameters`, ...fn.typeParams.map((param) => this.field(param.name, param.constraint)));
533
+ }
534
+ const params = fn.params ?? [];
535
+ if (params.length) {
536
+ lines.push(`${heading} Parameters`, ...(params.every((param) => Boolean(param.name))
537
+ ? params.map((param) => this.field(param.name, param.type))
538
+ : [this.returns(params)]));
539
+ }
540
+ if (fn.results?.length)
541
+ lines.push(`${heading} Returns`, this.returns(fn.results));
542
+ lines.push(...this.exampleSection(fn.examples, depth));
543
+ return lines;
544
+ }
545
+ returns(results) {
546
+ const tokens = [];
547
+ results.forEach((result, position) => {
548
+ if (position > 0)
549
+ tokens.push({ text: ', ' });
550
+ tokens.push(...this.tokens(result.type));
551
+ });
552
+ if (results.length > 1) {
553
+ tokens.unshift({ text: '(' });
554
+ tokens.push({ text: ')' });
555
+ }
556
+ return linkedCode(tokens) ?? inlineCode(tokens.map((token) => token.text).join(''));
557
+ }
558
+ fieldEntry(field) {
559
+ const body = join([
560
+ field.embedded ? 'Embedded field.' : undefined,
561
+ field.tag ? `**Tag:** ${inlineCode(field.tag)}` : undefined,
562
+ this.prose(field.doc),
563
+ this.deprecated(field.deprecated),
564
+ ]);
565
+ return this.field(field.name, field.type, body, field.name);
566
+ }
567
+ // The type attribute is cut at 80 characters, so a longer type is also
568
+ // written out in full in the body: linked when it has refs, plain otherwise.
569
+ field(name, code, body, id) {
570
+ const typeHref = this.singleRefHref(code);
571
+ const text = codeText(code);
572
+ const full = typeHref
573
+ ? undefined
574
+ : (linkedCode(this.tokens(code)) ??
575
+ (text.length > MAX_TYPE_ATTR ? inlineCode(text) : undefined));
576
+ const content = join([full && `**Type:** ${full}`, body]);
577
+ return responseField(name, text, true, content, typeHref, id);
578
+ }
579
+ singleRefHref(code) {
580
+ const position = code.findIndex((token) => token.ref);
581
+ if (position === -1 || position !== code.length - 1)
582
+ return undefined;
583
+ const prefix = codeText(code.slice(0, position));
584
+ if (!/^(?:\*|\[\]|\.\.\.)*(?:[\p{L}_][\p{L}\p{N}_]*\.)?$/u.test(prefix))
585
+ return undefined;
586
+ const ref = code[position]?.ref;
587
+ const href = ref ? this.refHref(ref) : undefined;
588
+ return href?.startsWith('/') ? href : undefined;
589
+ }
590
+ tokens(code) {
591
+ return code.map((token) => {
592
+ const href = token.ref ? this.refHref(token.ref) : undefined;
593
+ return href ? { text: token.t, href } : { text: token.t };
594
+ });
595
+ }
596
+ refHref(ref) {
597
+ return this.index.href(ref.pkg ?? this.pkg.importPath, ref.name, ref.private ?? false);
598
+ }
599
+ valueSection(heading, values, depth) {
600
+ if (!values?.length)
601
+ return [];
602
+ return [
603
+ `${'#'.repeat(depth)} ${heading}`,
604
+ ...values.flatMap((value) => [
605
+ codeBlock(codeText(value.decl)),
606
+ this.prose(value.doc),
607
+ this.deprecated(value.deprecated),
608
+ ]),
609
+ ];
610
+ }
611
+ exampleSection(examples, depth) {
612
+ if (!examples?.length)
613
+ return [];
614
+ const heading = '#'.repeat(depth);
615
+ return [
616
+ `${heading} Examples`,
617
+ ...examples.flatMap((example) => [
618
+ `${heading}# ${example.suffix ? `Example (${example.suffix})` : 'Example'}`,
619
+ this.prose(example.doc),
620
+ codeBlock(example.code),
621
+ example.output ? `Output:\n\n${codeBlock(example.output.replace(/\n+$/, ''), 'text')}` : '',
622
+ ]),
623
+ ];
624
+ }
625
+ deprecated(text) {
626
+ if (!text)
627
+ return '';
628
+ return `<Warning>\n${indent(`Deprecated: ${this.prose(text)}`)}\n</Warning>`;
629
+ }
630
+ prose(markdown) {
631
+ return markdown ? stringifyMdast(this.doc(markdown).tree) : '';
632
+ }
633
+ doc(markdown) {
634
+ let doc = this.rendered.get(markdown);
635
+ if (!doc) {
636
+ doc = this.parseDoc(markdown);
637
+ this.rendered.set(markdown, doc);
638
+ }
639
+ return doc;
640
+ }
641
+ parseDoc(markdown) {
642
+ if (unparseable(markdown)) {
643
+ return {
644
+ tree: { type: 'root', children: [{ type: 'code', lang: 'text', value: markdown }] },
645
+ };
646
+ }
647
+ const tree = parseMarkdown(markdown, MIN_COMMENT_HEADING_DEPTH);
648
+ rewriteMdastLinks(tree, (node) => {
649
+ if (!node.url.startsWith('go:'))
650
+ return safeHref(node.url);
651
+ return this.resolveDocLink(node.url.slice('go:'.length));
652
+ });
653
+ return { tree };
654
+ }
655
+ resolveDocLink(target) {
656
+ const hash = target.indexOf('#');
657
+ const importPath = hash === -1 ? target : target.slice(0, hash);
658
+ const name = hash === -1 ? undefined : target.slice(hash + 1);
659
+ return this.index.href(importPath, name, false);
660
+ }
661
+ }
662
+ function codeText(code) {
663
+ return code.map((token) => token.t).join('');
664
+ }
665
+ function declarationHeader(type) {
666
+ const text = codeText(type.decl);
667
+ if (type.kind !== 'struct' && type.kind !== 'interface')
668
+ return text;
669
+ const open = bodyStart(text, type.kind);
670
+ if (open === -1)
671
+ return text;
672
+ if (type.kind === 'interface') {
673
+ const body = text.slice(open + type.kind.length + 3, text.lastIndexOf('}'));
674
+ const entries = body
675
+ .split('\n')
676
+ .map((line) => line.trim())
677
+ .filter((line) => line && !line.startsWith('//'));
678
+ const shown = (type.methods?.length ?? 0) + (type.fields?.length ?? 0);
679
+ if (entries.length > shown)
680
+ return text;
681
+ }
682
+ return text.slice(0, open + type.kind.length + 1);
683
+ }
684
+ // The declaration's own body opens outside any type-parameter brackets; a
685
+ // constraint written as `interface{ ... }` inside `[...]` comes first in the
686
+ // text but is not it.
687
+ function bodyStart(text, kind) {
688
+ const marker = ` ${kind} {`;
689
+ let depth = 0;
690
+ for (let i = 0; i < text.length; i += 1) {
691
+ const char = text[i];
692
+ if (char === '[')
693
+ depth += 1;
694
+ else if (char === ']')
695
+ depth -= 1;
696
+ else if (depth === 0 && text.startsWith(marker, i))
697
+ return i;
698
+ }
699
+ return -1;
700
+ }
701
+ function codeBlock(code, lang = 'go') {
702
+ const longestRun = Math.max(0, ...(code.match(/`+/g) ?? []).map((run) => run.length));
703
+ const fence = '`'.repeat(Math.max(3, longestRun + 1));
704
+ return `${fence}${lang}\n${code}\n${fence}`;
705
+ }
706
+ function join(chunks) {
707
+ return chunks.filter(Boolean).join('\n\n');
708
+ }
709
+ // The README's first prose paragraph (badges, images, and HTML blocks such as a
710
+ // centred <h1> are skipped) supplies the description when the page has none,
711
+ // provided it is a single sentence, and is then removed from the intro since
712
+ // the page already shows it. When the package doc supplied the description and
713
+ // the README opens with the same sentence, that sentence is removed instead.
714
+ function takeLeadingSentence(tree, synopsis) {
715
+ const index = tree.children.findIndex((node) => node.type === 'paragraph' && !isHtmlParagraph(node) && isProseParagraph(node));
716
+ const paragraph = tree.children[index];
717
+ if (!paragraph)
718
+ return synopsis;
719
+ const text = plainText(paragraph).replace(/\s+/g, ' ').trim();
720
+ const sentence = leadingSentence(text);
721
+ if (!sentence || (synopsis !== undefined && truncate(sentence, MAX_DESCRIPTION) !== synopsis)) {
722
+ return synopsis;
723
+ }
724
+ if (sentence === text) {
725
+ tree.children.splice(index, 1);
726
+ return synopsis ?? truncate(sentence, MAX_DESCRIPTION);
727
+ }
728
+ if (synopsis !== undefined)
729
+ dropLeadingSentence(paragraph);
730
+ return synopsis;
731
+ }
732
+ // Removes a paragraph's first sentence, whatever inline markup it contains,
733
+ // by dropping the children that lie wholly inside it and trimming the text
734
+ // node it ends in. When the sentence ends inside a link or emphasis, nothing
735
+ // is removed rather than splitting that node.
736
+ function dropLeadingSentence(paragraph) {
737
+ const pieces = paragraph.children.map(plainText);
738
+ const end = /\.(?:\s|$)/.exec(pieces.join(''));
739
+ if (!end)
740
+ return;
741
+ const stop = end.index + 1;
742
+ let offset = 0;
743
+ for (let i = 0; i < paragraph.children.length; i += 1) {
744
+ const child = paragraph.children[i];
745
+ const length = pieces[i]?.length ?? 0;
746
+ if (offset + length <= stop) {
747
+ offset += length;
748
+ continue;
749
+ }
750
+ if (offset === stop) {
751
+ // The sentence ended with the previous node; drop it and any hard
752
+ // break that separated it from the rest.
753
+ paragraph.children.splice(0, i);
754
+ while (paragraph.children[0]?.type === 'break')
755
+ paragraph.children.shift();
756
+ const next = paragraph.children[0];
757
+ if (next?.type === 'text') {
758
+ next.value = next.value.trimStart();
759
+ if (next.value === '')
760
+ paragraph.children.shift();
761
+ }
762
+ return;
763
+ }
764
+ if (child?.type !== 'text')
765
+ return;
766
+ child.value = child.value.slice(stop - offset).trimStart();
767
+ paragraph.children.splice(0, i);
768
+ if (child.value === '')
769
+ paragraph.children.shift();
770
+ return;
771
+ }
772
+ paragraph.children = [];
773
+ }
774
+ // Text past either limit is rendered as a text block instead of Markdown.
775
+ // micromark is quadratic in unclosed brackets (80k `[a](` takes about 45 s)
776
+ // and in balanced link and emphasis markup (40k nested brackets, `[]` pairs,
777
+ // `[a]` references, or `*a` runs take 5 to 36 s), and remark-stringify is
778
+ // quadratic in the characters it escapes (40k `<` or `{` take 0.7 s, minutes
779
+ // at the 1 MiB cap). At 10k either costs well under 2 s. The largest real doc
780
+ // seen (openai-go's README, 60 KB) has about 1,500 such characters and no more
781
+ // than 3 unclosed brackets.
782
+ const MAX_UNCLOSED = 1000;
783
+ const MAX_MARKUP = 10000;
784
+ const MARKUP = new Set(['[', ']', '*', '_', '<', '>', '{', '}', '`', '&']);
785
+ function unparseable(markdown) {
786
+ let square = 0;
787
+ let round = 0;
788
+ let unclosed = 0;
789
+ let markup = 0;
790
+ for (let i = 0; i < markdown.length; i += 1) {
791
+ const char = markdown[i];
792
+ if (char !== undefined && MARKUP.has(char))
793
+ markup += 1;
794
+ if (char === '\\')
795
+ i += 1;
796
+ else if (char === '[')
797
+ square += 1;
798
+ else if (char === '(')
799
+ round += 1;
800
+ else if (char === ']') {
801
+ if (square > 0)
802
+ square -= 1;
803
+ else
804
+ unclosed += 1;
805
+ }
806
+ else if (char === ')') {
807
+ if (round > 0)
808
+ round -= 1;
809
+ else
810
+ unclosed += 1;
811
+ }
812
+ }
813
+ return square + round + unclosed > MAX_UNCLOSED || markup > MAX_MARKUP;
814
+ }
815
+ const MAX_DESCRIPTION = 160;
816
+ // The text up to and including the first full stop that ends a sentence, or
817
+ // all of it; untruncated, so it can be compared with the paragraph it came from.
818
+ function leadingSentence(text) {
819
+ if (!text)
820
+ return undefined;
821
+ const end = /\.\s/.exec(text);
822
+ return end ? text.slice(0, end.index + 1) : text;
823
+ }
824
+ function firstSentence(text) {
825
+ const sentence = leadingSentence(text);
826
+ return sentence && truncate(sentence, MAX_DESCRIPTION);
827
+ }
828
+ //# sourceMappingURL=go.js.map