@next-core/build-next-bricks 1.10.0 → 1.11.1

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.
@@ -1,35 +1,67 @@
1
+ // @ts-check
1
2
  import { parse } from "doctrine";
2
- import { getTypeAnnotation } from "./utils.js";
3
+ import { getTypeAnnotation } from "./getTypeDeclaration.js";
3
4
 
4
5
  /**
5
6
  * @typedef {import("@next-core/brick-manifest").BrickManifest} BrickManifest
6
7
  * @typedef {import("@next-core/brick-manifest").PropertyManifest} PropertyManifest
7
8
  * @typedef {import("@next-core/brick-manifest").EventManifest} EventManifest
8
9
  * @typedef {import("@next-core/brick-manifest").MethodManifest} MethodManifest
10
+ * @typedef {import("@next-core/brick-manifest").MethodParamManifest} MethodParamManifest
9
11
  * @typedef {import("@next-core/brick-manifest").ProviderManifest} ProviderManifest
12
+ * @typedef {import("@next-core/brick-manifest").Annotation} Annotation
10
13
  * @typedef {import("@babel/types").Node} Node
11
14
  * @typedef {import("@babel/traverse").NodePath} NodePath
12
15
  * @typedef {import("@babel/types").ClassDeclaration} ClassDeclaration
16
+ * @typedef {import("@babel/types").Identifier} Identifier
13
17
  * @typedef {import("doctrine").Tag} Tag
18
+ * @typedef {BrickManifest & { types: BrickTypes; }} BrickManifestAndTypes
19
+ * @typedef {{ name: string; annotation?: Annotation }} BrickPropertyWithAnnotation
20
+ * @typedef {{ name: string; detail: { annotation: Annotation } }} BrickEventWithAnnotation
21
+ * @typedef {{ name: string; params: BrickMethodParamWithAnnotation[]; returns: { annotation?: Annotation } }} BrickMethodWithAnnotation
22
+ * @typedef {{ name: string; annotation?: Annotation }} BrickMethodParamWithAnnotation
23
+ * @typedef {{
24
+ * properties: BrickPropertyWithAnnotation[];
25
+ * events: BrickEventWithAnnotation[];
26
+ * methods: BrickMethodWithAnnotation[];
27
+ * usedReferences?: Set<string>;
28
+ * }} BrickTypes
29
+ * @typedef {ProviderManifest & {
30
+ * params: (MethodParamManifest & {
31
+ * annotation?: Annotation;
32
+ * })[];
33
+ * returns?: {
34
+ * description?: string;
35
+ * annotation?: Annotation;
36
+ * };
37
+ * typeParameters?: Annotation;
38
+ * usedReferences?: Set<string>;
39
+ * }} ProviderManifestAndTypes
14
40
  */
15
41
 
16
42
  /**
17
43
  * @param {string} name
18
44
  * @param {NodePath} nodePath
19
- * @param {string} source'
20
- * @param {Set<string>} referenceSet
21
- * @returns {BrickManifest}
45
+ * @param {string} source
46
+ * @returns {BrickManifestAndTypes}
22
47
  */
23
48
  export default function makeBrickManifest(name, nodePath, source) {
24
- /** @type {import("@babel/traverse").NodePath<ClassDeclaration>} */
25
- const classPath = nodePath.parentPath;
26
- /** @type {BrickManifest} */
49
+ const classPath =
50
+ /** @type {import("@babel/traverse").NodePath<ClassDeclaration>} */ (
51
+ nodePath.parentPath
52
+ );
53
+ /** @type {BrickManifestAndTypes} */
27
54
  const manifest = {
28
55
  name,
29
56
  properties: [],
30
57
  events: [],
31
58
  slots: [],
32
59
  methods: [],
60
+ types: {
61
+ properties: [],
62
+ events: [],
63
+ methods: [],
64
+ },
33
65
  };
34
66
 
35
67
  const docComment = findDocComment(nodePath, source);
@@ -52,7 +84,11 @@ export default function makeBrickManifest(name, nodePath, source) {
52
84
  }
53
85
  }
54
86
 
55
- scanFields(manifest, classPath.node.body.body, source);
87
+ manifest.types.usedReferences = scanFields(
88
+ manifest,
89
+ classPath.node.body.body,
90
+ source
91
+ );
56
92
 
57
93
  return manifest;
58
94
  }
