@metamask-previews/platform-api-docs 0.1.0-preview-abca9bcea → 0.2.0-preview-0a30e47

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 (74) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/{cli.mjs → cli.js} +10 -40
  5. package/dist/cli.js.map +1 -0
  6. package/dist/{extraction.d.cts → extraction.d.ts} +3 -3
  7. package/dist/extraction.d.ts.map +1 -0
  8. package/dist/{extraction.mjs → extraction.js} +3 -3
  9. package/dist/extraction.js.map +1 -0
  10. package/dist/{generate.d.cts → generate.d.ts} +2 -2
  11. package/dist/generate.d.ts.map +1 -0
  12. package/dist/{generate.mjs → generate.js} +10 -10
  13. package/dist/generate.js.map +1 -0
  14. package/dist/{markdown.d.cts → markdown.d.ts} +2 -2
  15. package/dist/markdown.d.ts.map +1 -0
  16. package/dist/{markdown.mjs → markdown.js} +1 -1
  17. package/dist/markdown.js.map +1 -0
  18. package/dist/{root-messenger-discovery.d.cts → root-messenger-discovery.d.ts} +2 -2
  19. package/dist/root-messenger-discovery.d.ts.map +1 -0
  20. package/dist/{root-messenger-discovery.mjs → root-messenger-discovery.js} +5 -5
  21. package/dist/root-messenger-discovery.js.map +1 -0
  22. package/dist/{ts-project.d.cts → ts-project.d.ts} +2 -2
  23. package/dist/ts-project.d.ts.map +1 -0
  24. package/dist/{ts-project.mjs → ts-project.js} +2 -2
  25. package/dist/ts-project.js.map +1 -0
  26. package/dist/{types.d.cts → types.d.ts} +1 -1
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +2 -0
  29. package/dist/types.js.map +1 -0
  30. package/package.json +12 -8
  31. package/dist/cli.cjs +0 -328
  32. package/dist/cli.cjs.map +0 -1
  33. package/dist/cli.d.cts +0 -3
  34. package/dist/cli.d.cts.map +0 -1
  35. package/dist/cli.d.mts +0 -3
  36. package/dist/cli.d.mts.map +0 -1
  37. package/dist/cli.mjs.map +0 -1
  38. package/dist/extraction.cjs +0 -754
  39. package/dist/extraction.cjs.map +0 -1
  40. package/dist/extraction.d.cts.map +0 -1
  41. package/dist/extraction.d.mts +0 -73
  42. package/dist/extraction.d.mts.map +0 -1
  43. package/dist/extraction.mjs.map +0 -1
  44. package/dist/generate.cjs +0 -505
  45. package/dist/generate.cjs.map +0 -1
  46. package/dist/generate.d.cts.map +0 -1
  47. package/dist/generate.d.mts +0 -70
  48. package/dist/generate.d.mts.map +0 -1
  49. package/dist/generate.mjs.map +0 -1
  50. package/dist/markdown.cjs +0 -239
  51. package/dist/markdown.cjs.map +0 -1
  52. package/dist/markdown.d.cts.map +0 -1
  53. package/dist/markdown.d.mts +0 -52
  54. package/dist/markdown.d.mts.map +0 -1
  55. package/dist/markdown.mjs.map +0 -1
  56. package/dist/root-messenger-discovery.cjs +0 -359
  57. package/dist/root-messenger-discovery.cjs.map +0 -1
  58. package/dist/root-messenger-discovery.d.cts.map +0 -1
  59. package/dist/root-messenger-discovery.d.mts +0 -61
  60. package/dist/root-messenger-discovery.d.mts.map +0 -1
  61. package/dist/root-messenger-discovery.mjs.map +0 -1
  62. package/dist/ts-project.cjs +0 -33
  63. package/dist/ts-project.cjs.map +0 -1
  64. package/dist/ts-project.d.cts.map +0 -1
  65. package/dist/ts-project.d.mts +0 -12
  66. package/dist/ts-project.d.mts.map +0 -1
  67. package/dist/ts-project.mjs.map +0 -1
  68. package/dist/types.cjs +0 -3
  69. package/dist/types.cjs.map +0 -1
  70. package/dist/types.d.cts.map +0 -1
  71. package/dist/types.d.mts +0 -47
  72. package/dist/types.d.mts.map +0 -1
  73. package/dist/types.mjs +0 -2
  74. package/dist/types.mjs.map +0 -1
