@metamask-previews/platform-api-docs 0.0.0-preview-1275d0fda → 0.0.0-preview-6235c3779
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extraction.cjs +357 -317
- package/dist/extraction.cjs.map +1 -1
- package/dist/extraction.d.cts +8 -7
- package/dist/extraction.d.cts.map +1 -1
- package/dist/extraction.d.mts +8 -7
- package/dist/extraction.d.mts.map +1 -1
- package/dist/extraction.mjs +357 -317
- package/dist/extraction.mjs.map +1 -1
- package/dist/generate.cjs.map +1 -1
- package/dist/generate.mjs.map +1 -1
- package/dist/markdown.cjs.map +1 -1
- package/dist/markdown.d.cts +2 -2
- package/dist/markdown.d.cts.map +1 -1
- package/dist/markdown.d.mts +2 -2
- package/dist/markdown.d.mts.map +1 -1
- package/dist/markdown.mjs.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +5 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +5 -5
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs.map +1 -1
- package/package.json +2 -2
- package/site/tsconfig.json +2 -4
package/dist/extraction.cjs
CHANGED
|
@@ -27,12 +27,18 @@ exports.extractFromFile = exports.extractFromSourceFile = exports.createExtracti
|
|
|
27
27
|
const path = __importStar(require("node:path"));
|
|
28
28
|
const ts_morph_1 = require("ts-morph");
|
|
29
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
|
+
// ---------------------------------------------------------------------------
|
|
30
36
|
// JSDoc utilities
|
|
31
37
|
// ---------------------------------------------------------------------------
|
|
32
38
|
/**
|
|
33
|
-
* Convert `{@link X}` references inside a string to plain
|
|
34
|
-
* and escape any remaining (out-of-backtick) curly braces
|
|
35
|
-
* safe to
|
|
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.
|
|
36
42
|
*
|
|
37
43
|
* @param text - The raw text to normalize.
|
|
38
44
|
* @returns The text with `@link` resolved and stray braces escaped.
|
|
@@ -50,32 +56,30 @@ function escapeJsDocTextForMdx(text) {
|
|
|
50
56
|
});
|
|
51
57
|
}
|
|
52
58
|
/**
|
|
53
|
-
* Extract a JSDoc tag
|
|
54
|
-
*
|
|
55
|
-
*
|
|
59
|
+
* Extract the comment text of a JSDoc tag — the part that comes after the tag
|
|
60
|
+
* and any identifier, e.g. "Some param" in "@param foo Some param" —
|
|
61
|
+
* normalizing whitespace to a single space so we can better control how it's
|
|
62
|
+
* rendered within the site.
|
|
56
63
|
*
|
|
57
64
|
* @param tag - The JSDoc tag.
|
|
58
65
|
* @returns The flattened comment text.
|
|
59
66
|
*/
|
|
60
67
|
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
68
|
return (tag.getCommentText() ?? '').replace(/\s+/gu, ' ').trim();
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
66
71
|
* Strip the conventional `- ` separator from the start of a `@param` tag's
|
|
67
|
-
* comment.
|
|
68
|
-
* back `- description` for the comment. The hyphen is purely cosmetic; we'd
|
|
69
|
-
* rather render the description without it.
|
|
72
|
+
* comment.
|
|
70
73
|
*
|
|
71
74
|
* @param comment - The flattened comment text from a `@param` tag.
|
|
72
75
|
* @returns The comment with any leading `- ` (or `– `, `— `) removed.
|
|
73
76
|
*/
|
|
74
|
-
function
|
|
77
|
+
function stripJsDocParamSeparator(comment) {
|
|
75
78
|
return comment.replace(/^[-–—]\s*/u, '');
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
78
|
-
*
|
|
81
|
+
* Extract JSDoc from a TypeScript AST node and decompose it into the parts we
|
|
82
|
+
* need to render docs:
|
|
79
83
|
*
|
|
80
84
|
* - `description` — the body above the first tag, with `@deprecated` comments
|
|
81
85
|
* appended as `**Deprecated:** <comment>` lines and normalized for MDX
|
|
@@ -85,7 +89,7 @@ function stripParamSeparator(comment) {
|
|
|
85
89
|
*
|
|
86
90
|
* Other tags (`@see`, `@throws`, `@template`, `@example`) are dropped.
|
|
87
91
|
*
|
|
88
|
-
* @param node - The AST node to extract JSDoc from.
|
|
92
|
+
* @param node - The AST node to extract JSDoc from (e.g. a type or a method).
|
|
89
93
|
* @returns The decomposed JSDoc; empty strings/arrays when the node has no JSDoc.
|
|
90
94
|
*/
|
|
91
95
|
function extractJsDoc(node) {
|
|
@@ -102,23 +106,23 @@ function extractJsDoc(node) {
|
|
|
102
106
|
const tagName = tag.getTagName();
|
|
103
107
|
if (tagName === 'deprecated') {
|
|
104
108
|
const comment = extractJsDocTagComment(tag);
|
|
105
|
-
// istanbul ignore next: bare `@deprecated` (without explanatory text)
|
|
106
|
-
// doesn't appear in messenger JSDoc in practice.
|
|
107
109
|
deprecatedLines.push(comment ? `**Deprecated:** ${comment}` : '**Deprecated:**');
|
|
108
110
|
}
|
|
109
111
|
else if (tagName === 'param' && ts_morph_1.Node.isJSDocParameterTag(tag)) {
|
|
110
112
|
const nameNode = tag.getNameNode();
|
|
111
|
-
|
|
112
|
-
if (!
|
|
113
|
+
const paramName = nameNode.getText();
|
|
114
|
+
if (!paramName) {
|
|
113
115
|
continue;
|
|
114
116
|
}
|
|
117
|
+
const comment = extractJsDocTagComment(tag);
|
|
115
118
|
params.push({
|
|
116
|
-
name:
|
|
117
|
-
description: escapeJsDocTextForMdx(
|
|
119
|
+
name: paramName,
|
|
120
|
+
description: escapeJsDocTextForMdx(stripJsDocParamSeparator(comment)),
|
|
118
121
|
});
|
|
119
122
|
}
|
|
120
123
|
else if (tagName === 'returns' || tagName === 'return') {
|
|
121
|
-
|
|
124
|
+
const comment = extractJsDocTagComment(tag);
|
|
125
|
+
returns = escapeJsDocTextForMdx(comment);
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
const description = escapeJsDocTextForMdx([descriptionBody, ...deprecatedLines]
|
|
@@ -132,7 +136,7 @@ function extractJsDoc(node) {
|
|
|
132
136
|
* @param node - The AST node to check.
|
|
133
137
|
* @returns True if the node has an `@deprecated` tag.
|
|
134
138
|
*/
|
|
135
|
-
function
|
|
139
|
+
function hasDeprecatedJsDocTag(node) {
|
|
136
140
|
return node
|
|
137
141
|
.getJsDocs()
|
|
138
142
|
.flatMap((jsDoc) => jsDoc.getTags())
|
|
@@ -141,50 +145,6 @@ function isDeprecated(node) {
|
|
|
141
145
|
// ---------------------------------------------------------------------------
|
|
142
146
|
// Type-resolution helpers (powered by ts-morph's type checker)
|
|
143
147
|
// ---------------------------------------------------------------------------
|
|
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
148
|
/**
|
|
189
149
|
* If `typeNode` is `Class['method']`, resolve `Class` to its declaration
|
|
190
150
|
* (across files if needed via the type checker) and look up the method by
|
|
@@ -267,15 +227,11 @@ function buildMethodInfo(method) {
|
|
|
267
227
|
const paramName = param.getNameNode().getText();
|
|
268
228
|
const optional = param.hasQuestionToken() ? '?' : '';
|
|
269
229
|
const typeNode = param.getTypeNode();
|
|
270
|
-
// istanbul ignore next: handler parameters in messenger fixtures always
|
|
271
|
-
// declare an explicit type.
|
|
272
230
|
const paramType = typeNode ? typeNode.getText() : 'unknown';
|
|
273
231
|
return `${paramName}${optional}: ${paramType}`;
|
|
274
232
|
})
|
|
275
233
|
.join(', ');
|
|
276
234
|
const returnTypeNode = method.getReturnTypeNode();
|
|
277
|
-
// istanbul ignore next: handler class methods in messenger fixtures always
|
|
278
|
-
// declare an explicit return type.
|
|
279
235
|
const returnType = returnTypeNode ? returnTypeNode.getText() : 'void';
|
|
280
236
|
// For async methods, the declared return type already includes `Promise<>`,
|
|
281
237
|
// so we don't need to wrap again.
|
|
@@ -284,224 +240,280 @@ function buildMethodInfo(method) {
|
|
|
284
240
|
return { jsDoc: description, params, returns, signature };
|
|
285
241
|
}
|
|
286
242
|
/**
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
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.
|
|
243
|
+
* Looks for Messenger types in the source file (that is, those that are type
|
|
244
|
+
* aliases whose names end with "Messenger"), then extracts the `Actions` and
|
|
245
|
+
* `Events` parameters from these types.
|
|
296
246
|
*
|
|
297
247
|
* @param sourceFile - The TypeScript source file to scan.
|
|
298
|
-
* @returns
|
|
248
|
+
* @returns A list of objects that represent messenger types.
|
|
299
249
|
*/
|
|
300
|
-
function
|
|
301
|
-
const
|
|
302
|
-
const seen = new Set();
|
|
250
|
+
function findMessengerTypeAliases(sourceFile) {
|
|
251
|
+
const parsedMessengerTypeAliases = [];
|
|
303
252
|
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
304
253
|
if (!typeAlias.getName().endsWith('Messenger')) {
|
|
305
254
|
continue;
|
|
306
255
|
}
|
|
307
256
|
const body = typeAlias.getTypeNode();
|
|
257
|
+
// Basic check
|
|
308
258
|
if (!body || !ts_morph_1.Node.isTypeReference(body)) {
|
|
309
259
|
continue;
|
|
310
260
|
}
|
|
311
261
|
const typeArgs = body.getTypeArguments();
|
|
262
|
+
// Messenger types always have 3 type parameters
|
|
263
|
+
// (e.g. `Messenger<'FooController', Actions, Events>`)
|
|
312
264
|
if (typeArgs.length < 3) {
|
|
313
265
|
continue;
|
|
314
266
|
}
|
|
315
|
-
|
|
316
|
-
|
|
267
|
+
parsedMessengerTypeAliases.push({
|
|
268
|
+
actionsTypeParameter: typeArgs[1],
|
|
269
|
+
eventsTypeParameter: typeArgs[2],
|
|
270
|
+
});
|
|
317
271
|
}
|
|
318
|
-
return
|
|
272
|
+
return parsedMessengerTypeAliases;
|
|
319
273
|
}
|
|
320
274
|
/**
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
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:
|
|
275
|
+
* Walks the `Actions` and `Events` type parameters of the given messenger
|
|
276
|
+
* types, extracted in a previous step, to find all type declarations (i.e.,
|
|
277
|
+
* statements) that represent individual messenger actions or events.
|
|
333
278
|
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
*
|
|
279
|
+
* @param parsedMessengerTypeAliases - The list of objects representing
|
|
280
|
+
* messenger types, parsed in a previous step.
|
|
281
|
+
* @returns The list of type aliases that represent messenger capabilities among
|
|
282
|
+
* the given messenger types.
|
|
283
|
+
*/
|
|
284
|
+
function findAllMessengerCapabilityTypeDeclarations(parsedMessengerTypeAliases) {
|
|
285
|
+
const allCapabilityTypeDeclarations = [];
|
|
286
|
+
let allVisitedTypeDeclarations = new Set();
|
|
287
|
+
for (const { actionsTypeParameter, eventsTypeParameter, } of parsedMessengerTypeAliases) {
|
|
288
|
+
for (const [typeParameter, kind] of [
|
|
289
|
+
[actionsTypeParameter, 'action'],
|
|
290
|
+
[eventsTypeParameter, 'event'],
|
|
291
|
+
]) {
|
|
292
|
+
const result = recursivelyFindMessengerCapabilityTypeDeclarations(typeParameter, kind, allVisitedTypeDeclarations);
|
|
293
|
+
allCapabilityTypeDeclarations.push(...result.capabilityTypeDeclarations);
|
|
294
|
+
allVisitedTypeDeclarations = result.visitedTypeDeclarations;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return allCapabilityTypeDeclarations;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Recursively walks a `ts-morph` AST node — at first the `Actions` or `Events`
|
|
301
|
+
* type parameter of a messenger type, and then a node within that parameter —
|
|
302
|
+
* to find all type aliases that represent individual messenger actions or
|
|
303
|
+
* events, no matter how deeply the type aliases exist in the tree or in which
|
|
304
|
+
* file they are located.
|
|
356
305
|
*
|
|
357
|
-
* @param node - The
|
|
358
|
-
* @param kind - Whether to tag found
|
|
359
|
-
* @param
|
|
360
|
-
*
|
|
361
|
-
*
|
|
306
|
+
* @param node - The `ts-morph` AST node to walk.
|
|
307
|
+
* @param kind - Whether to tag found type aliases as 'action' or 'event'.
|
|
308
|
+
* @param visitedTypeDeclarations - A variable that tracks visited type aliases
|
|
309
|
+
* and prevents duplicates.
|
|
310
|
+
* @returns The list of extracted messenger capability type aliases as well as
|
|
311
|
+
* an updated version of `visitedTypeDeclarations`.
|
|
362
312
|
*/
|
|
363
|
-
function
|
|
313
|
+
function recursivelyFindMessengerCapabilityTypeDeclarations(node, kind, visitedTypeDeclarations) {
|
|
314
|
+
const result = {
|
|
315
|
+
capabilityTypeDeclarations: [],
|
|
316
|
+
visitedTypeDeclarations: new Set([...visitedTypeDeclarations]),
|
|
317
|
+
};
|
|
318
|
+
// If `node` is a union type, walk each type within it.
|
|
319
|
+
// EXAMPLES:
|
|
320
|
+
// type Actions = FooControllerSomeAction | FooControllerSomeOtherAction
|
|
321
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
322
|
+
// type FooControllerActions = FooControllerSomeAction | FooControllerSomeOtherAction
|
|
323
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
364
324
|
if (ts_morph_1.Node.isUnionTypeNode(node)) {
|
|
365
|
-
for (const
|
|
366
|
-
|
|
325
|
+
for (const typeNode of node.getTypeNodes()) {
|
|
326
|
+
const innerResult = recursivelyFindMessengerCapabilityTypeDeclarations(typeNode, kind, result.visitedTypeDeclarations);
|
|
327
|
+
result.capabilityTypeDeclarations.push(...innerResult.capabilityTypeDeclarations);
|
|
328
|
+
for (const typeDeclaration of innerResult.visitedTypeDeclarations) {
|
|
329
|
+
result.visitedTypeDeclarations.add(typeDeclaration);
|
|
330
|
+
}
|
|
367
331
|
}
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
// If `node` is not a type reference, don't walk it.
|
|
335
|
+
// EXAMPLE:
|
|
336
|
+
// // Bad
|
|
337
|
+
// type Actions = { ... }
|
|
338
|
+
// ^^^^^^^
|
|
339
|
+
// // Good
|
|
340
|
+
// type Actions = FooControllerSomeAction;
|
|
341
|
+
// // Good
|
|
342
|
+
// type Actions = ControllerGetStateAction<...>;
|
|
370
343
|
if (!ts_morph_1.Node.isTypeReference(node)) {
|
|
371
|
-
return;
|
|
344
|
+
return result;
|
|
372
345
|
}
|
|
373
346
|
const nameNode = node.getTypeName();
|
|
374
|
-
//
|
|
375
|
-
//
|
|
347
|
+
// Reject qualified-name type references, as we can't follow those.
|
|
348
|
+
// EXAMPLE:
|
|
349
|
+
// import * as somePackage from '...';
|
|
350
|
+
// type Actions = somePackage.FooControllerSomeAction;
|
|
351
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
376
352
|
if (!ts_morph_1.Node.isIdentifier(nameNode)) {
|
|
377
|
-
return;
|
|
353
|
+
return result;
|
|
378
354
|
}
|
|
379
|
-
//
|
|
380
|
-
//
|
|
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.
|
|
355
|
+
// Since we know we have a type reference, we can assume that we have a symbol.
|
|
356
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
384
357
|
const localSymbol = nameNode.getSymbol();
|
|
385
|
-
//
|
|
386
|
-
// in
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
358
|
+
// If we have a type imported from another file, ensure that when we access
|
|
359
|
+
// the declaration, it's the *type* declaration in the other file, not the
|
|
360
|
+
// *import* declaration in this file.
|
|
361
|
+
// EXAMPLE:
|
|
362
|
+
// import { FooControllerSomeAction } from '@metamask/foo-controller';
|
|
363
|
+
// type Actions = FooControllerSomeAction;
|
|
364
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^
|
|
390
365
|
const symbol = localSymbol.getAliasedSymbol() ?? localSymbol;
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
366
|
+
// At this point, we have a type *reference*, but we need to find the type
|
|
367
|
+
// *declaration*.
|
|
368
|
+
// For instance, if we have `FooControllerSomeAction`, we need to find
|
|
369
|
+
// the full `type FooControllerSomeAction = ...` statement.
|
|
370
|
+
for (const declaration of symbol.getDeclarations()) {
|
|
371
|
+
// Prevent duplicates
|
|
372
|
+
if (result.visitedTypeDeclarations.has(declaration)) {
|
|
395
373
|
continue;
|
|
396
374
|
}
|
|
397
|
-
|
|
375
|
+
result.visitedTypeDeclarations.add(declaration);
|
|
376
|
+
// If we have a type alias, then we have to handle a few scenarios.
|
|
377
|
+
// EXAMPLES:
|
|
378
|
+
// type FooControllerMethodActions = ...
|
|
379
|
+
// type FooControllerSomeAction = ...
|
|
398
380
|
if (ts_morph_1.Node.isTypeAliasDeclaration(declaration)) {
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
381
|
+
const body = declaration.getTypeNode();
|
|
382
|
+
// If we have a union type, then walk each type in the union.
|
|
383
|
+
// EXAMPLE:
|
|
384
|
+
// type FooControllerMethodActions = FooControllerSomeAction | FooControllerSomeOtherAction
|
|
385
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
386
|
+
if (body && ts_morph_1.Node.isUnionTypeNode(body)) {
|
|
387
|
+
const innerResult = recursivelyFindMessengerCapabilityTypeDeclarations(body, kind, result.visitedTypeDeclarations);
|
|
388
|
+
result.capabilityTypeDeclarations.push(...innerResult.capabilityTypeDeclarations);
|
|
389
|
+
for (const typeDeclaration of innerResult.visitedTypeDeclarations) {
|
|
390
|
+
result.visitedTypeDeclarations.add(typeDeclaration);
|
|
391
|
+
}
|
|
403
392
|
continue;
|
|
404
393
|
}
|
|
394
|
+
// TODO: Is this necessary?
|
|
405
395
|
// A bare TypeReference body with no type arguments (e.g.
|
|
406
396
|
// `type AllowedActions = ConnectivityControllerGetStateAction`) is a
|
|
407
397
|
// plain re-export — leave the target to be documented from its home
|
|
408
398
|
// package, not this one. TypeReferences *with* type arguments are
|
|
409
399
|
// capability-type-constructor invocations (e.g.
|
|
410
400
|
// `ControllerGetStateAction<typeof name, State>`) and are recorded.
|
|
411
|
-
if (
|
|
412
|
-
ts_morph_1.Node.isTypeReference(
|
|
413
|
-
|
|
401
|
+
if (body &&
|
|
402
|
+
ts_morph_1.Node.isTypeReference(body) &&
|
|
403
|
+
body.getTypeArguments().length === 0) {
|
|
414
404
|
continue;
|
|
415
405
|
}
|
|
416
|
-
//
|
|
417
|
-
|
|
406
|
+
// We finally found a messenger capability type! Capture the whole thing.
|
|
407
|
+
// (We will parse the body later.)
|
|
408
|
+
// EXAMPLE:
|
|
409
|
+
// type FooControllerSomeAction = { ... }
|
|
410
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
411
|
+
result.capabilityTypeDeclarations.push({ declaration, kind });
|
|
418
412
|
}
|
|
413
|
+
// If we have an interface, we don't have any scenarios to handle, so
|
|
414
|
+
// capture it as is.
|
|
415
|
+
// EXAMPLE:
|
|
416
|
+
// interface FooControllerSomeAction { ... }
|
|
417
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
419
418
|
else if (ts_morph_1.Node.isInterfaceDeclaration(declaration)) {
|
|
420
|
-
|
|
419
|
+
result.capabilityTypeDeclarations.push({ declaration, kind });
|
|
421
420
|
}
|
|
422
421
|
}
|
|
422
|
+
return result;
|
|
423
423
|
}
|
|
424
424
|
// ---------------------------------------------------------------------------
|
|
425
425
|
// Per-statement extraction
|
|
426
426
|
// ---------------------------------------------------------------------------
|
|
427
427
|
/**
|
|
428
|
-
*
|
|
429
|
-
*
|
|
428
|
+
* Given the declaration of a messenger capability type, extract information
|
|
429
|
+
* about it (action/event type string, handler/payload arguments and return
|
|
430
|
+
* type, etc.)
|
|
430
431
|
*
|
|
431
|
-
* @param
|
|
432
|
-
*
|
|
432
|
+
* @param capabilityTypeDeclaration - The statement that declared the type for a
|
|
433
|
+
* messenger action or event, extracted in a previous step.
|
|
433
434
|
* @param projectPath - Project root, used for computing relative source paths.
|
|
434
|
-
* @returns
|
|
435
|
+
* @returns Information that may be extracted from the messenger capability type
|
|
436
|
+
* (may be `null` if the type is ineligible for extraction).
|
|
435
437
|
*/
|
|
436
|
-
function
|
|
437
|
-
|
|
438
|
-
|
|
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;
|
|
438
|
+
function extractFromMessengerCapabilityTypeDeclaration(capabilityTypeDeclaration, projectPath) {
|
|
439
|
+
return (tryToExtractFromMessengerCapabilityTypeLiteral(capabilityTypeDeclaration, projectPath) ??
|
|
440
|
+
tryToExtractFromCapabilityTypeConstructor(capabilityTypeDeclaration, projectPath));
|
|
448
441
|
}
|
|
449
442
|
/**
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
443
|
+
* If a messenger capability type is a type alias or interface and its body is
|
|
444
|
+
* a literal object type — i.e. one of:
|
|
445
|
+
*
|
|
446
|
+
* - `{ type: '...'; handler: ... }` (action)
|
|
447
|
+
* - `{ type: '...'; payload: ... }` (event)
|
|
454
448
|
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
449
|
+
* then this function extracts information about the type (action/event type
|
|
450
|
+
* string, handler/payload arguments and return type, etc.)
|
|
451
|
+
*
|
|
452
|
+
* @param capabilityTypeDeclaration - The statement that declared the type for a
|
|
453
|
+
* messenger action or event, extracted in a previous step.
|
|
457
454
|
* @param projectPath - Project root, used for computing relative source paths.
|
|
458
|
-
* @returns The extracted capability, or null if the shape
|
|
455
|
+
* @returns The extracted capability packet, or null if the shape of the type
|
|
456
|
+
* doesn't match.
|
|
459
457
|
*/
|
|
460
|
-
function
|
|
458
|
+
function tryToExtractFromMessengerCapabilityTypeLiteral(capabilityTypeDeclaration, projectPath) {
|
|
459
|
+
const { declaration, kind } = capabilityTypeDeclaration;
|
|
460
|
+
// Reject empty type aliases or interfaces.
|
|
461
|
+
// EXAMPLES:
|
|
462
|
+
// // Good
|
|
463
|
+
// type FooControllerSomeAction = {
|
|
464
|
+
// type: 'FooController:getState';
|
|
465
|
+
// handler: FooController['getState'];
|
|
466
|
+
// }
|
|
467
|
+
// // Good
|
|
468
|
+
// interface FooControllerSomeAction {
|
|
469
|
+
// type: 'FooController:getState';
|
|
470
|
+
// handler: FooController['getState'];
|
|
471
|
+
// }
|
|
472
|
+
// // Bad
|
|
473
|
+
// type FooControllerSomeAction = {};
|
|
474
|
+
// // Bad
|
|
475
|
+
// interface FooControllerSomeAction {};
|
|
461
476
|
let members;
|
|
462
|
-
if (ts_morph_1.Node.isTypeAliasDeclaration(
|
|
463
|
-
const body =
|
|
477
|
+
if (ts_morph_1.Node.isTypeAliasDeclaration(declaration)) {
|
|
478
|
+
const body = declaration.getTypeNode();
|
|
464
479
|
if (body && ts_morph_1.Node.isTypeLiteral(body)) {
|
|
465
480
|
members = body.getMembers();
|
|
466
481
|
}
|
|
467
482
|
}
|
|
468
483
|
else {
|
|
469
|
-
members =
|
|
484
|
+
members = declaration.getMembers();
|
|
470
485
|
}
|
|
471
486
|
if (!members) {
|
|
472
487
|
return null;
|
|
473
488
|
}
|
|
474
|
-
const
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
if (!typeString
|
|
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) {
|
|
489
|
+
const propertySignatures = members.filter(ts_morph_1.Node.isPropertySignature.bind(ts_morph_1.Node));
|
|
490
|
+
// Actions and events must have a `type`, and it must be a string.
|
|
491
|
+
const typeString = getMessengerCapabilityTypeString(propertySignatures);
|
|
492
|
+
if (!typeString) {
|
|
487
493
|
return null;
|
|
488
494
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (!rawSourceTypeNode) {
|
|
495
|
+
// Actions must have a `handler`, and events must have a `payload`.
|
|
496
|
+
const handlerOrPayloadProperty = findProperty(propertySignatures, kind === 'action' ? 'handler' : 'payload');
|
|
497
|
+
if (!handlerOrPayloadProperty) {
|
|
493
498
|
return null;
|
|
494
499
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
+
const handlerOrPayloadPropertyTypeNode =
|
|
501
|
+
// We can assume the property has a type; otherwise it wouldn't compile.
|
|
502
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
503
|
+
handlerOrPayloadProperty.getTypeNode();
|
|
504
|
+
let handlerOrPayloadSignature = handlerOrPayloadPropertyTypeNode
|
|
505
|
+
.getText()
|
|
506
|
+
.trim();
|
|
507
|
+
let { description: jsDoc, params, returns } = extractJsDoc(declaration);
|
|
508
|
+
// For actions, if the handler resolves to a class method (e.g.
|
|
509
|
+
// `FooController['method']`), inherit its signature plus any JSDoc fields the
|
|
510
|
+
// type alias itself doesn't already provide.
|
|
511
|
+
// TODO: We don't want to do this.
|
|
500
512
|
if (kind === 'action') {
|
|
501
|
-
const resolvedMethod = resolveIndexedAccessMethod(
|
|
513
|
+
const resolvedMethod = resolveIndexedAccessMethod(handlerOrPayloadPropertyTypeNode);
|
|
502
514
|
if (resolvedMethod) {
|
|
503
515
|
const info = buildMethodInfo(resolvedMethod);
|
|
504
|
-
|
|
516
|
+
handlerOrPayloadSignature = info.signature;
|
|
505
517
|
if (!jsDoc && info.jsDoc) {
|
|
506
518
|
jsDoc = info.jsDoc;
|
|
507
519
|
}
|
|
@@ -513,147 +525,176 @@ function extractFromInlineMessengerCapabilityType(statement, kind, projectPath)
|
|
|
513
525
|
}
|
|
514
526
|
}
|
|
515
527
|
}
|
|
516
|
-
const sourceFile =
|
|
528
|
+
const sourceFile = declaration.getSourceFile();
|
|
517
529
|
return {
|
|
518
|
-
typeName:
|
|
530
|
+
typeName: declaration.getName(),
|
|
519
531
|
typeString,
|
|
520
532
|
kind,
|
|
521
533
|
jsDoc,
|
|
522
534
|
params,
|
|
523
535
|
returns,
|
|
524
|
-
handlerOrPayload,
|
|
536
|
+
handlerOrPayload: handlerOrPayloadSignature,
|
|
525
537
|
sourceFile: path.relative(projectPath, sourceFile.getFilePath()),
|
|
526
|
-
line:
|
|
527
|
-
deprecated:
|
|
538
|
+
line: declaration.getStartLineNumber(),
|
|
539
|
+
deprecated: hasDeprecatedJsDocTag(declaration),
|
|
528
540
|
};
|
|
529
541
|
}
|
|
530
542
|
/**
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
543
|
+
* Searches the property signatures of a messenger capability type alias or
|
|
544
|
+
* interface to find the value of the `type` property, and then resolves it to a
|
|
545
|
+
* string (assuming it is already a string or a resolvable template literal).
|
|
534
546
|
*
|
|
535
|
-
* @param
|
|
536
|
-
*
|
|
547
|
+
* @param capabilityTypeProperties - The property signatures of the messenger
|
|
548
|
+
* capability type.
|
|
549
|
+
* @returns The extracted capability type string, or null if `type` cannot be
|
|
550
|
+
* found in the members or it is an unexpected node.
|
|
537
551
|
*/
|
|
538
|
-
function
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return
|
|
570
|
-
}
|
|
571
|
-
if (ts_morph_1.Node.isTemplateLiteralTypeNode(typeNode)) {
|
|
572
|
-
return resolveTemplateLiteralType(typeNode);
|
|
552
|
+
function getMessengerCapabilityTypeString(capabilityTypeProperties) {
|
|
553
|
+
const typeProperty = findProperty(capabilityTypeProperties, 'type');
|
|
554
|
+
if (!typeProperty) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
// A `type` property without an explicit type wouldn't compile.
|
|
558
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
559
|
+
const typeNode = typeProperty.getTypeNode();
|
|
560
|
+
// Ask the type checker to resolve the value of `type`. We're looking for
|
|
561
|
+
// `type` to be either a string literal or template literal.
|
|
562
|
+
//
|
|
563
|
+
// EXAMPLES:
|
|
564
|
+
// type FooControllerSomeAction = {
|
|
565
|
+
// type: 'FooController:someAction';
|
|
566
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
567
|
+
// }
|
|
568
|
+
// type FooControllerSomeAction = {
|
|
569
|
+
// type: `FooController:someAction`;
|
|
570
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
571
|
+
// }
|
|
572
|
+
// type FooControllerSomeAction = {
|
|
573
|
+
// type: `${typeof CONTROLLER_NAME}:someAction`;
|
|
574
|
+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
575
|
+
// }
|
|
576
|
+
const resolvedType = typeNode.getType();
|
|
577
|
+
if (resolvedType.isStringLiteral()) {
|
|
578
|
+
// Type assertion: There aren't any type guards we can use to narrow this
|
|
579
|
+
// type further.
|
|
580
|
+
const literalValue = resolvedType.getLiteralValueOrThrow();
|
|
581
|
+
// Messenger action/event types need to be namespaced.
|
|
582
|
+
if (literalValue.includes(':')) {
|
|
583
|
+
return literalValue;
|
|
573
584
|
}
|
|
574
585
|
}
|
|
575
|
-
// istanbul ignore next: the inline shape always has a `type` member that
|
|
576
|
-
// produces either a literal or template-literal type.
|
|
577
586
|
return null;
|
|
578
587
|
}
|
|
579
588
|
/**
|
|
580
|
-
*
|
|
589
|
+
* Finds a specific property in a list of property signatures for an object
|
|
590
|
+
* type.
|
|
581
591
|
*
|
|
582
|
-
* @param
|
|
583
|
-
*
|
|
592
|
+
* @param propertySignatures - The property signatures of the messenger
|
|
593
|
+
* capability type.
|
|
594
|
+
* @param name - The property name to find.
|
|
584
595
|
* @returns The property signature, or null.
|
|
585
596
|
*/
|
|
586
|
-
function
|
|
587
|
-
for (const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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) {
|
|
597
|
+
function findProperty(propertySignatures, name) {
|
|
598
|
+
for (const property of propertySignatures) {
|
|
599
|
+
const propertyNameNode = property.getNameNode();
|
|
600
|
+
if (!ts_morph_1.Node.isIdentifier(propertyNameNode) ||
|
|
601
|
+
propertyNameNode.getText() !== name) {
|
|
598
602
|
continue;
|
|
599
603
|
}
|
|
600
|
-
return
|
|
604
|
+
return property;
|
|
601
605
|
}
|
|
602
606
|
return null;
|
|
603
607
|
}
|
|
604
608
|
/**
|
|
605
|
-
*
|
|
606
|
-
* `ControllerGetStateAction
|
|
607
|
-
*
|
|
609
|
+
* If a messenger capability type is a type alias for either the
|
|
610
|
+
* `ControllerGetStateAction` or `ControllerStateChangeEvent` type constructors,
|
|
611
|
+
* then this function extracts information about the type (action/event type
|
|
612
|
+
* string, handler/payload arguments and return type, etc.)
|
|
608
613
|
*
|
|
609
|
-
* @param
|
|
610
|
-
*
|
|
614
|
+
* @param capabilityTypeDeclaration - The statement that declared the type for a
|
|
615
|
+
* messenger action or event, extracted in a previous step.
|
|
611
616
|
* @param projectPath - Project root, used for computing relative source paths.
|
|
612
|
-
* @returns The extracted capability, or null if the
|
|
617
|
+
* @returns The extracted capability packet, or null if the shape of the type
|
|
618
|
+
* doesn't match.
|
|
613
619
|
*/
|
|
614
|
-
function
|
|
615
|
-
const
|
|
616
|
-
//
|
|
617
|
-
|
|
620
|
+
function tryToExtractFromCapabilityTypeConstructor(capabilityTypeDeclaration, projectPath) {
|
|
621
|
+
const { declaration, kind } = capabilityTypeDeclaration;
|
|
622
|
+
// Basic check: we need a type alias, otherwise the rest doesn't make sense.
|
|
623
|
+
// EXAMPLE:
|
|
624
|
+
// type FooControllerSomeAction = ...
|
|
625
|
+
if (!ts_morph_1.Node.isTypeAliasDeclaration(declaration)) {
|
|
618
626
|
return null;
|
|
619
627
|
}
|
|
620
|
-
|
|
621
|
-
//
|
|
622
|
-
|
|
628
|
+
// We want our type alias to be a reference to a utility type.
|
|
629
|
+
// Non-null assertion: We can assume the type has a body of some kind,
|
|
630
|
+
// otherwise it wouldn't compile.
|
|
631
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
632
|
+
const body = declaration.getTypeNode();
|
|
633
|
+
// We already determined in
|
|
634
|
+
// `recursivelyFindMessengerCapabilityTypeDeclarations` that the declaration
|
|
635
|
+
// is a type reference.
|
|
636
|
+
// TODO: Capture the following information ahead of time so we don't need to
|
|
637
|
+
// check this again.
|
|
638
|
+
// istanbul ignore next
|
|
639
|
+
if (!ts_morph_1.Node.isTypeReference(body)) {
|
|
623
640
|
return null;
|
|
624
641
|
}
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
642
|
+
const typeName = body.getTypeName();
|
|
643
|
+
// We already determined in
|
|
644
|
+
// `recursivelyFindMessengerCapabilityTypeDeclarations` that the type is an
|
|
645
|
+
// identifier.
|
|
646
|
+
// TODO: Capture the following information ahead of time so we don't need to
|
|
647
|
+
// check this again.
|
|
648
|
+
// istanbul ignore next
|
|
649
|
+
if (!ts_morph_1.Node.isIdentifier(typeName)) {
|
|
628
650
|
return null;
|
|
629
651
|
}
|
|
652
|
+
// The name of the utility type should be either `ControllerGetStateAction`
|
|
653
|
+
// (for actions) or `ControllerStateChangeEvent` (for events).
|
|
654
|
+
// EXAMPLES:
|
|
655
|
+
// type FooControllerSomeAction = ControllerGetStateAction<...>
|
|
656
|
+
// type FooControllerSomeEvent = ControllerStateChangeEvent<...>
|
|
630
657
|
const expectedConstructor = kind === 'action'
|
|
631
658
|
? 'ControllerGetStateAction'
|
|
632
659
|
: 'ControllerStateChangeEvent';
|
|
633
|
-
if (
|
|
660
|
+
if (typeName.getText() !== expectedConstructor) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
// The utility type should take two parameters.
|
|
664
|
+
// EXAMPLES:
|
|
665
|
+
// type FooControllerSomeAction = ControllerGetStateAction<..., ...>
|
|
666
|
+
// type FooControllerSomeEvent = ControllerStateChangeEvent<..., ...>
|
|
667
|
+
const typeArgs = body.getTypeArguments();
|
|
668
|
+
if (typeArgs.length < 2) {
|
|
634
669
|
return null;
|
|
635
670
|
}
|
|
636
|
-
const
|
|
637
|
-
//
|
|
638
|
-
|
|
671
|
+
const namespaceArgType = typeArgs[0].getType();
|
|
672
|
+
// The first parameter should be a string literal.
|
|
673
|
+
// EXAMPLES:
|
|
674
|
+
// type FooControllerSomeAction = ControllerGetStateAction<'FooController', ...>
|
|
675
|
+
// type FooControllerSomeAction = ControllerGetStateAction<typeof CONTROLLER_NAME, ...>
|
|
676
|
+
if (!namespaceArgType.isStringLiteral()) {
|
|
639
677
|
return null;
|
|
640
678
|
}
|
|
679
|
+
// Type assertion: There aren't any type guards we can use to narrow this type
|
|
680
|
+
// further.
|
|
681
|
+
const namespace = namespaceArgType.getLiteralValueOrThrow();
|
|
682
|
+
const typeString = kind === 'action' ? `${namespace}:getState` : `${namespace}:stateChange`;
|
|
641
683
|
const stateArgText = typeArgs[1].getText();
|
|
642
|
-
const
|
|
643
|
-
const
|
|
684
|
+
const handlerOrPayload = kind === 'action' ? `() => ${stateArgText}` : `[${stateArgText}, Patch[]]`;
|
|
685
|
+
const { description, params, returns } = extractJsDoc(declaration);
|
|
686
|
+
const sourceFile = declaration.getSourceFile();
|
|
644
687
|
return {
|
|
645
|
-
typeName:
|
|
646
|
-
typeString
|
|
688
|
+
typeName: declaration.getName(),
|
|
689
|
+
typeString,
|
|
647
690
|
kind,
|
|
648
691
|
jsDoc: description,
|
|
649
692
|
params,
|
|
650
693
|
returns,
|
|
651
|
-
handlerOrPayload
|
|
652
|
-
? `() => ${stateArgText}`
|
|
653
|
-
: `[${stateArgText}, Patch[]]`,
|
|
694
|
+
handlerOrPayload,
|
|
654
695
|
sourceFile: path.relative(projectPath, sourceFile.getFilePath()),
|
|
655
|
-
line:
|
|
656
|
-
deprecated:
|
|
696
|
+
line: declaration.getStartLineNumber(),
|
|
697
|
+
deprecated: hasDeprecatedJsDocTag(declaration),
|
|
657
698
|
};
|
|
658
699
|
}
|
|
659
700
|
// ---------------------------------------------------------------------------
|
|
@@ -686,30 +727,29 @@ function createExtractionProject() {
|
|
|
686
727
|
}
|
|
687
728
|
exports.createExtractionProject = createExtractionProject;
|
|
688
729
|
/**
|
|
689
|
-
* Extract
|
|
690
|
-
*
|
|
730
|
+
* Extract information (action/event type string, handler/payload arguments and
|
|
731
|
+
* return type, etc.) about every messenger action or event type which is
|
|
732
|
+
* reachable through all of a source file's `*Messenger` type declarations.
|
|
691
733
|
*
|
|
692
734
|
* 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
|
|
735
|
+
* imports from) belongs to a `ts-morph` Project so cross-file symbol resolution
|
|
694
736
|
* works.
|
|
695
737
|
*
|
|
696
738
|
* @param sourceFile - The TypeScript source file to extract from.
|
|
697
739
|
* @param projectPath - Project root, used for computing relative source paths.
|
|
698
|
-
* @returns The extracted
|
|
740
|
+
* @returns The extracted information about actions and events.
|
|
699
741
|
*/
|
|
700
742
|
function extractFromSourceFile(sourceFile, projectPath) {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (item) {
|
|
709
|
-
items.push(item);
|
|
743
|
+
const messengerTypeAliases = findMessengerTypeAliases(sourceFile);
|
|
744
|
+
const capabilityTypeDeclarations = findAllMessengerCapabilityTypeDeclarations(messengerTypeAliases);
|
|
745
|
+
const messengerCapabilityPackets = [];
|
|
746
|
+
for (const capabilityTypeDeclaration of capabilityTypeDeclarations) {
|
|
747
|
+
const messengerCapabilityPacket = extractFromMessengerCapabilityTypeDeclaration(capabilityTypeDeclaration, projectPath);
|
|
748
|
+
if (messengerCapabilityPacket) {
|
|
749
|
+
messengerCapabilityPackets.push(messengerCapabilityPacket);
|
|
710
750
|
}
|
|
711
751
|
}
|
|
712
|
-
return
|
|
752
|
+
return messengerCapabilityPackets;
|
|
713
753
|
}
|
|
714
754
|
exports.extractFromSourceFile = extractFromSourceFile;
|
|
715
755
|
/**
|