@@ -61,18 +97,74 @@ export default function makeBrickManifest(name, nodePath, source) {
61
97
  * @param {string} name
62
98
  * @param {NodePath} nodePath
63
99
  * @param {string} source
64
- * @returns {ProviderManifest}
100
+ * @returns {ProviderManifestAndTypes}
65
101
  */
66
102
  export function makeProviderManifest(name, nodePath, source) {
67
- /** @type {ProviderManifest} */
103
+ /**
104
+ * @type {ProviderManifestAndTypes}
105
+ */
68
106
  const manifest = {
69
107
  name,
108
+ type: "provider",
109
+ params: [],
110
+ usedReferences: new Set(),
70
111
  };
112
+
71
113
  const docComment = findDocComment(nodePath, source);
72
114
  if (docComment) {
73
115
  manifest.description = docComment.description;
74
116
  manifest.deprecated = getDeprecatedInfo(docComment.tags);
75
117
  }
118
+
119
+ const fn = /** @type {import("@babel/types").FunctionDeclaration} */ (
120
+ nodePath.node
121
+ );
122
+ let index = 0;
123
+ for (const param of fn.params) {
124
+ const annotation = getTypeAnnotation(
125
+ param.typeAnnotation,
126
+ source,
127
+ manifest.usedReferences
128
+ );
129
+ if (param.type === "Identifier") {
130
+ manifest.params.push({
131
+ name: param.name,
132
+ description: docComment?.tags.find(
133
+ (tag) => tag.title === "param" && tag.name === param.name
134
+ )?.description,
135
+ annotation,
136
+ });
137
+ } else {
138
+ const paramTag = docComment?.tags.filter(
139
+ (tag) => tag.title === "param"
140
+ )?.[index];
141
+ manifest.params.push({
142
+ name: paramTag?.name ?? `param_${index + 1}`,
143
+ description: paramTag?.description,
144
+ isRestElement: param.type === "RestElement",
145
+ annotation,
146
+ });
147
+ }
148
+ index++;
149
+ }
150
+ const returnAnnotation = getTypeAnnotation(
151
+ fn.returnType,
152
+ source,
153
+ manifest.usedReferences
154
+ );
155
+
156
+ manifest.returns = {
157
+ description: docComment?.tags.find((tag) => tag.title === "returns")
158
+ ?.description,
159
+ annotation: returnAnnotation,
160
+ };
161
+
162
+ manifest.typeParameters = getTypeAnnotation(
163
+ fn.typeParameters,
164
+ source,
165
+ manifest.usedReferences
166
+ );
167
+
76
168
  return manifest;
77
169
  }
78
170
 
@@ -93,12 +185,14 @@ function findDocComment({ node, parentPath }, source) {
93
185
  }
94
186
 
95
187
  /**
96
- * @param {BrickManifest} manifest
188
+ * @param {BrickManifestAndTypes} manifest
97
189
  * @param {Node[]} nodes
98
190
  * @param {string} source
99
- * @param {Set<string>} referenceSet
191
+ * @returns {Set<string>}
100
192
  */
101
- function scanFields(manifest, nodes, source, referenceSet = new Set()) {
193
+ function scanFields(manifest, nodes, source) {
194
+ /** @type {Set<string>} */
195
+ const usedReferences = new Set();
102
196
  for (const node of nodes) {
103
197
  if (node.type === "ClassAccessorProperty" && node.decorators?.length) {
104
198
  for (const { expression } of node.decorators) {
@@ -110,7 +204,7 @@ function scanFields(manifest, nodes, source, referenceSet = new Set()) {
110
204
  case "property": {
111
205
  /** @type {PropertyManifest} */
112
206
  const prop = {
113
- name: node.key.name,
207
+ name: /** @type {Identifier} */ (node.key).name,
114
208
  };
115
209
  const docComment = parseDocComment(node, source);
116
210
  if (docComment) {
@@ -148,12 +242,41 @@ function scanFields(manifest, nodes, source, referenceSet = new Set()) {
148
242
  ) {
149
243
  const { typeAnnotation } = node.typeAnnotation;
150
244
  prop.type = getTypeWithoutUndefined(typeAnnotation, source);
151
- prop.types = getTypesWithoutUndefined(
152
- typeAnnotation,
245
+
246
+ const annotation = getTypeAnnotation(
247
+ getNodeWithoutUndefined(typeAnnotation),
153
248
  source,
154
- referenceSet
249
+ usedReferences
155
250
  );
156
- prop.reference = [...referenceSet];
251
+ if (annotation) {
252
+ manifest.types.properties.push({
253
+ name: prop.name,
254
+ annotation,
255
+ });
256
+ }
257
+ } else if (node.value) {
258
+ // Infer type annotation from the default value.
259
+ let inferType;
260
+ switch (node.value.type) {
261
+ case "BooleanLiteral":
262
+ inferType = "boolean";
263
+ break;
264
+ case "StringLiteral":
265
+ inferType = "string";
266
+ break;
267
+ case "NumericLiteral":
268
+ inferType = "number";
269
+ break;
270
+ }
271
+ if (inferType) {
272
+ manifest.types.properties.push({
273
+ name: prop.name,
274
+ annotation: {
275
+ type: "keyword",
276
+ value: inferType,
277
+ },
278
+ });
279
+ }
157
280
  }
158
281
  if (node.value && !prop.default) {
159
282
  prop.default = source.substring(
@@ -167,7 +290,7 @@ function scanFields(manifest, nodes, source, referenceSet = new Set()) {
167
290
 
168
291
  case "event": {
169
292
  /** @type {EventManifest} */
170
- const event = {};
293
+ const event = { name: undefined };
171
294
 
172
295
  // Find out the `type` option for the event.
173
296
  if (expression.arguments.length > 0) {
@@ -190,7 +313,9 @@ function scanFields(manifest, nodes, source, referenceSet = new Set()) {
190
313
  }
191
314
  if (event.name === undefined) {
192
315
  throw new Error(
193
- `Invalid @event() call: no literal type option in event '${node.key.name}'`
316
+ `Invalid @event() call: no literal type option in event '${
317
+ /** @type {Identifier} */ (node.key).name
318
+ }'`
194
319
  );
195
320
  }
196
321
  const docComment = parseDocComment(node, source);
@@ -218,12 +343,21 @@ function scanFields(manifest, nodes, source, referenceSet = new Set()) {
218
343
  const param = typeAnnotation.typeParameters.params[0];
219
344
  event.detail ??= {};
220
345
  event.detail.type = source.substring(param.start, param.end);
221
- event.detail.types = getTypeAnnotation(
346
+
347
+ const annotation = getTypeAnnotation(
222
348
  param,
223
349
  source,
224
- referenceSet
350
+ usedReferences
225
351
  );
226
- event.detail.reference = [...referenceSet];
352
+
353
+ if (annotation) {
354
+ manifest.types.events.push({
355
+ name: event.name,
356
+ detail: {
357
+ annotation,
358
+ },
359
+ });
360
+ }
227
361
  }
228
362
  }
229
363
  manifest.events.push(event);
@@ -241,40 +375,99 @@ function scanFields(manifest, nodes, source, referenceSet = new Set()) {
241
375
  ) {
242
376
  /** @type {MethodManifest} */
243
377
  const method = {
244
- name: node.key.name,
378
+ name: /** @type {Identifier} */ (node.key).name,
245
379
  params: [],
246
380
  };
247
381
  const docComment = parseDocComment(node, source);
248
382
  if (docComment) {
249
383
  method.description = docComment.description;
250
384
  method.deprecated = getDeprecatedInfo(docComment.tags);
385
+ method.returns = {
386
+ description: docComment.tags.find(
387
+ (tag) => tag.title === "returns"
388
+ )?.description,
389
+ };
251
390
  }
391
+
392
+ let index = 0;
393
+ /** @type {BrickMethodParamWithAnnotation[]} */
394
+ const typedParams = [];
252
395
  for (const param of node.params) {
253
- method.params.push(source.substring(param.start, param.end));
396
+ const typeAnnotation =
397
+ /** @type {Identifier} */
398
+ (param).typeAnnotation;
399
+ const annotation = getTypeAnnotation(
400
+ typeAnnotation,
401
+ source,
402
+ usedReferences
403
+ );
404
+ const paramType =
405
+ typeAnnotation.type === "TSTypeAnnotation"
406
+ ? source.substring(
407
+ typeAnnotation.typeAnnotation.start,
408
+ typeAnnotation.typeAnnotation.end
409
+ )
410
+ : undefined;
411
+ /** @type {string} */
412
+ let paramName;
413
+ if (param.type === "Identifier") {
414
+ paramName = param.name;
415
+ method.params.push({
416
+ name: paramName,
417
+ description: docComment?.tags.find(
418
+ (tag) => tag.title === "param" && tag.name === param.name
419
+ )?.description,
420
+ type: paramType,
421
+ });
422
+ } else {
423
+ const paramTag = docComment?.tags.filter(
424
+ (tag) => tag.title === "param"
425
+ )?.[index];
426
+ paramName = paramTag?.name ?? `param_${index + 1}`;
427
+ method.params.push({
428
+ name: paramName,
429
+ description: paramTag?.description,
430
+ type: paramType,
431
+ });
432
+ }
433
+ typedParams.push({
434
+ name: paramName,
435
+ annotation,
436
+ });
437
+ index++;
254
438
  }
439
+
440
+ /** @type {{ annotation?: Annotation }} */
441
+ const typedReturns = {};
255
442
  if (node.returnType && node.returnType.type === "TSTypeAnnotation") {
256
443
  const { typeAnnotation } = node.returnType;
257
- method.return ??= {};
258
- method.return.type = source.substring(
259
- typeAnnotation.start,
260
- typeAnnotation.end
261
- );
262
- method.return.types = getTypeAnnotation(
444
+ method.returns = {
445
+ ...method.returns,
446
+ type: source.substring(typeAnnotation.start, typeAnnotation.end),
447
+ };
448
+
449
+ typedReturns.annotation = getTypeAnnotation(
263
450
  typeAnnotation,
264
451
  source,
265
- referenceSet
452
+ usedReferences
266
453
  );
267
- method.return.reference = [...referenceSet];
268
454
  }
455
+ manifest.types.methods.push({
456
+ name: method.name,
457
+ params: typedParams,
458
+ returns: typedReturns,
459
+ });
269
460
  manifest.methods.push(method);
270
461
  }
271
462
  }
272
463
  }
273
464
  }
465
+
466
+ return usedReferences;
274
467
  }
275
468
 
276
469
  /**
277
- * @param {Node[]} nodes
470
+ * @param {Node} node
278
471
  * @param {string} source
279
472
  */
280
473
  export function parseDocComment(node, source) {
@@ -290,6 +483,21 @@ export function parseDocComment(node, source) {
290
483
  }
291
484
  }
292
485
 
486
+ /**
487
+ * @param {Node} node
488
+ * @param {string} source
489
+ * @returns {undefined | { description?: string; deprecated?: boolean | string; }}
490
+ */
491
+ export function parseTypeComment(node, source) {
492
+ const docComment = parseDocComment(node, source);
493
+ if (docComment) {
494
+ return {
495
+ description: docComment.description,
496
+ deprecated: getDeprecatedInfo(docComment.tags),
497
+ };
498
+ }
499
+ }
500
+
293
501
  /**
294
502
  * @param {Node} node
295
503
  * @param {string} source
@@ -309,20 +517,25 @@ function getTypeWithoutUndefined(node, source) {
309
517
  }
310
518
 
311
519
  /**
312
- * @param {import("@babel/types")/.typeAnnotation} typeAnnotation
313
- * @param {string} source
314
- * @param {Set<string} set
520
+ * @param {Node} node
521
+ * @returns {Node}
315
522
  */
316
- function getTypesWithoutUndefined(typeAnnotation, source, set) {
317
- if (typeAnnotation.type === "TSUnionType" && typeAnnotation.types) {
318
- return {
319
- type: "union",
320
- types: typeAnnotation.types
321
- .filter((type) => type.type !== "TSUndefinedKeyword")
322
- .map((item) => getTypeAnnotation(item, source, set)),
323
- };
523
+ function getNodeWithoutUndefined(node) {
524
+ if (node.type === "TSUnionType") {
525
+ const filteredTypes = node.types.filter(
526
+ (type) => type.type !== "TSUndefinedKeyword"
527
+ );
528
+ if (filteredTypes.length < node.types.length) {
529
+ if (filteredTypes.length === 1) {
530
+ return filteredTypes[0];
531
+ }
532
+ return {
533
+ ...node,
534
+ types: filteredTypes,
535
+ };
536
+ }
324
537
  }
325
- return getTypeAnnotation(typeAnnotation, source, set);
538
+ return node;
326
539
  }
327
540
 
328
541
  /**