@metamask-previews/platform-api-docs 0.0.0-preview-1275d0fda

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +6 -0
  3. package/LICENSE.APACHE2 +201 -0
  4. package/LICENSE.MIT +21 -0
  5. package/README.md +39 -0
  6. package/dist/cli.cjs +214 -0
  7. package/dist/cli.cjs.map +1 -0
  8. package/dist/cli.d.cts +3 -0
  9. package/dist/cli.d.cts.map +1 -0
  10. package/dist/cli.d.mts +3 -0
  11. package/dist/cli.d.mts.map +1 -0
  12. package/dist/cli.mjs +212 -0
  13. package/dist/cli.mjs.map +1 -0
  14. package/dist/discovery.cjs +53 -0
  15. package/dist/discovery.cjs.map +1 -0
  16. package/dist/discovery.d.cts +22 -0
  17. package/dist/discovery.d.cts.map +1 -0
  18. package/dist/discovery.d.mts +22 -0
  19. package/dist/discovery.d.mts.map +1 -0
  20. package/dist/discovery.mjs +48 -0
  21. package/dist/discovery.mjs.map +1 -0
  22. package/dist/extraction.cjs +745 -0
  23. package/dist/extraction.cjs.map +1 -0
  24. package/dist/extraction.d.cts +40 -0
  25. package/dist/extraction.d.cts.map +1 -0
  26. package/dist/extraction.d.mts +40 -0
  27. package/dist/extraction.d.mts.map +1 -0
  28. package/dist/extraction.mjs +716 -0
  29. package/dist/extraction.mjs.map +1 -0
  30. package/dist/generate.cjs +373 -0
  31. package/dist/generate.cjs.map +1 -0
  32. package/dist/generate.d.cts +37 -0
  33. package/dist/generate.d.cts.map +1 -0
  34. package/dist/generate.d.mts +37 -0
  35. package/dist/generate.d.mts.map +1 -0
  36. package/dist/generate.mjs +346 -0
  37. package/dist/generate.mjs.map +1 -0
  38. package/dist/markdown.cjs +239 -0
  39. package/dist/markdown.cjs.map +1 -0
  40. package/dist/markdown.d.cts +52 -0
  41. package/dist/markdown.d.cts.map +1 -0
  42. package/dist/markdown.d.mts +52 -0
  43. package/dist/markdown.d.mts.map +1 -0
  44. package/dist/markdown.mjs +232 -0
  45. package/dist/markdown.mjs.map +1 -0
  46. package/dist/types.cjs +3 -0
  47. package/dist/types.cjs.map +1 -0
  48. package/dist/types.d.cts +63 -0
  49. package/dist/types.d.cts.map +1 -0
  50. package/dist/types.d.mts +63 -0
  51. package/dist/types.d.mts.map +1 -0
  52. package/dist/types.mjs +2 -0
  53. package/dist/types.mjs.map +1 -0
  54. package/package.json +75 -0
  55. package/site/docusaurus.config.ts +103 -0
  56. package/site/src/css/custom.css +336 -0
  57. package/site/static/fonts/MM-Sans/MM_Sans_Mono-Regular.woff2 +0 -0
  58. package/site/static/img/favicons/favicon-96x96.png +0 -0
  59. package/site/static/img/metamask-fox.svg +12 -0
  60. package/site/static/img/metamask-logo-dark.svg +3 -0
  61. package/site/static/img/metamask-logo.svg +3 -0
  62. package/site/tsconfig.json +13 -0