@@ -1,754 +0,0 @@
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.extractFromSourceFile = exports.extractFromMessengerCapabilityTypeDeclaration = exports.classifyMessengerCapabilityTypeDeclaration = void 0;
27
- const path = __importStar(require("node:path"));
28
- const ts_morph_1 = require("ts-morph");
29
- // ---------------------------------------------------------------------------
30
- // NOTE: `ts-morph` is used heavily in this file to parse and extract
31
- // information from TypeScript files. Although this library is not well
32
- // documented, it wraps the TypeScript AST fairly well, and you can get a good
33
- // sense of the AST by using this website: <https://ts-ast-viewer.com>
34
- // ---------------------------------------------------------------------------
35
- // ---------------------------------------------------------------------------
36
- // JSDoc utilities
37
- // ---------------------------------------------------------------------------
38
- /**
39
- * Convert `{@link X}` references inside a JSDoc comment string to plain
40
- * backtick code spans and escape any remaining (out-of-backtick) curly braces.
41
- * This way the output is safe to render in a MDX document.
42
- *
43
- * @param text - The raw text to normalize.
44
- * @returns The text with `@link` resolved and stray braces escaped.
45
- */
46
- function escapeJsDocTextForMdx(text) {
47
- const withLinksResolved = text.replace(/\{@link\s+([^}]+)\}/gu, '`$1`');
48
- // Escape the characters MDX reads as syntax rather than text: `{` and `}`
49
- // open an expression, and `<` opens a JSX tag — so an unescaped return type
50
- // like `Promise<Foo[]>` fails the site build. Content already inside a code
51
- // span is left alone.
52
- return withLinksResolved.replace(/`[^`]*`|([{}<])/gu, (match, special) => special === undefined ? match : `\\${special}`);
53
- }
54
- /**
55
- * Extract the comment text of a JSDoc tag — the part that comes after the tag
56
- * and any identifier, e.g. "Some param" in "@param foo Some param" —
57
- * normalizing whitespace to a single space so we can better control how it's
58
- * rendered within the site.
59
- *
60
- * @param tag - The JSDoc tag.
61
- * @returns The flattened comment text.
62
- */
63
- function extractJsDocTagComment(tag) {
64
- return (tag.getCommentText() ?? '').replace(/\s+/gu, ' ').trim();
65
- }
66
- /**
67
- * Strip the conventional `- ` separator from the start of a `@param` tag's
68
- * comment.
69
- *
70
- * @param comment - The flattened comment text from a `@param` tag.
71
- * @returns The comment with any leading `- ` (or `– `, `— `) removed.
72
- */
73
- function stripJsDocParamSeparator(comment) {
74
- return comment.replace(/^[-–—]\s*/u, '');
75
- }
76
- /**
77
- * Extract JSDoc from a TypeScript AST node and decompose it into the parts we
78
- * need to render docs:
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 (e.g. a type or a method).
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
- deprecatedLines.push(comment ? `**Deprecated:** ${comment}` : '**Deprecated:**');
106
- }
107
- else if (tagName === 'param' && ts_morph_1.Node.isJSDocParameterTag(tag)) {
108
- const nameNode = tag.getNameNode();
109
- const paramName = nameNode.getText();
110
- if (!paramName) {
111
- continue;
112
- }
113
- const comment = extractJsDocTagComment(tag);
114
- params.push({
115
- name: paramName,
116
- description: escapeJsDocTextForMdx(stripJsDocParamSeparator(comment)),
117
- });
118
- }
119
- else if (tagName === 'returns' || tagName === 'return') {
120
- const comment = extractJsDocTagComment(tag);
121
- returns = escapeJsDocTextForMdx(comment);
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 hasDeprecatedJsDocTag(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
- * Locates the type that represents a method on a class (e.g.
146
- * `Class['method']`), which itself comes from a messenger action handler.
147
- *
148
- * @param typeNode - The node that represents the indexed access.
149
- * @returns The found method declaration, or null.
150
- */
151
- function findClassMethodDeclaration(typeNode) {
152
- // Fundamental check: if we don't have `Class['method']`, we can't do anything.
153
- if (!ts_morph_1.Node.isIndexedAccessTypeNode(typeNode)) {
154
- return null;
155
- }
156
- // The type that represents the class being accessed.
157
- // EXAMPLE:
158
- // FooController['someMethod']
159
- // ^^^^^^^^^^^^^
160
- const objectType = typeNode.getObjectTypeNode();
161
- // The type that represents the property being accessed.
162
- // EXAMPLE:
163
- // FooController['someMethod']
164
- // ^^^^^^^^^^^^
165
- const indexType = typeNode.getIndexTypeNode();
166
- // To access a property on a type, it must be a type we can access properties of.
167
- if (!ts_morph_1.Node.isTypeReference(objectType) ||
168
- !ts_morph_1.Node.isLiteralTypeNode(indexType)) {
169
- return null;
170
- }
171
- const indexLiteral = indexType.getLiteral();
172
- // Names of methods must be static strings; they cannot be template strings.
173
- if (!ts_morph_1.Node.isStringLiteral(indexLiteral) &&
174
- !ts_morph_1.Node.isNoSubstitutionTemplateLiteral(indexLiteral)) {
175
- return null;
176
- }
177
- const methodName = indexLiteral.getLiteralValue();
178
- // Reject qualified-name type names, as we need a plain identifier to
179
- // resolve the symbol.
180
- // EXAMPLE:
181
- // import * as somePackage from '....js';
182
- // somePackage.FooController['someMethod']
183
- // ^^^^^^^^^^^^^^^^^^^^^^^^^
184
- const classNameNode = objectType.getTypeName();
185
- if (!ts_morph_1.Node.isIdentifier(classNameNode)) {
186
- return null;
187
- }
188
- // Since we know we have a type reference, we can assume that we have a symbol.
189
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
190
- const localSymbol = classNameNode.getSymbol();
191
- // If we have a type imported from another file, ensure that when we access
192
- // the declaration, it's the type declaration in the other file, not the
193
- // import declaration in this file.
194
- // EXAMPLE:
195
- // import { FooController } from '@metamask/foo-controller';
196
- // FooController['someMethod']
197
- // ^^^^^^^^^^^^^
198
- const symbol = localSymbol.getAliasedSymbol() ?? localSymbol;
199
- for (const declaration of symbol.getDeclarations()) {
200
- // We must have a class to treat the property on the object type as a
201
- // method.
202
- if (ts_morph_1.Node.isClassDeclaration(declaration)) {
203
- const method = declaration.getMethod(methodName);
204
- if (method) {
205
- return method;
206
- }
207
- }
208
- }
209
- return null;
210
- }
211
- // ---------------------------------------------------------------------------
212
- // Method info
213
- // ---------------------------------------------------------------------------
214
- /**
215
- * Build the textual signature of a class method — its parameter list and
216
- * return type expressed as a TypeScript function type — so a handler that
217
- * references `Class['method']` can be rendered as `(arg: T) => R` instead of
218
- * the bare indexed-access syntax.
219
- *
220
- * @param method - The method declaration.
221
- * @returns The signature, e.g. `(id: number) => Promise<string>`.
222
- */
223
- function buildMethodSignature(method) {
224
- const signatureParams = method
225
- .getParameters()
226
- .map((param) => {
227
- const rest = param.isRestParameter() ? '...' : '';
228
- const paramName = param.getNameNode().getText();
229
- const optional = param.hasQuestionToken() ? '?' : '';
230
- const typeNode = param.getTypeNode();
231
- const paramType = typeNode ? typeNode.getText() : 'unknown';
232
- return `${rest}${paramName}${optional}: ${paramType}`;
233
- })
234
- .join(', ');
235
- const returnTypeNode = method.getReturnTypeNode();
236
- const returnType = returnTypeNode ? returnTypeNode.getText() : 'void';
237
- // For async methods, the declared return type already includes `Promise<>`,
238
- // so we don't need to wrap again.
239
- return `(${signatureParams}) => ${returnType}`;
240
- }
241
- /**
242
- * Tag a capability type declaration with the body shape it has, so the right
243
- * extractor can read it: 'constructor' for a capability-type-constructor
244
- * invocation such as `ControllerGetStateAction<...>`, 'object' otherwise.
245
- *
246
- * Callers must resolve unions and bare type references first, so that the
247
- * declaration reaching here is a leaf.
248
- *
249
- * @param declaration - The type alias or interface to classify.
250
- * @param kind - Whether to tag the declaration as 'action' or 'event'.
251
- * @returns The tagged declaration, or null for a qualified-name reference.
252
- */
253
- function classifyMessengerCapabilityTypeDeclaration(declaration, kind) {
254
- // Interfaces always carry their members directly.
255
- // EXAMPLE:
256
- // interface FooControllerSomeAction { ... }
257
- if (ts_morph_1.Node.isInterfaceDeclaration(declaration)) {
258
- return { bodyShape: 'object', kind, declaration };
259
- }
260
- const body = declaration.getTypeNode();
261
- // A TypeReference body is a capability-type-constructor invocation (e.g.
262
- // `ControllerGetStateAction<typeof name, State>`). Tag it so the constructor
263
- // extractor can read `body` directly without re-checking its shape.
264
- // EXAMPLE:
265
- // type FooControllerSomeAction = ControllerGetStateAction<...>
266
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
267
- if (body && ts_morph_1.Node.isTypeReference(body)) {
268
- // Reject qualified-name constructor type names, as we need a plain
269
- // identifier to match the constructor by name.
270
- // EXAMPLE:
271
- // import * as somePackage from '....js';
272
- // type FooControllerSomeAction = somePackage.ControllerGetStateAction<...>
273
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
274
- const constructorTypeName = body.getTypeName();
275
- if (!ts_morph_1.Node.isIdentifier(constructorTypeName)) {
276
- return null;
277
- }
278
- return {
279
- bodyShape: 'constructor',
280
- kind,
281
- declaration,
282
- body,
283
- typeName: constructorTypeName,
284
- };
285
- }
286
- // Anything else (a type literal, intersection, conditional, …) goes to the
287
- // literal extractor, which knows how to read members off a type literal and
288
- // rejects exotic shapes.
289
- // EXAMPLE:
290
- // type FooControllerSomeAction = { ... }
291
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
292
- return { bodyShape: 'object', kind, declaration };
293
- }
294
- exports.classifyMessengerCapabilityTypeDeclaration = classifyMessengerCapabilityTypeDeclaration;
295
- /**
296
- * Looks for Messenger types in the source file (that is, those that are type
297
- * aliases whose names end with "Messenger"), then extracts the `Actions` and
298
- * `Events` parameters from these types.
299
- *
300
- * @param sourceFile - The TypeScript source file to scan.
301
- * @returns A list of objects that represent messenger types.
302
- */
303
- function findMessengerTypeAliases(sourceFile) {
304
- const parsedMessengerTypeAliases = [];
305
- for (const typeAlias of sourceFile.getTypeAliases()) {
306
- if (!typeAlias.getName().endsWith('Messenger')) {
307
- continue;
308
- }
309
- const body = typeAlias.getTypeNode();
310
- // Basic check
311
- if (!body || !ts_morph_1.Node.isTypeReference(body)) {
312
- continue;
313
- }
314
- const typeArgs = body.getTypeArguments();
315
- // Messenger types always have 3 type parameters
316
- // (e.g. `Messenger<'FooController', Actions, Events>`)
317
- if (typeArgs.length < 3) {
318
- continue;
319
- }
320
- parsedMessengerTypeAliases.push({
321
- actionsTypeParameter: typeArgs[1],
322
- eventsTypeParameter: typeArgs[2],
323
- });
324
- }
325
- return parsedMessengerTypeAliases;
326
- }
327
- /**
328
- * Walks the `Actions` and `Events` type parameters of the given messenger
329
- * types, extracted in a previous step, to find all type declarations (i.e.,
330
- * statements) that represent individual messenger actions or events.
331
- *
332
- * @param parsedMessengerTypeAliases - The list of objects representing
333
- * messenger types, parsed in a previous step.
334
- * @returns The list of type aliases that represent messenger capabilities among
335
- * the given messenger types.
336
- */
337
- function findAllMessengerCapabilityTypeDeclarations(parsedMessengerTypeAliases) {
338
- const allCapabilityTypeDeclarations = [];
339
- let allVisitedTypeDeclarations = new Set();
340
- for (const { actionsTypeParameter, eventsTypeParameter, } of parsedMessengerTypeAliases) {
341
- for (const [typeParameter, kind] of [
342
- [actionsTypeParameter, 'action'],
343
- [eventsTypeParameter, 'event'],
344
- ]) {
345
- const result = recursivelyFindMessengerCapabilityTypeDeclarations(typeParameter, kind, allVisitedTypeDeclarations);
346
- allCapabilityTypeDeclarations.push(...result.capabilityTypeDeclarations);
347
- allVisitedTypeDeclarations = result.visitedTypeDeclarations;
348
- }
349
- }
350
- return allCapabilityTypeDeclarations;
351
- }
352
- /**
353
- * Recursively walks a `ts-morph` AST node — at first the `Actions` or `Events`
354
- * type parameter of a messenger type, and then a node within that parameter —
355
- * to find all type aliases that represent individual messenger actions or
356
- * events, no matter how deeply the type aliases exist in the tree or in which
357
- * file they are located.
358
- *
359
- * @param node - The `ts-morph` AST node to walk.
360
- * @param kind - Whether to tag found type aliases as 'action' or 'event'.
361
- * @param visitedTypeDeclarations - A variable that tracks visited type aliases
362
- * and prevents duplicates.
363
- * @returns The list of extracted messenger capability type aliases as well as
364
- * an updated version of `visitedTypeDeclarations`.
365
- */
366
- function recursivelyFindMessengerCapabilityTypeDeclarations(node, kind, visitedTypeDeclarations) {
367
- const result = {
368
- capabilityTypeDeclarations: [],
369
- visitedTypeDeclarations: new Set([...visitedTypeDeclarations]),
370
- };
371
- // If `node` is a union type, walk each type within it.
372
- // EXAMPLES:
373
- // type Actions = FooControllerSomeAction | FooControllerSomeOtherAction
374
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
375
- // type FooControllerActions = FooControllerSomeAction | FooControllerSomeOtherAction
376
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
377
- if (ts_morph_1.Node.isUnionTypeNode(node)) {
378
- for (const typeNode of node.getTypeNodes()) {
379
- const innerResult = recursivelyFindMessengerCapabilityTypeDeclarations(typeNode, kind, result.visitedTypeDeclarations);
380
- result.capabilityTypeDeclarations.push(...innerResult.capabilityTypeDeclarations);
381
- for (const typeDeclaration of innerResult.visitedTypeDeclarations) {
382
- result.visitedTypeDeclarations.add(typeDeclaration);
383
- }
384
- }
385
- return result;
386
- }
387
- // If `node` is not a type reference, don't walk it.
388
- // EXAMPLE:
389
- // // Bad
390
- // type Actions = { ... }
391
- // ^^^^^^^
392
- // // Good
393
- // type Actions = FooControllerSomeAction;
394
- // ^^^^^^^^^^^^^^^^^^^^^^^
395
- // // Good
396
- // type Actions = ControllerGetStateAction<...>;
397
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
398
- if (!ts_morph_1.Node.isTypeReference(node)) {
399
- return result;
400
- }
401
- const nameNode = node.getTypeName();
402
- // Reject qualified-name type names, as we need a plain identifier to
403
- // resolve the symbol.
404
- // EXAMPLE:
405
- // import * as somePackage from '....js';
406
- // type Actions = somePackage.FooControllerSomeAction;
407
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
408
- if (!ts_morph_1.Node.isIdentifier(nameNode)) {
409
- return result;
410
- }
411
- // Since we know we have a type reference, we can assume that we have a symbol.
412
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
413
- const localSymbol = nameNode.getSymbol();
414
- // If we have a type imported from another file, ensure that when we access
415
- // the declaration, it's the type declaration in the other file, not the
416
- // import declaration in this file.
417
- // EXAMPLE:
418
- // import { FooControllerSomeAction } from '@metamask/foo-controller';
419
- // type Actions = FooControllerSomeAction;
420
- // ^^^^^^^^^^^^^^^^^^^^^^^
421
- const symbol = localSymbol.getAliasedSymbol() ?? localSymbol;
422
- // At this point, we have a type *reference*, but we need to find the type
423
- // *declaration*.
424
- // For instance, if we have `FooControllerSomeAction`, we need to find
425
- // the full `type FooControllerSomeAction = ...` statement.
426
- for (const declaration of symbol.getDeclarations()) {
427
- // Prevent duplicates
428
- if (result.visitedTypeDeclarations.has(declaration)) {
429
- continue;
430
- }
431
- result.visitedTypeDeclarations.add(declaration);
432
- // If we have a type alias, then we have to handle a few scenarios.
433
- // EXAMPLES:
434
- // type FooControllerMethodActions = ...
435
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
436
- // type FooControllerSomeAction = ...
437
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
438
- if (ts_morph_1.Node.isTypeAliasDeclaration(declaration)) {
439
- const body = declaration.getTypeNode();
440
- // If the body is a union type or a plain type reference (not a utility
441
- // type), walk it.
442
- // EXAMPLES:
443
- // type FooControllerMethodActions = FooControllerSomeAction | FooControllerSomeOtherAction
444
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
445
- // type DelegationControllerMethodActions = DelegationControllerSignDelegationAction
446
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
447
- if (body &&
448
- (ts_morph_1.Node.isUnionTypeNode(body) ||
449
- (ts_morph_1.Node.isTypeReference(body) &&
450
- body.getTypeArguments().length === 0))) {
451
- const innerResult = recursivelyFindMessengerCapabilityTypeDeclarations(body, kind, result.visitedTypeDeclarations);
452
- result.capabilityTypeDeclarations.push(...innerResult.capabilityTypeDeclarations);
453
- for (const typeDeclaration of innerResult.visitedTypeDeclarations) {
454
- result.visitedTypeDeclarations.add(typeDeclaration);
455
- }
456
- continue;
457
- }
458
- // Everything else is a leaf capability type — a capability-type-
459
- // constructor invocation (e.g. `ControllerGetStateAction<typeof name,
460
- // State>`) or a literal object type. Tag it so the matching extractor
461
- // can read it without re-checking its shape.
462
- // EXAMPLES:
463
- // type FooControllerSomeAction = ControllerGetStateAction<...>
464
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
465
- // type FooControllerSomeAction = { ... }
466
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
467
- const classified = classifyMessengerCapabilityTypeDeclaration(declaration, kind);
468
- if (classified) {
469
- result.capabilityTypeDeclarations.push(classified);
470
- }
471
- }
472
- // Interfaces always carry their members directly — tag for the literal
473
- // extractor.
474
- // EXAMPLE:
475
- // interface FooControllerSomeAction { ... }
476
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
477
- else if (ts_morph_1.Node.isInterfaceDeclaration(declaration)) {
478
- result.capabilityTypeDeclarations.push({
479
- bodyShape: 'object',
480
- kind,
481
- declaration,
482
- });
483
- }
484
- }
485
- return result;
486
- }
487
- // ---------------------------------------------------------------------------
488
- // Per-statement extraction
489
- // ---------------------------------------------------------------------------
490
- /**
491
- * Given the declaration of a messenger capability type, extract information
492
- * about it (action/event type string, handler/payload arguments and return
493
- * type, etc.)
494
- *
495
- * @param capabilityTypeDeclaration - The statement that declared the type for a
496
- * messenger action or event, extracted in a previous step.
497
- * @param projectPath - Project root, used for computing relative source paths.
498
- * @returns Information that may be extracted from the messenger capability type
499
- * (may be `null` if the type is ineligible for extraction).
500
- */
501
- function extractFromMessengerCapabilityTypeDeclaration(capabilityTypeDeclaration, projectPath) {
502
- if (capabilityTypeDeclaration.bodyShape === 'constructor') {
503
- return tryToExtractFromCapabilityTypeConstructor(capabilityTypeDeclaration, projectPath);
504
- }
505
- return tryToExtractFromMessengerCapabilityTypeLiteral(capabilityTypeDeclaration, projectPath);
506
- }
507
- exports.extractFromMessengerCapabilityTypeDeclaration = extractFromMessengerCapabilityTypeDeclaration;
508
- /**
509
- * If a messenger capability type is a type alias or interface and its body is
510
- * a literal object type — i.e. one of:
511
- *
512
- * - `{ type: '...'; handler: ... }` (action)
513
- * - `{ type: '...'; payload: ... }` (event)
514
- *
515
- * then this function extracts information about the type (action/event type
516
- * string, handler/payload arguments and return type, etc.)
517
- *
518
- * @param capabilityTypeDeclaration - The statement that declared the type for a
519
- * messenger action or event, extracted in a previous step.
520
- * @param projectPath - Project root, used for computing relative source paths.
521
- * @returns The extracted capability packet, or null if the shape of the type
522
- * doesn't match.
523
- */
524
- function tryToExtractFromMessengerCapabilityTypeLiteral(capabilityTypeDeclaration, projectPath) {
525
- const { declaration, kind } = capabilityTypeDeclaration;
526
- // We must have a object type alias or an interface, and the body must not be empty.
527
- // EXAMPLES:
528
- // // Good
529
- // type FooControllerSomeAction = {
530
- // type: 'FooController:getState';
531
- // handler: FooController['getState'];
532
- // }
533
- // // Good
534
- // interface FooControllerSomeAction {
535
- // type: 'FooController:getState';
536
- // handler: FooController['getState'];
537
- // }
538
- // // Bad
539
- // type FooControllerSomeAction = {};
540
- // // Bad
541
- // interface FooControllerSomeAction {};
542
- let members;
543
- if (ts_morph_1.Node.isTypeAliasDeclaration(declaration)) {
544
- const body = declaration.getTypeNode();
545
- if (body && ts_morph_1.Node.isTypeLiteral(body)) {
546
- members = body.getMembers();
547
- }
548
- }
549
- else {
550
- members = declaration.getMembers();
551
- }
552
- if (!members) {
553
- return null;
554
- }
555
- const propertySignatures = members.filter(ts_morph_1.Node.isPropertySignature.bind(ts_morph_1.Node));
556
- // Actions and events must have a `type`, and it must be a string.
557
- const typeString = getMessengerCapabilityTypeString(propertySignatures);
558
- if (!typeString) {
559
- return null;
560
- }
561
- // Actions must have a `handler`, and events must have a `payload`.
562
- const handlerOrPayloadProperty = findProperty(propertySignatures, kind === 'action' ? 'handler' : 'payload');
563
- if (!handlerOrPayloadProperty) {
564
- return null;
565
- }
566
- const handlerOrPayloadPropertyTypeNode =
567
- // We can assume the property has a type; otherwise it wouldn't compile.
568
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
569
- handlerOrPayloadProperty.getTypeNode();
570
- let handlerOrPayloadSignature = handlerOrPayloadPropertyTypeNode
571
- .getText()
572
- .trim();
573
- const { description: jsDoc, params, returns } = extractJsDoc(declaration);
574
- // For actions that represent methods (e.g. `Class['method']`), walk the
575
- // handler type to find the underlying handler signature
576
- // (e.g. `(id: number) => Promise<string>`).
577
- if (kind === 'action') {
578
- const methodDeclaration = findClassMethodDeclaration(handlerOrPayloadPropertyTypeNode);
579
- if (methodDeclaration) {
580
- handlerOrPayloadSignature = buildMethodSignature(methodDeclaration);
581
- }
582
- }
583
- const sourceFile = declaration.getSourceFile();
584
- return {
585
- typeName: declaration.getName(),
586
- typeString,
587
- kind,
588
- jsDoc,
589
- params,
590
- returns,
591
- handlerOrPayload: handlerOrPayloadSignature,
592
- sourceFile: path.relative(projectPath, sourceFile.getFilePath()),
593
- line: declaration.getStartLineNumber(),
594
- deprecated: hasDeprecatedJsDocTag(declaration),
595
- };
596
- }
597
- /**
598
- * Searches the property signatures of a messenger capability type alias or
599
- * interface to find the value of the `type` property, and then resolves it to a
600
- * string (assuming it is already a string or a resolvable template literal).
601
- *
602
- * @param capabilityTypeProperties - The property signatures of the messenger
603
- * capability type.
604
- * @returns The extracted capability type string, or null if `type` cannot be
605
- * found in the members or it is an unexpected node.
606
- */
607
- function getMessengerCapabilityTypeString(capabilityTypeProperties) {
608
- const typeProperty = findProperty(capabilityTypeProperties, 'type');
609
- if (!typeProperty) {
610
- return null;
611
- }
612
- // A `type` property without an explicit type wouldn't compile.
613
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
614
- const typeNode = typeProperty.getTypeNode();
615
- // Ask the type checker to resolve the value of `type`. We're looking for
616
- // `type` to be either a string literal or template literal.
617
- //
618
- // EXAMPLES:
619
- // type FooControllerSomeAction = {
620
- // type: 'FooController:someAction';
621
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^
622
- // }
623
- // type FooControllerSomeAction = {
624
- // type: `FooController:someAction`;
625
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^
626
- // }
627
- // type FooControllerSomeAction = {
628
- // type: `${typeof CONTROLLER_NAME}:someAction`;
629
- // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
630
- // }
631
- const resolvedType = typeNode.getType();
632
- if (resolvedType.isStringLiteral()) {
633
- // Type assertion: There aren't any type guards we can use to narrow this
634
- // type further.
635
- const literalValue = resolvedType.getLiteralValueOrThrow();
636
- // Messenger action/event types need to be namespaced.
637
- if (literalValue.includes(':')) {
638
- return literalValue;
639
- }
640
- }
641
- return null;
642
- }
643
- /**
644
- * Finds a specific property in a list of property signatures for an object
645
- * type.
646
- *
647
- * @param propertySignatures - The property signatures of the messenger
648
- * capability type.
649
- * @param name - The property name to find.
650
- * @returns The property signature, or null.
651
- */
652
- function findProperty(propertySignatures, name) {
653
- for (const property of propertySignatures) {
654
- const propertyNameNode = property.getNameNode();
655
- if (!ts_morph_1.Node.isIdentifier(propertyNameNode) ||
656
- propertyNameNode.getText() !== name) {
657
- continue;
658
- }
659
- return property;
660
- }
661
- return null;
662
- }
663
- /**
664
- * If a messenger capability type is a type alias for either the
665
- * `ControllerGetStateAction` or `ControllerStateChangeEvent` type constructors,
666
- * then this function extracts information about the type (action/event type
667
- * string, handler/payload arguments and return type, etc.)
668
- *
669
- * @param capabilityTypeDeclaration - The statement that declared the type for a
670
- * messenger action or event, extracted in a previous step.
671
- * @param projectPath - Project root, used for computing relative source paths.
672
- * @returns The extracted capability packet, or null if the shape of the type
673
- * doesn't match.
674
- */
675
- function tryToExtractFromCapabilityTypeConstructor(capabilityTypeDeclaration, projectPath) {
676
- const { declaration, kind, body, typeName } = capabilityTypeDeclaration;
677
- // The name of the utility type should be either `ControllerGetStateAction`
678
- // (for actions) or `ControllerStateChangeEvent` (for events).
679
- // EXAMPLES:
680
- // type FooControllerSomeAction = ControllerGetStateAction<...>
681
- // type FooControllerSomeEvent = ControllerStateChangeEvent<...>
682
- const expectedConstructor = kind === 'action'
683
- ? 'ControllerGetStateAction'
684
- : 'ControllerStateChangeEvent';
685
- if (typeName.getText() !== expectedConstructor) {
686
- return null;
687
- }
688
- // The utility type should take two parameters.
689
- // EXAMPLES:
690
- // type FooControllerSomeAction = ControllerGetStateAction<..., ...>
691
- // type FooControllerSomeEvent = ControllerStateChangeEvent<..., ...>
692
- const typeArgs = body.getTypeArguments();
693
- if (typeArgs.length < 2) {
694
- return null;
695
- }
696
- const namespaceArgType = typeArgs[0].getType();
697
- // The first parameter should be a string literal.
698
- // EXAMPLES:
699
- // type FooControllerSomeAction = ControllerGetStateAction<'FooController', ...>
700
- // type FooControllerSomeAction = ControllerGetStateAction<typeof CONTROLLER_NAME, ...>
701
- if (!namespaceArgType.isStringLiteral()) {
702
- return null;
703
- }
704
- // Type assertion: There aren't any type guards we can use to narrow this type
705
- // further.
706
- const namespace = namespaceArgType.getLiteralValueOrThrow();
707
- const typeString = kind === 'action' ? `${namespace}:getState` : `${namespace}:stateChange`;
708
- const stateArgText = typeArgs[1].getText();
709
- const handlerOrPayload = kind === 'action' ? `() => ${stateArgText}` : `[${stateArgText}, Patch[]]`;
710
- const { description, params, returns } = extractJsDoc(declaration);
711
- const sourceFile = declaration.getSourceFile();
712
- return {
713
- typeName: declaration.getName(),
714
- typeString,
715
- kind,
716
- jsDoc: description,
717
- params,
718
- returns,
719
- handlerOrPayload,
720
- sourceFile: path.relative(projectPath, sourceFile.getFilePath()),
721
- line: declaration.getStartLineNumber(),
722
- deprecated: hasDeprecatedJsDocTag(declaration),
723
- };
724
- }
725
- // ---------------------------------------------------------------------------
726
- // Public entry points
727
- // ---------------------------------------------------------------------------
728
- /**
729
- * Extract information (action/event type string, handler/payload arguments and
730
- * return type, etc.) about every messenger action or event type which is
731
- * reachable through all of a source file's `*Messenger` type declarations.
732
- *
733
- * The caller is responsible for ensuring `sourceFile` (plus any files it
734
- * imports from) belongs to a `ts-morph` Project so cross-file symbol resolution
735
- * works.
736
- *
737
- * @param sourceFile - The TypeScript source file to extract from.
738
- * @param projectPath - Project root, used for computing relative source paths.
739
- * @returns The extracted information about actions and events.
740
- */
741
- function extractFromSourceFile(sourceFile, projectPath) {
742
- const messengerTypeAliases = findMessengerTypeAliases(sourceFile);
743
- const capabilityTypeDeclarations = findAllMessengerCapabilityTypeDeclarations(messengerTypeAliases);
744
- const messengerCapabilityPackets = [];
745
- for (const capabilityTypeDeclaration of capabilityTypeDeclarations) {
746
- const messengerCapabilityPacket = extractFromMessengerCapabilityTypeDeclaration(capabilityTypeDeclaration, projectPath);
747
- if (messengerCapabilityPacket) {
748
- messengerCapabilityPackets.push(messengerCapabilityPacket);
749
- }
750
- }
751
- return messengerCapabilityPackets;
752
- }
753
- exports.extractFromSourceFile = extractFromSourceFile;
754
- //# sourceMappingURL=extraction.cjs.map