bio-dts 0.6.1 → 0.8.0
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/README.md +3 -1
- package/lib/parsers/jsdoc.js +2 -2
- package/lib/post-transform.js +353 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ Utilities to generate sane and clean type definitions from JavaScript files.
|
|
|
6
6
|
|
|
7
7
|
## About
|
|
8
8
|
|
|
9
|
-
This module provides `pre` and `post` processing helpers to a type definition pipeline
|
|
9
|
+
This module provides `pre` and `post` processing helpers to a type definition pipeline.
|
|
10
|
+
|
|
11
|
+
You can use it [via API](#api) or through a simple [generator cli](#usage).
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
## Usage
|
package/lib/parsers/jsdoc.js
CHANGED
|
@@ -86,7 +86,7 @@ export function parse(str) {
|
|
|
86
86
|
i = skipWhitespace(str, j);
|
|
87
87
|
|
|
88
88
|
// parse type
|
|
89
|
-
if ([ 'param', 'return', 'returns', 'typedef' ].includes(tag.name)) {
|
|
89
|
+
if ([ 'param', 'return', 'returns', 'typedef', 'template' ].includes(tag.name)) {
|
|
90
90
|
|
|
91
91
|
let match = findClose(str, i, '{', '}');
|
|
92
92
|
|
|
@@ -98,7 +98,7 @@ export function parse(str) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// parse param
|
|
101
|
-
if ([ 'param', 'typedef' ].includes(tag.name)) {
|
|
101
|
+
if ([ 'param', 'typedef', 'template' ].includes(tag.name)) {
|
|
102
102
|
|
|
103
103
|
let match = tag.name === 'param' && findClose(str, i, '[', ']');
|
|
104
104
|
|
package/lib/post-transform.js
CHANGED
|
@@ -82,6 +82,56 @@ export default function transform(src) {
|
|
|
82
82
|
}));
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function getDocumentedParams(node, params = [], comment = null) {
|
|
86
|
+
|
|
87
|
+
if (!comment) {
|
|
88
|
+
return params;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const doc = comment.value.replace(/\n\s+/g, '\n ');
|
|
92
|
+
|
|
93
|
+
// parse known parameters
|
|
94
|
+
// ignore hierarchical (sub-type) params
|
|
95
|
+
const knownParams =
|
|
96
|
+
parseJSDoc(doc)
|
|
97
|
+
.filter(tag => tag.name === 'param' && !tag.param.name.includes('.'))
|
|
98
|
+
.map(tag => tag.param.name);
|
|
99
|
+
|
|
100
|
+
if (!knownParams.length) {
|
|
101
|
+
return params;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let i = 0;
|
|
105
|
+
let j = 0;
|
|
106
|
+
|
|
107
|
+
const filteredParams = [];
|
|
108
|
+
|
|
109
|
+
while (j < knownParams.length) {
|
|
110
|
+
|
|
111
|
+
let expectedName = knownParams[j];
|
|
112
|
+
let param = params[i++];
|
|
113
|
+
|
|
114
|
+
if (!param) {
|
|
115
|
+
throw error(node, `documented parameter <${ expectedName }> not found`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (param.name === 'this') {
|
|
119
|
+
filteredParams.push(param);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const actualName = param.argument?.name || param.name;
|
|
124
|
+
|
|
125
|
+
if (actualName !== expectedName) {
|
|
126
|
+
throw error(node, `documented parameter <${ expectedName }> differs from actual parameter <${ actualName }>`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
filteredParams.push(param);
|
|
130
|
+
j++;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return filteredParams;
|
|
134
|
+
}
|
|
85
135
|
|
|
86
136
|
/**
|
|
87
137
|
* Ensure optional args methods are properly escaped
|
|
@@ -96,36 +146,34 @@ export default function transform(src) {
|
|
|
96
146
|
value: node
|
|
97
147
|
} = nodePath;
|
|
98
148
|
|
|
99
|
-
const
|
|
100
|
-
node.type === 'TSDeclareMethod' ||
|
|
101
|
-
node.type === 'TSDeclareFunction'
|
|
102
|
-
);
|
|
149
|
+
const functionKind = getFunctionKind(node);
|
|
103
150
|
|
|
104
|
-
|
|
105
|
-
node?.typeAnnotation?.typeAnnotation?.type === 'TSFunctionType'
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
if (!fnDeclaration && !fnDefinition) {
|
|
151
|
+
if (!functionKind) {
|
|
109
152
|
return;
|
|
110
153
|
}
|
|
111
154
|
|
|
112
|
-
const params =
|
|
113
|
-
? node.
|
|
114
|
-
: node
|
|
155
|
+
const params = functionKind === 'TSFunctionType'
|
|
156
|
+
? node.typeAnnotation?.typeAnnotation?.parameters
|
|
157
|
+
: node.params;
|
|
158
|
+
|
|
159
|
+
const typeParameters = functionKind === 'TSFunctionType'
|
|
160
|
+
? node.typeAnnotation?.typeAnnotation?.typeParameters
|
|
161
|
+
: node.typeParameters;
|
|
162
|
+
|
|
163
|
+
const returnType = functionKind === 'TSFunctionType'
|
|
164
|
+
? node.typeAnnotation?.typeAnnotation?.typeAnnotation
|
|
165
|
+
: node.returnType;
|
|
115
166
|
|
|
116
|
-
const
|
|
117
|
-
? node.returnType
|
|
118
|
-
: node?.typeAnnotation?.typeAnnotation?.typeAnnotation;
|
|
167
|
+
const hostPath = getHostPath(nodePath);
|
|
119
168
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
);
|
|
169
|
+
const commentPaths = hostPath.get('comments');
|
|
170
|
+
|
|
171
|
+
// last comment is significant
|
|
172
|
+
const commentPath = commentPaths?.value && commentPaths.get(commentPaths.value.length - 1) || { value: null };
|
|
125
173
|
|
|
126
174
|
const {
|
|
127
175
|
variations
|
|
128
|
-
} = params.slice().reverse().reduce((res, param) => {
|
|
176
|
+
} = getDocumentedParams(hostPath.value, params, commentPath.value).slice().reverse().reduce((res, param) => {
|
|
129
177
|
|
|
130
178
|
let {
|
|
131
179
|
required,
|
|
@@ -179,11 +227,6 @@ export default function transform(src) {
|
|
|
179
227
|
return;
|
|
180
228
|
}
|
|
181
229
|
|
|
182
|
-
const commentPaths = hostPath.get('comments');
|
|
183
|
-
|
|
184
|
-
// last comment is significant
|
|
185
|
-
const commentPath = commentPaths?.value && commentPaths.get(commentPaths.value.length - 1) || { value: null };
|
|
186
|
-
|
|
187
230
|
const replacements = variations.slice().reverse().map(variation => {
|
|
188
231
|
|
|
189
232
|
const builder = {
|
|
@@ -207,7 +250,10 @@ export default function transform(src) {
|
|
|
207
250
|
returnType: returnType ? b.tsTypeAnnotation.from({
|
|
208
251
|
...returnType
|
|
209
252
|
}) : null,
|
|
210
|
-
comments: hostPath === nodePath ? variationComments : []
|
|
253
|
+
comments: hostPath === nodePath ? variationComments : [],
|
|
254
|
+
typeParameters: typeParameters ? b.tsTypeParameterDeclaration.from({
|
|
255
|
+
...typeParameters
|
|
256
|
+
}) : null
|
|
211
257
|
});
|
|
212
258
|
|
|
213
259
|
return (
|
|
@@ -227,6 +273,216 @@ export default function transform(src) {
|
|
|
227
273
|
}
|
|
228
274
|
|
|
229
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Ensures we don't re-export external declarations
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
*
|
|
281
|
+
* ```javascript
|
|
282
|
+
* export type Woop = import('./Woop').default;
|
|
283
|
+
*
|
|
284
|
+
* // ===>
|
|
285
|
+
*
|
|
286
|
+
* type Woop = import('./Woop').default;
|
|
287
|
+
* ```
|
|
288
|
+
*
|
|
289
|
+
* @param {import('./util.js').Path} nodePath
|
|
290
|
+
*
|
|
291
|
+
* @return {any[]} replacements
|
|
292
|
+
*/
|
|
293
|
+
function fixTypeExport(nodePath) {
|
|
294
|
+
|
|
295
|
+
const {
|
|
296
|
+
value: node
|
|
297
|
+
} = nodePath;
|
|
298
|
+
|
|
299
|
+
if (
|
|
300
|
+
node.type !== 'ExportNamedDeclaration' ||
|
|
301
|
+
node.declaration?.typeAnnotation?.type !== 'TSImportType'
|
|
302
|
+
) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
replace(nodePath, nodePath.get('declaration').value);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Ensure that only documented method parameters are used.
|
|
311
|
+
*
|
|
312
|
+
* @param {Path} nodePath
|
|
313
|
+
*
|
|
314
|
+
* @return {boolean} true if modified
|
|
315
|
+
*/
|
|
316
|
+
function removeUnknownParams(nodePath) {
|
|
317
|
+
|
|
318
|
+
const {
|
|
319
|
+
value: node
|
|
320
|
+
} = nodePath;
|
|
321
|
+
|
|
322
|
+
const functionKind = getFunctionKind(node);
|
|
323
|
+
|
|
324
|
+
if (!functionKind) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const params = functionKind === 'TSFunctionType'
|
|
329
|
+
? nodePath.get('typeAnnotation', 'typeAnnotation', 'parameters')
|
|
330
|
+
: nodePath.get('params');
|
|
331
|
+
|
|
332
|
+
const hostPath = getHostPath(nodePath);
|
|
333
|
+
|
|
334
|
+
const commentPaths = hostPath.get('comments');
|
|
335
|
+
|
|
336
|
+
// last comment is significant
|
|
337
|
+
const commentPath = commentPaths?.value && commentPaths.get(commentPaths.value.length - 1) || { value: null };
|
|
338
|
+
|
|
339
|
+
const knownParams = getDocumentedParams(hostPath.value, params.value, commentPath.value);
|
|
340
|
+
|
|
341
|
+
let replaced = false;
|
|
342
|
+
|
|
343
|
+
for (const idx in params.value || []) {
|
|
344
|
+
if (!knownParams[idx]) {
|
|
345
|
+
replace(params.get(idx));
|
|
346
|
+
replaced = true;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return replaced;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Generate method overloads based on `@overlord` annotated JSDoc comments.
|
|
355
|
+
*
|
|
356
|
+
* Our strategy is to parse for separate `@overlord` annotated tags,
|
|
357
|
+
* use the meta-data, and generate a completely new method from it.
|
|
358
|
+
*
|
|
359
|
+
* @param {Path} nodePath
|
|
360
|
+
*
|
|
361
|
+
* @return {boolean} true if modified
|
|
362
|
+
*/
|
|
363
|
+
function generateOverloads(nodePath) {
|
|
364
|
+
|
|
365
|
+
const {
|
|
366
|
+
value: node
|
|
367
|
+
} = nodePath;
|
|
368
|
+
|
|
369
|
+
const functionKind = getFunctionKind(node);
|
|
370
|
+
|
|
371
|
+
if (!functionKind) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const hostPath = getHostPath(nodePath);
|
|
376
|
+
|
|
377
|
+
const commentPaths = hostPath.get('comments');
|
|
378
|
+
|
|
379
|
+
const comments = (commentPaths?.value || []);
|
|
380
|
+
|
|
381
|
+
// scan for potential overloads
|
|
382
|
+
const overloads = comments.reduce((overloads, comment, idx) => {
|
|
383
|
+
const commentText = comment.value;
|
|
384
|
+
|
|
385
|
+
if (commentText.includes('@overlord') || idx === comments.length - 1) {
|
|
386
|
+
const tags = parseJSDoc(comment.value);
|
|
387
|
+
|
|
388
|
+
const paramTags = tags.filter(t => t.name === 'param');
|
|
389
|
+
const templateTags = tags.filter(t => t.name === 'template');
|
|
390
|
+
const returnTags = tags.filter(t => t.name === 'return' || t.name === 'returns');
|
|
391
|
+
|
|
392
|
+
if (returnTags.length > 1) {
|
|
393
|
+
throw error(hostPath.value, 'must specify zero or one @return(s) type in @overload fn');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return [
|
|
397
|
+
...overloads,
|
|
398
|
+
{
|
|
399
|
+
comment,
|
|
400
|
+
templateTags,
|
|
401
|
+
paramTags,
|
|
402
|
+
returnTag: returnTags[0]
|
|
403
|
+
}
|
|
404
|
+
];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return overloads;
|
|
408
|
+
}, []);
|
|
409
|
+
|
|
410
|
+
if (overloads.length < 2) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const replacements = overloads.slice().reverse().map(overload => {
|
|
415
|
+
|
|
416
|
+
const {
|
|
417
|
+
comment,
|
|
418
|
+
templateTags,
|
|
419
|
+
paramTags,
|
|
420
|
+
returnTag
|
|
421
|
+
} = overload;
|
|
422
|
+
|
|
423
|
+
const methodCode = `/*${cleanComment(comment).value}*/
|
|
424
|
+
declare function p${
|
|
425
|
+
templateTags.length ? '<' + templateTags.map(
|
|
426
|
+
t => [
|
|
427
|
+
t.param.name,
|
|
428
|
+
t.type ? 'extends ' + t.type.value.slice(1, -1) : null
|
|
429
|
+
].filter(f => f).join(' ')
|
|
430
|
+
).join(',') + '>' : ''
|
|
431
|
+
}(${
|
|
432
|
+
paramTags.map(p => {
|
|
433
|
+
|
|
434
|
+
return [
|
|
435
|
+
p.param.name,
|
|
436
|
+
p.param.value.startsWith('[') ? '?' : null,
|
|
437
|
+
': ',
|
|
438
|
+
p.type.value.slice(1, -1)
|
|
439
|
+
].filter(f => f).join('');
|
|
440
|
+
}).join(',')
|
|
441
|
+
}) : ${ returnTag ? returnTag.type.value.slice(1, -1) : 'void' };`;
|
|
442
|
+
|
|
443
|
+
const func = parseDts(methodCode).program.body[0];
|
|
444
|
+
|
|
445
|
+
const builder = {
|
|
446
|
+
'ClassProperty': b.tsDeclareMethod,
|
|
447
|
+
'TSDeclareFunction': b.tsDeclareFunction,
|
|
448
|
+
'TSDeclareMethod': b.tsDeclareMethod
|
|
449
|
+
}[node.type];
|
|
450
|
+
|
|
451
|
+
const hostBuilder = {
|
|
452
|
+
'ExportNamedDeclaration': b.exportNamedDeclaration,
|
|
453
|
+
'ExportDefaultDeclaration': b.exportDefaultDeclaration
|
|
454
|
+
}[hostPath.value.type];
|
|
455
|
+
|
|
456
|
+
const newNode = builder.from({
|
|
457
|
+
key: node.key,
|
|
458
|
+
params: func.params,
|
|
459
|
+
id: node.id,
|
|
460
|
+
declare: node.declare || false,
|
|
461
|
+
returnType: func.returnType ? b.tsTypeAnnotation.from({
|
|
462
|
+
...func.returnType
|
|
463
|
+
}) : null,
|
|
464
|
+
comments: hostPath === nodePath ? func.comments : [],
|
|
465
|
+
typeParameters: func.typeParameters ? b.tsTypeParameterDeclaration.from({
|
|
466
|
+
...func.typeParameters
|
|
467
|
+
}) : null
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
hostBuilder
|
|
472
|
+
? hostBuilder.from({
|
|
473
|
+
...hostPath.value,
|
|
474
|
+
declaration: newNode,
|
|
475
|
+
comments: func.comments
|
|
476
|
+
})
|
|
477
|
+
: newNode
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
replace(hostPath, ...replacements);
|
|
482
|
+
|
|
483
|
+
return replacements;
|
|
484
|
+
}
|
|
485
|
+
|
|
230
486
|
function cleanComment(comment) {
|
|
231
487
|
|
|
232
488
|
const replacements = [];
|
|
@@ -237,7 +493,10 @@ export default function transform(src) {
|
|
|
237
493
|
for (const tag of tags) {
|
|
238
494
|
|
|
239
495
|
// remove full line including the non-TS tag
|
|
240
|
-
if (
|
|
496
|
+
if (
|
|
497
|
+
/class|constructor|template|method|typedef|property|this|overlord/.test(tag.name) ||
|
|
498
|
+
tag.param?.name?.includes('.')
|
|
499
|
+
) {
|
|
241
500
|
replacements.push([ { start: tag.start - 4, end: tag.end } ]);
|
|
242
501
|
|
|
243
502
|
continue;
|
|
@@ -304,20 +563,27 @@ export default function transform(src) {
|
|
|
304
563
|
return replace(path);
|
|
305
564
|
}
|
|
306
565
|
|
|
566
|
+
if (generateOverloads(path)) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
|
|
307
570
|
if (fixOptionalArgsMethods(path)) {
|
|
308
571
|
return false;
|
|
309
572
|
}
|
|
310
573
|
|
|
574
|
+
removeUnknownParams(path);
|
|
311
575
|
cleanComments(path);
|
|
576
|
+
|
|
577
|
+
fixTypeExport(path);
|
|
312
578
|
});
|
|
313
579
|
|
|
314
580
|
for (const [ path, args ] of replacements) {
|
|
315
581
|
try {
|
|
316
582
|
path.replace(...args);
|
|
317
583
|
} catch (err) {
|
|
318
|
-
console.error('Failed to replace path', path, ...args);
|
|
584
|
+
console.error('Failed to replace path', path, ...args, err);
|
|
319
585
|
|
|
320
|
-
throw err;
|
|
586
|
+
throw error(path.value, 'failed to replace path: ' + err.message);
|
|
321
587
|
}
|
|
322
588
|
}
|
|
323
589
|
|
|
@@ -342,4 +608,61 @@ function traverse(path, cb) {
|
|
|
342
608
|
|
|
343
609
|
traverse(path.get(key), cb);
|
|
344
610
|
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* @param {any} node
|
|
616
|
+
* @return {'TSDeclareMethod' | 'TSDeclareFunction' | 'TSFunctionType' | null}
|
|
617
|
+
*/
|
|
618
|
+
function getFunctionKind(node) {
|
|
619
|
+
|
|
620
|
+
if (
|
|
621
|
+
node.type === 'TSDeclareMethod'
|
|
622
|
+
) {
|
|
623
|
+
return 'TSDeclareMethod';
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (node.type === 'TSDeclareFunction') {
|
|
627
|
+
return 'TSDeclareFunction';
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (
|
|
631
|
+
node.typeAnnotation?.typeAnnotation?.type === 'TSFunctionType'
|
|
632
|
+
) {
|
|
633
|
+
return 'TSFunctionType';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Return host path for node (with attached comments).
|
|
641
|
+
*
|
|
642
|
+
* @param {Path} nodePath
|
|
643
|
+
*
|
|
644
|
+
* @return {Path}
|
|
645
|
+
*/
|
|
646
|
+
function getHostPath(nodePath) {
|
|
647
|
+
return [ 'ExportDefaultDeclaration', 'ExportNamedDeclaration' ].includes(nodePath.parentPath.value.type)
|
|
648
|
+
? nodePath.parentPath
|
|
649
|
+
: nodePath;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* @param {Node} node
|
|
654
|
+
*
|
|
655
|
+
* @return {string}
|
|
656
|
+
*/
|
|
657
|
+
function error(node, message) {
|
|
658
|
+
|
|
659
|
+
const {
|
|
660
|
+
loc: {
|
|
661
|
+
start
|
|
662
|
+
}
|
|
663
|
+
} = node;
|
|
664
|
+
|
|
665
|
+
const loc = `[line ${start.line + 1}, column ${start.column + 1}]`;
|
|
666
|
+
|
|
667
|
+
return new Error(`${message} ${loc}`);
|
|
345
668
|
}
|