@@ -0,0 +1,745 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.extractFromFile = exports.extractFromSourceFile = exports.createExtractionProject = void 0;
27
+ const path = __importStar(require("node:path"));
28
+ const ts_morph_1 = require("ts-morph");
29
+ // ---------------------------------------------------------------------------
30
+ // JSDoc utilities
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Convert `{@link X}` references inside a string to plain backtick code spans,
34
+ * and escape any remaining (out-of-backtick) curly braces so the output is
35
+ * safe to drop into MDX.
36
+ *
37
+ * @param text - The raw text to normalize.
38
+ * @returns The text with `@link` resolved and stray braces escaped.
39
+ */
40
+ function escapeJsDocTextForMdx(text) {
41
+ const withLinksResolved = text.replace(/\{@link\s+([^}]+)\}/gu, '`$1`');
42
+ return withLinksResolved.replace(/`[^`]*`|(\{)|(\})/gu, (match, open, close) => {
43
+ if (open) {
44
+ return '\\{';
45
+ }
46
+ if (close) {
47
+ return '\\}';
48
+ }
49
+ return match;
50
+ });
51
+ }
52
+ /**
53
+ * Extract a JSDoc tag's comment text, normalizing whitespace so continuation
54
+ * lines are joined with single spaces. ts-morph returns the raw comment with
55
+ * embedded newlines preserved; we collapse those for the markdown output.
56
+ *
57
+ * @param tag - The JSDoc tag.
58
+ * @returns The flattened comment text.
59
+ */
60
+ function extractJsDocTagComment(tag) {
61
+ // istanbul ignore next: ts-morph returns null for tags without comment text,
62
+ // but every fixture tag we extract from carries a comment.
63
+ return (tag.getCommentText() ?? '').replace(/\s+/gu, ' ').trim();
64
+ }
65
+ /**
66
+ * Strip the conventional `- ` separator from the start of a `@param` tag's
67
+ * comment. JSDoc style is `@param name - description`, and ts-morph hands us
68
+ * back `- description` for the comment. The hyphen is purely cosmetic; we'd
69
+ * rather render the description without it.
70
+ *
71
+ * @param comment - The flattened comment text from a `@param` tag.
72
+ * @returns The comment with any leading `- ` (or `– `, `— `) removed.
73
+ */
74
+ function stripParamSeparator(comment) {
75
+ return comment.replace(/^[-–—]\s*/u, '');
76
+ }
77
+ /**
78
+ * Decompose a node's JSDoc into the parts the rendered docs need:
79
+ *
80
+ * - `description` — the body above the first tag, with `@deprecated` comments
81
+ * appended as `**Deprecated:** <comment>` lines and normalized for MDX
82
+ * (curly braces escaped, `{@link}` resolved),
83
+ * - `params` — every `@param` tag in source order, with name and description,
84
+ * - `returns` — the `@returns` tag's comment, or empty string if absent.
85
+ *
86
+ * Other tags (`@see`, `@throws`, `@template`, `@example`) are dropped.
87
+ *
88
+ * @param node - The AST node to extract JSDoc from.
89
+ * @returns The decomposed JSDoc; empty strings/arrays when the node has no JSDoc.
90
+ */
91
+ function extractJsDoc(node) {
92
+ const jsDocs = node.getJsDocs();
93
+ if (jsDocs.length === 0) {
94
+ return { description: '', params: [], returns: '' };
95
+ }
96
+ const jsDoc = jsDocs[0];
97
+ const descriptionBody = jsDoc.getDescription().trim();
98
+ const deprecatedLines = [];
99
+ const params = [];
100
+ let returns = '';
101
+ for (const tag of jsDoc.getTags()) {
102
+ const tagName = tag.getTagName();
103
+ if (tagName === 'deprecated') {
104
+ const comment = extractJsDocTagComment(tag);
105
+ // istanbul ignore next: bare `@deprecated` (without explanatory text)
106
+ // doesn't appear in messenger JSDoc in practice.
107
+ deprecatedLines.push(comment ? `**Deprecated:** ${comment}` : '**Deprecated:**');
108
+ }
109
+ else if (tagName === 'param' && ts_morph_1.Node.isJSDocParameterTag(tag)) {
110
+ const nameNode = tag.getNameNode();
111
+ // istanbul ignore next: `@param` tags without a name aren't valid JSDoc.
112
+ if (!nameNode) {
113
+ continue;
114
+ }
115
+ params.push({
116
+ name: nameNode.getText(),
117
+ description: escapeJsDocTextForMdx(stripParamSeparator(extractJsDocTagComment(tag))),
118
+ });
119
+ }
120
+ else if (tagName === 'returns' || tagName === 'return') {
121
+ returns = escapeJsDocTextForMdx(extractJsDocTagComment(tag));
122
+ }
123
+ }
124
+ const description = escapeJsDocTextForMdx([descriptionBody, ...deprecatedLines]
125
+ .filter((line) => line.length > 0)
126
+ .join('\n'));
127
+ return { description, params, returns };
128
+ }
129
+ /**
130
+ * Check whether a node has an `@deprecated` JSDoc tag.
131
+ *
132
+ * @param node - The AST node to check.
133
+ * @returns True if the node has an `@deprecated` tag.
134
+ */
135
+ function isDeprecated(node) {
136
+ return node
137
+ .getJsDocs()
138
+ .flatMap((jsDoc) => jsDoc.getTags())
139
+ .some((tag) => tag.getTagName() === 'deprecated');
140
+ }
141
+ // ---------------------------------------------------------------------------
142
+ // Type-resolution helpers (powered by ts-morph's type checker)
143
+ // ---------------------------------------------------------------------------
144
+ /**
145
+ * Resolve a TemplateLiteralTypeNode (e.g. `` `${typeof X}:foo` ``) to its
146
+ * concrete string value, using the type checker to follow `typeof X` to its
147
+ * literal type. Returns null if the type checker can't reduce it to a single
148
+ * string literal.
149
+ *
150
+ * @param node - The template literal type node.
151
+ * @returns The resolved string, or null.
152
+ */
153
+ function resolveTemplateLiteralType(node) {
154
+ const type = node.getType();
155
+ if (type.isStringLiteral()) {
156
+ return type.getLiteralValueOrThrow();
157
+ }
158
+ // istanbul ignore next: messenger fixtures always reduce template literal
159
+ // types to a single string literal via `typeof X` constants.
160
+ return null;
161
+ }
162
+ /**
163
+ * Resolve a capability-type-constructor's first generic argument to a
164
+ * namespace string. Accepts either `typeof X` (resolved via the type checker)
165
+ * or a string literal.
166
+ *
167
+ * @param typeArg - The first generic argument node.
168
+ * @returns The resolved namespace, or null.
169
+ */
170
+ function resolveNamespaceFromTypeArg(typeArg) {
171
+ if (ts_morph_1.Node.isTypeQuery(typeArg)) {
172
+ const type = typeArg.getType();
173
+ if (type.isStringLiteral()) {
174
+ return type.getLiteralValueOrThrow();
175
+ }
176
+ }
177
+ if (ts_morph_1.Node.isLiteralTypeNode(typeArg)) {
178
+ const literal = typeArg.getLiteral();
179
+ // istanbul ignore else: only string literals are valid namespace args.
180
+ if (ts_morph_1.Node.isStringLiteral(literal)) {
181
+ return literal.getLiteralValue();
182
+ }
183
+ }
184
+ // istanbul ignore next: namespace args are always a `typeof X` query or a
185
+ // string literal in valid messenger usage.
186
+ return null;
187
+ }
188
+ /**
189
+ * If `typeNode` is `Class['method']`, resolve `Class` to its declaration
190
+ * (across files if needed via the type checker) and look up the method by
191
+ * name. Returns the method declaration when found.
192
+ *
193
+ * @param typeNode - The handler's type node.
194
+ * @returns The method declaration, or null.
195
+ */
196
+ function resolveIndexedAccessMethod(typeNode) {
197
+ if (!typeNode || !ts_morph_1.Node.isIndexedAccessTypeNode(typeNode)) {
198
+ return null;
199
+ }
200
+ const objectType = typeNode.getObjectTypeNode();
201
+ const indexType = typeNode.getIndexTypeNode();
202
+ // istanbul ignore next: handler indexed-access types in messenger fixtures
203
+ // always reference a class via TypeReference.
204
+ if (!ts_morph_1.Node.isTypeReference(objectType)) {
205
+ return null;
206
+ }
207
+ // istanbul ignore next: the index in `Class['method']` is always a literal
208
+ // type node in valid handler syntax.
209
+ if (!ts_morph_1.Node.isLiteralTypeNode(indexType)) {
210
+ return null;
211
+ }
212
+ const indexLiteral = indexType.getLiteral();
213
+ // The index can be written as `'method'`, `"method"`, or `` `method` ``.
214
+ // The first two land as `StringLiteral`; the bare template literal is a
215
+ // `NoSubstitutionTemplateLiteral`.
216
+ // istanbul ignore next: numeric/boolean indices aren't valid method names.
217
+ if (!ts_morph_1.Node.isStringLiteral(indexLiteral) &&
218
+ !ts_morph_1.Node.isNoSubstitutionTemplateLiteral(indexLiteral)) {
219
+ return null;
220
+ }
221
+ const methodName = indexLiteral.getLiteralValue();
222
+ const classNameNode = objectType.getTypeName();
223
+ // istanbul ignore next: qualified-name class references aren't used in
224
+ // messenger handler types.
225
+ if (!ts_morph_1.Node.isIdentifier(classNameNode)) {
226
+ return null;
227
+ }
228
+ const localSymbol = classNameNode.getSymbol();
229
+ // istanbul ignore next: a referenced class name always resolves to a symbol
230
+ // in a typechecked project.
231
+ if (!localSymbol) {
232
+ return null;
233
+ }
234
+ // Follow the import alias (if any) so we can find the class declaration in
235
+ // its home file.
236
+ const symbol = localSymbol.getAliasedSymbol() ?? localSymbol;
237
+ // istanbul ignore next: a resolved symbol always exposes its declarations
238
+ // array in a typechecked project.
239
+ for (const declaration of symbol.getDeclarations() ?? []) {
240
+ if (ts_morph_1.Node.isClassDeclaration(declaration)) {
241
+ const method = declaration.getMethod(methodName);
242
+ if (method) {
243
+ return method;
244
+ }
245
+ }
246
+ }
247
+ // istanbul ignore next: only reached if the indexed method isn't declared
248
+ // on any of the resolved class declarations, which doesn't happen in valid
249
+ // handler types.
250
+ return null;
251
+ }
252
+ // ---------------------------------------------------------------------------
253
+ // Method info
254
+ // ---------------------------------------------------------------------------
255
+ /**
256
+ * Build a {@link MethodInfo} record for a class method — its textual
257
+ * signature plus the JSDoc description, `@param`, and `@returns` extracted
258
+ * from the method itself.
259
+ *
260
+ * @param method - The method declaration.
261
+ * @returns The method info.
262
+ */
263
+ function buildMethodInfo(method) {
264
+ const signatureParams = method
265
+ .getParameters()
266
+ .map((param) => {
267
+ const paramName = param.getNameNode().getText();
268
+ const optional = param.hasQuestionToken() ? '?' : '';
269
+ const typeNode = param.getTypeNode();
270
+ // istanbul ignore next: handler parameters in messenger fixtures always
271
+ // declare an explicit type.
272
+ const paramType = typeNode ? typeNode.getText() : 'unknown';
273
+ return `${paramName}${optional}: ${paramType}`;
274
+ })
275
+ .join(', ');
276
+ const returnTypeNode = method.getReturnTypeNode();
277
+ // istanbul ignore next: handler class methods in messenger fixtures always
278
+ // declare an explicit return type.
279
+ const returnType = returnTypeNode ? returnTypeNode.getText() : 'void';
280
+ // For async methods, the declared return type already includes `Promise<>`,
281
+ // so we don't need to wrap again.
282
+ const signature = `(${signatureParams}) => ${returnType}`;
283
+ const { description, params, returns } = extractJsDoc(method);
284
+ return { jsDoc: description, params, returns, signature };
285
+ }
286
+ /**
287
+ * Find every `*Messenger` type alias in a source file, parse its
288
+ * `Messenger<Namespace, Actions, Events>` generic arguments, and return the
289
+ * capability-type declarations referenced from the Actions and Events slots
290
+ * — paired with the kind under which they were referenced.
291
+ *
292
+ * The walker follows imported references via ts-morph's symbol resolution,
293
+ * so capability types declared in sibling files (e.g. the auto-generated
294
+ * `*-method-action-types.ts` files) are discovered even though only the
295
+ * `*Messenger` declaration is local to this file.
296
+ *
297
+ * @param sourceFile - The TypeScript source file to scan.
298
+ * @returns The list of locally- and transitively-referenced capability types.
299
+ */
300
+ function collectMessengerCapabilityTypeDeclarations(sourceFile) {
301
+ const declarations = [];
302
+ const seen = new Set();
303
+ for (const typeAlias of sourceFile.getTypeAliases()) {
304
+ if (!typeAlias.getName().endsWith('Messenger')) {
305
+ continue;
306
+ }
307
+ const body = typeAlias.getTypeNode();
308
+ if (!body || !ts_morph_1.Node.isTypeReference(body)) {
309
+ continue;
310
+ }
311
+ const typeArgs = body.getTypeArguments();
312
+ if (typeArgs.length < 3) {
313
+ continue;
314
+ }
315
+ walkCapabilityTypes(typeArgs[1], 'action', declarations, seen);
316
+ walkCapabilityTypes(typeArgs[2], 'event', declarations, seen);
317
+ }
318
+ return declarations;
319
+ }
320
+ /**
321
+ * Walk an Actions or Events type-argument tree and append the underlying
322
+ * capability-type declarations to `output`. Unions are expanded. Type
323
+ * references are resolved via ts-morph's symbol resolution (so imported
324
+ * names are followed across files); a resolved alias whose body is itself a
325
+ * union expands transparently, leaving the intermediate alias unrecorded.
326
+ *
327
+ * A resolved alias whose body is a single non-union type reference (e.g.
328
+ * `type AllowedActions = ConnectivityControllerGetStateAction`) is treated
329
+ * as an opaque re-export — the walk stops there, leaving the target to be
330
+ * documented from its home package by the dedup logic later.
331
+ *
332
+ * For example, given:
333
+ *
334
+ * ```typescript
335
+ * // NetworkController.ts
336
+ * import type { NetworkControllerMethodActions } from './NetworkController-method-action-types';
337
+ * type NetworkControllerActions =
338
+ * | NetworkControllerGetStateAction
339
+ * | NetworkControllerMethodActions;
340
+ * type NetworkControllerMessenger = Messenger<typeof name, NetworkControllerActions, ...>;
341
+ *
342
+ * // NetworkController-method-action-types.ts
343
+ * export type NetworkControllerAddNetworkAction = {
344
+ * type: 'NetworkController:addNetwork';
345
+ * handler: NetworkController['addNetwork'];
346
+ * };
347
+ * // ... more ...
348
+ * export type NetworkControllerMethodActions =
349
+ * | NetworkControllerAddNetworkAction
350
+ * | ...;
351
+ * ```
352
+ *
353
+ * walking the Actions slot yields each individual `NetworkController*Action`
354
+ * declaration — both the local `NetworkControllerGetStateAction` and the
355
+ * cross-file ones reached via `NetworkControllerMethodActions`.
356
+ *
357
+ * @param node - The Actions or Events type-argument node to walk.
358
+ * @param kind - Whether to tag found declarations as 'action' or 'event'.
359
+ * @param output - The list to append discovered declarations to.
360
+ * @param seen - Declaration nodes already visited (prevents cycles and
361
+ * duplicate work).
362
+ */
363
+ function walkCapabilityTypes(node, kind, output, seen) {
364
+ if (ts_morph_1.Node.isUnionTypeNode(node)) {
365
+ for (const member of node.getTypeNodes()) {
366
+ walkCapabilityTypes(member, kind, output, seen);
367
+ }
368
+ return;
369
+ }
370
+ if (!ts_morph_1.Node.isTypeReference(node)) {
371
+ return;
372
+ }
373
+ const nameNode = node.getTypeName();
374
+ // istanbul ignore next: qualified-name references aren't used in messenger
375
+ // generic arguments.
376
+ if (!ts_morph_1.Node.isIdentifier(nameNode)) {
377
+ return;
378
+ }
379
+ // For a TypeReference whose name was imported, `getSymbol()` returns the
380
+ // import alias symbol — its only declaration is the `ImportSpecifier`.
381
+ // `getAliasedSymbol()` follows the alias to the original declaration in
382
+ // the imported file. Use it when present; otherwise fall back to the
383
+ // (already-local) symbol.
384
+ const localSymbol = nameNode.getSymbol();
385
+ // istanbul ignore next: a referenced type name always resolves to a symbol
386
+ // in a typechecked project.
387
+ if (!localSymbol) {
388
+ return;
389
+ }
390
+ const symbol = localSymbol.getAliasedSymbol() ?? localSymbol;
391
+ // istanbul ignore next: a resolved symbol always exposes its declarations
392
+ // array in a typechecked project.
393
+ for (const declaration of symbol.getDeclarations() ?? []) {
394
+ if (seen.has(declaration)) {
395
+ continue;
396
+ }
397
+ seen.add(declaration);
398
+ if (ts_morph_1.Node.isTypeAliasDeclaration(declaration)) {
399
+ const aliasBody = declaration.getTypeNode();
400
+ if (aliasBody && ts_morph_1.Node.isUnionTypeNode(aliasBody)) {
401
+ // Umbrella union — descend through it without documenting the alias.
402
+ walkCapabilityTypes(aliasBody, kind, output, seen);
403
+ continue;
404
+ }
405
+ // A bare TypeReference body with no type arguments (e.g.
406
+ // `type AllowedActions = ConnectivityControllerGetStateAction`) is a
407
+ // plain re-export — leave the target to be documented from its home
408
+ // package, not this one. TypeReferences *with* type arguments are
409
+ // capability-type-constructor invocations (e.g.
410
+ // `ControllerGetStateAction<typeof name, State>`) and are recorded.
411
+ if (aliasBody &&
412
+ ts_morph_1.Node.isTypeReference(aliasBody) &&
413
+ aliasBody.getTypeArguments().length === 0) {
414
+ continue;
415
+ }
416
+ // TypeLiteral body, capability-type-constructor invocation, etc.
417
+ output.push({ statement: declaration, kind });
418
+ }
419
+ else if (ts_morph_1.Node.isInterfaceDeclaration(declaration)) {
420
+ output.push({ statement: declaration, kind });
421
+ }
422
+ }
423
+ }
424
+ // ---------------------------------------------------------------------------
425
+ // Per-statement extraction
426
+ // ---------------------------------------------------------------------------
427
+ /**
428
+ * Extract a single capability-type declaration to an
429
+ * {@link ExtractedMessengerCapabilityType}.
430
+ *
431
+ * @param statement - The type alias or interface declaration.
432
+ * @param kind - Whether this statement is referenced as an action or an event.
433
+ * @param projectPath - Project root, used for computing relative source paths.
434
+ * @returns The extracted capability, or null if no recognized pattern matches.
435
+ */
436
+ function extractFromMessengerCapabilityType(statement, kind, projectPath) {
437
+ const inlineCapability = extractFromInlineMessengerCapabilityType(statement, kind, projectPath);
438
+ if (inlineCapability) {
439
+ return inlineCapability;
440
+ }
441
+ if (ts_morph_1.Node.isTypeAliasDeclaration(statement)) {
442
+ return extractFromCapabilityTypeConstructor(statement, kind, projectPath);
443
+ }
444
+ // istanbul ignore next: interface declarations always have members and
445
+ // therefore an inline shape, so the inline branch above always returns
446
+ // non-null here.
447
+ return null;
448
+ }
449
+ /**
450
+ * Try the inline messenger-capability-type pattern:
451
+ * `{ type: '...'; handler: ... }` (action) or
452
+ * `{ type: '...'; payload: ... }` (event), expressed as either a type alias
453
+ * body or an interface body.
454
+ *
455
+ * @param statement - The type alias or interface declaration.
456
+ * @param kind - The expected kind (action or event).
457
+ * @param projectPath - Project root, used for computing relative source paths.
458
+ * @returns The extracted capability, or null if the shape doesn't match.
459
+ */
460
+ function extractFromInlineMessengerCapabilityType(statement, kind, projectPath) {
461
+ let members;
462
+ if (ts_morph_1.Node.isTypeAliasDeclaration(statement)) {
463
+ const body = statement.getTypeNode();
464
+ if (body && ts_morph_1.Node.isTypeLiteral(body)) {
465
+ members = body.getMembers();
466
+ }
467
+ }
468
+ else {
469
+ members = statement.getMembers();
470
+ }
471
+ if (!members) {
472
+ return null;
473
+ }
474
+ const typeString = resolveInlineTypeString(members);
475
+ // istanbul ignore next: capabilities reachable via the messenger walk
476
+ // always have a resolvable `Namespace:name` type string.
477
+ if (!typeString?.includes(':')) {
478
+ return null;
479
+ }
480
+ const handlerMember = getPropertyMember(members, 'handler');
481
+ const payloadMember = getPropertyMember(members, 'payload');
482
+ const rawSourceMember = kind === 'action' ? handlerMember : payloadMember;
483
+ // istanbul ignore next: actions always have `handler` and events always
484
+ // have `payload`; the messenger walk wouldn't have surfaced this
485
+ // declaration otherwise.
486
+ if (!rawSourceMember) {
487
+ return null;
488
+ }
489
+ const rawSourceTypeNode = rawSourceMember.getTypeNode();
490
+ // istanbul ignore next: property signatures we care about always have an
491
+ // explicit type.
492
+ if (!rawSourceTypeNode) {
493
+ return null;
494
+ }
495
+ let handlerOrPayload = rawSourceTypeNode.getText().trim();
496
+ let { description: jsDoc, params, returns } = extractJsDoc(statement);
497
+ // For actions, if the handler resolves to a class method (`Class['method']`
498
+ // — possibly in another file), inherit its signature plus any JSDoc fields
499
+ // the type alias itself doesn't already provide.
500
+ if (kind === 'action') {
501
+ const resolvedMethod = resolveIndexedAccessMethod(rawSourceTypeNode);
502
+ if (resolvedMethod) {
503
+ const info = buildMethodInfo(resolvedMethod);
504
+ handlerOrPayload = info.signature;
505
+ if (!jsDoc && info.jsDoc) {
506
+ jsDoc = info.jsDoc;
507
+ }
508
+ if (params.length === 0 && info.params.length > 0) {
509
+ params = info.params;
510
+ }
511
+ if (!returns && info.returns) {
512
+ returns = info.returns;
513
+ }
514
+ }
515
+ }
516
+ const sourceFile = statement.getSourceFile();
517
+ return {
518
+ typeName: statement.getName(),
519
+ typeString,
520
+ kind,
521
+ jsDoc,
522
+ params,
523
+ returns,
524
+ handlerOrPayload,
525
+ sourceFile: path.relative(projectPath, sourceFile.getFilePath()),
526
+ line: statement.getStartLineNumber(),
527
+ deprecated: isDeprecated(statement),
528
+ };
529
+ }
530
+ /**
531
+ * Resolve the literal value of an inline shape's `type` property — either a
532
+ * direct string literal or a template literal type with `typeof X`
533
+ * substitutions (resolved via the type checker).
534
+ *
535
+ * @param members - The type elements of the inline shape.
536
+ * @returns The resolved type string, or null if it can't be resolved.
537
+ */
538
+ function resolveInlineTypeString(members) {
539
+ for (const member of members) {
540
+ // istanbul ignore next: capability-type bodies only contain property
541
+ // signatures.
542
+ if (!ts_morph_1.Node.isPropertySignature(member)) {
543
+ continue;
544
+ }
545
+ const memberNameNode = member.getNameNode();
546
+ if (
547
+ // istanbul ignore next: `type` is always a plain identifier.
548
+ !ts_morph_1.Node.isIdentifier(memberNameNode) ||
549
+ memberNameNode.getText() !== 'type') {
550
+ continue;
551
+ }
552
+ const typeNode = member.getTypeNode();
553
+ // istanbul ignore next: a `type` property without an explicit type
554
+ // wouldn't compile.
555
+ if (!typeNode) {
556
+ continue;
557
+ }
558
+ if (ts_morph_1.Node.isLiteralTypeNode(typeNode)) {
559
+ const literal = typeNode.getLiteral();
560
+ // istanbul ignore next: messenger `type` fields are written as
561
+ // quoted string literals in real fixtures; backtick template literals
562
+ // are valid TypeScript but don't appear in practice.
563
+ if (ts_morph_1.Node.isStringLiteral(literal) ||
564
+ ts_morph_1.Node.isNoSubstitutionTemplateLiteral(literal)) {
565
+ return literal.getLiteralValue();
566
+ }
567
+ // istanbul ignore next: numeric/boolean literal types aren't valid as a
568
+ // messenger `type` and don't appear in real fixtures.
569
+ return null;
570
+ }
571
+ if (ts_morph_1.Node.isTemplateLiteralTypeNode(typeNode)) {
572
+ return resolveTemplateLiteralType(typeNode);
573
+ }
574
+ }
575
+ // istanbul ignore next: the inline shape always has a `type` member that
576
+ // produces either a literal or template-literal type.
577
+ return null;
578
+ }
579
+ /**
580
+ * Find the `PropertySignature` named `propName` in a type literal body.
581
+ *
582
+ * @param members - The type literal members to search.
583
+ * @param propName - The property name to find.
584
+ * @returns The property signature, or null.
585
+ */
586
+ function getPropertyMember(members, propName) {
587
+ for (const member of members) {
588
+ // istanbul ignore next: capability-type bodies only contain property
589
+ // signatures.
590
+ if (!ts_morph_1.Node.isPropertySignature(member)) {
591
+ continue;
592
+ }
593
+ const memberNameNode = member.getNameNode();
594
+ if (
595
+ // istanbul ignore next: capability properties always have identifier names.
596
+ !ts_morph_1.Node.isIdentifier(memberNameNode) ||
597
+ memberNameNode.getText() !== propName) {
598
+ continue;
599
+ }
600
+ return member;
601
+ }
602
+ return null;
603
+ }
604
+ /**
605
+ * Try the capability-type-constructor pattern:
606
+ * `ControllerGetStateAction<typeof X, State>` for actions, or
607
+ * `ControllerStateChangeEvent<typeof X, State>` for events.
608
+ *
609
+ * @param statement - The type alias declaration.
610
+ * @param kind - The expected kind (action or event).
611
+ * @param projectPath - Project root, used for computing relative source paths.
612
+ * @returns The extracted capability, or null if the constructor doesn't match.
613
+ */
614
+ function extractFromCapabilityTypeConstructor(statement, kind, projectPath) {
615
+ const aliasBody = statement.getTypeNode();
616
+ // istanbul ignore next: walker only records aliases whose body matches.
617
+ if (!aliasBody || !ts_morph_1.Node.isTypeReference(aliasBody)) {
618
+ return null;
619
+ }
620
+ const nameNode = aliasBody.getTypeName();
621
+ // istanbul ignore next: qualified-name type references aren't used.
622
+ if (!ts_morph_1.Node.isIdentifier(nameNode)) {
623
+ return null;
624
+ }
625
+ const constructorName = nameNode.getText();
626
+ const typeArgs = aliasBody.getTypeArguments();
627
+ if (typeArgs.length < 2) {
628
+ return null;
629
+ }
630
+ const expectedConstructor = kind === 'action'
631
+ ? 'ControllerGetStateAction'
632
+ : 'ControllerStateChangeEvent';
633
+ if (constructorName !== expectedConstructor) {
634
+ return null;
635
+ }
636
+ const namespace = resolveNamespaceFromTypeArg(typeArgs[0]);
637
+ // istanbul ignore next: recognized constructors always have resolvable args.
638
+ if (!namespace) {
639
+ return null;
640
+ }
641
+ const stateArgText = typeArgs[1].getText();
642
+ const { description, params, returns } = extractJsDoc(statement);
643
+ const sourceFile = statement.getSourceFile();
644
+ return {
645
+ typeName: statement.getName(),
646
+ typeString: kind === 'action' ? `${namespace}:getState` : `${namespace}:stateChange`,
647
+ kind,
648
+ jsDoc: description,
649
+ params,
650
+ returns,
651
+ handlerOrPayload: kind === 'action'
652
+ ? `() => ${stateArgText}`
653
+ : `[${stateArgText}, Patch[]]`,
654
+ sourceFile: path.relative(projectPath, sourceFile.getFilePath()),
655
+ line: statement.getStartLineNumber(),
656
+ deprecated: isDeprecated(statement),
657
+ };
658
+ }
659
+ // ---------------------------------------------------------------------------
660
+ // Public entry points
661
+ // ---------------------------------------------------------------------------
662
+ /**
663
+ * Create a ts-morph Project configured for messenger-docs extraction. The
664
+ * caller should add every source file that may be referenced (directly or
665
+ * transitively) before calling {@link extractFromSourceFile}, so the type
666
+ * checker can resolve cross-file references.
667
+ *
668
+ * @returns A new ts-morph Project.
669
+ */
670
+ function createExtractionProject() {
671
+ return new ts_morph_1.Project({
672
+ compilerOptions: {
673
+ allowJs: false,
674
+ noEmit: true,
675
+ // Match the project's permissive defaults — we just need symbol
676
+ // resolution, not full typechecking.
677
+ strict: false,
678
+ skipLibCheck: true,
679
+ // Explicit module options so cross-file symbol resolution works
680
+ // regardless of the host process's tsconfig.
681
+ target: ts_morph_1.ts.ScriptTarget.ESNext,
682
+ module: ts_morph_1.ts.ModuleKind.ESNext,
683
+ moduleResolution: ts_morph_1.ts.ModuleResolutionKind.NodeJs,
684
+ },
685
+ });
686
+ }
687
+ exports.createExtractionProject = createExtractionProject;
688
+ /**
689
+ * Extract every messenger action/event type reachable from a single source
690
+ * file's `*Messenger` declarations.
691
+ *
692
+ * The caller is responsible for ensuring `sourceFile` (plus any files it
693
+ * imports from) belongs to a ts-morph Project so cross-file symbol resolution
694
+ * works.
695
+ *
696
+ * @param sourceFile - The TypeScript source file to extract from.
697
+ * @param projectPath - Project root, used for computing relative source paths.
698
+ * @returns The extracted capability list.
699
+ */
700
+ function extractFromSourceFile(sourceFile, projectPath) {
701
+ const declarations = collectMessengerCapabilityTypeDeclarations(sourceFile);
702
+ if (declarations.length === 0) {
703
+ return [];
704
+ }
705
+ const items = [];
706
+ for (const { statement, kind } of declarations) {
707
+ const item = extractFromMessengerCapabilityType(statement, kind, projectPath);
708
+ if (item) {
709
+ items.push(item);
710
+ }
711
+ }
712
+ return items;
713
+ }
714
+ exports.extractFromSourceFile = extractFromSourceFile;
715
+ /**
716
+ * Convenience wrapper: extract from a single file by path. Loads the file
717
+ * and any sibling files in its parent directory into a fresh Project so
718
+ * relative imports resolve.
719
+ *
720
+ * For batch operations across many files, prefer
721
+ * {@link createExtractionProject} + {@link extractFromSourceFile} so one
722
+ * Project amortizes the type-checker setup across the whole run.
723
+ *
724
+ * @param filePath - The absolute path to the TypeScript file.
725
+ * @param projectPath - Base path for computing relative source paths.
726
+ * @returns A promise that resolves to the extracted capability list.
727
+ */
728
+ async function extractFromFile(filePath, projectPath) {
729
+ const project = createExtractionProject();
730
+ const parentDir = path.dirname(filePath);
731
+ // Load the file's directory so single-hop relative imports resolve.
732
+ project.addSourceFilesAtPaths([
733
+ path.join(parentDir, '**/*.ts'),
734
+ path.join(parentDir, '**/*.d.cts'),
735
+ ]);
736
+ const sourceFile = project.getSourceFile(filePath);
737
+ // istanbul ignore next: the path always exists since the caller just
738
+ // wrote the file or it was scanned from disk.
739
+ if (!sourceFile) {
740
+ return [];
741
+ }
742
+ return extractFromSourceFile(sourceFile, projectPath);
743
+ }
744
+ exports.extractFromFile = extractFromFile;
745
+ //# sourceMappingURL=extraction.cjs.map