bio-dts 0.2.1 → 0.4.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/bin/cmd.js +37 -12
- package/lib/parsers/jsdoc.js +159 -0
- package/lib/post-transform.js +253 -20
- package/package.json +4 -4
package/bin/cmd.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import Module from 'node:module';
|
|
4
4
|
|
|
5
5
|
import glob from 'tiny-glob';
|
|
6
6
|
|
|
@@ -10,6 +10,9 @@ import fs from 'node:fs';
|
|
|
10
10
|
import generateTypes from '../lib/generate-types.js';
|
|
11
11
|
|
|
12
12
|
async function run() {
|
|
13
|
+
|
|
14
|
+
const ts = getTypescript();
|
|
15
|
+
|
|
13
16
|
const args = process.argv.slice(2);
|
|
14
17
|
|
|
15
18
|
const {
|
|
@@ -44,18 +47,12 @@ async function run() {
|
|
|
44
47
|
|
|
45
48
|
for (const fileName of fileNames) {
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const globbedFiles = await glob(globPattern, { cwd: fileName, filesOnly: true });
|
|
51
|
-
|
|
52
|
-
files.push(...globbedFiles.map(f => path.join(fileName, f)));
|
|
53
|
-
} else {
|
|
54
|
-
files.push(fileName);
|
|
55
|
-
}
|
|
56
|
-
} catch (err) {
|
|
50
|
+
if (isDir(fileName)) {
|
|
51
|
+
const globbedFiles = await glob(globPattern, { cwd: fileName, filesOnly: true });
|
|
57
52
|
|
|
58
|
-
|
|
53
|
+
files.push(...globbedFiles.map(f => path.join(fileName, f)));
|
|
54
|
+
} else {
|
|
55
|
+
files.push(fileName);
|
|
59
56
|
}
|
|
60
57
|
}
|
|
61
58
|
|
|
@@ -72,6 +69,34 @@ async function run() {
|
|
|
72
69
|
console.log('Done.');
|
|
73
70
|
}
|
|
74
71
|
|
|
72
|
+
function isDir(fileName) {
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
return fs.statSync(fileName).isDirectory();
|
|
76
|
+
} catch (err) {
|
|
77
|
+
|
|
78
|
+
// ignore
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getTypescript() {
|
|
84
|
+
|
|
85
|
+
const cwd = process.cwd();
|
|
86
|
+
|
|
87
|
+
const createRequire = Module.createRequire || Module.createRequireFromPath;
|
|
88
|
+
|
|
89
|
+
const requireLocal = createRequire(
|
|
90
|
+
path.join(cwd, '__placeholder__.js')
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
return requireLocal('typescript');
|
|
95
|
+
} catch (err) {
|
|
96
|
+
throw new Error(`failed to load <typescript> from <${ cwd }>`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
run().catch(err => {
|
|
76
101
|
console.error(err);
|
|
77
102
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
function skipWhitespace(str, pos) {
|
|
2
|
+
let i = pos;
|
|
3
|
+
|
|
4
|
+
let char;
|
|
5
|
+
for (; (char = str.charAt(i)) && char !== '\n' && /\s/.test(char); i++) {
|
|
6
|
+
|
|
7
|
+
// ignore
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return i;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function skipText(str, pos) {
|
|
14
|
+
let i = pos;
|
|
15
|
+
let char;
|
|
16
|
+
|
|
17
|
+
for (; (char = str.charAt(i)) && /\S/.test(char); i++) {
|
|
18
|
+
|
|
19
|
+
// ignore
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return i;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findClose(str, pos, openChar, closeChar) {
|
|
26
|
+
|
|
27
|
+
if (str.charAt(pos) !== openChar) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let char;
|
|
32
|
+
|
|
33
|
+
let i = pos;
|
|
34
|
+
|
|
35
|
+
let open = 0;
|
|
36
|
+
|
|
37
|
+
while ((char = str.charAt(i++))) {
|
|
38
|
+
switch (char) {
|
|
39
|
+
case openChar:
|
|
40
|
+
open++;
|
|
41
|
+
break;
|
|
42
|
+
case closeChar:
|
|
43
|
+
if (--open === 0) {
|
|
44
|
+
return {
|
|
45
|
+
start: pos,
|
|
46
|
+
end: i,
|
|
47
|
+
value: str.substring(pos, i)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findNewline(str, pos) {
|
|
56
|
+
|
|
57
|
+
let i = pos;
|
|
58
|
+
let char;
|
|
59
|
+
|
|
60
|
+
for (; (char = str.charAt(i)) && /[^\n]/.test(char); i++) {
|
|
61
|
+
|
|
62
|
+
// ignore
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return i;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function parse(str) {
|
|
69
|
+
|
|
70
|
+
var i = 0, j;
|
|
71
|
+
|
|
72
|
+
var tags = [];
|
|
73
|
+
var tag;
|
|
74
|
+
|
|
75
|
+
while ((i = str.indexOf('@', i)) && i !== -1) {
|
|
76
|
+
j = i + 1;
|
|
77
|
+
tag = {
|
|
78
|
+
start: i
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// parse tag name
|
|
82
|
+
j = skipText(str, i);
|
|
83
|
+
|
|
84
|
+
tag.name = str.substring(i + 1, j);
|
|
85
|
+
|
|
86
|
+
i = skipWhitespace(str, j);
|
|
87
|
+
|
|
88
|
+
// parse type
|
|
89
|
+
if ([ 'param', 'return', 'returns', 'typedef' ].includes(tag.name)) {
|
|
90
|
+
|
|
91
|
+
let match = findClose(str, i, '{', '}');
|
|
92
|
+
|
|
93
|
+
if (match) {
|
|
94
|
+
tag.type = match;
|
|
95
|
+
|
|
96
|
+
i = skipWhitespace(str, match.end);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// parse param
|
|
101
|
+
if ([ 'param', 'typedef' ].includes(tag.name)) {
|
|
102
|
+
|
|
103
|
+
let match = tag.name === 'param' && findClose(str, i, '[', ']');
|
|
104
|
+
|
|
105
|
+
if (match) {
|
|
106
|
+
tag.param = match;
|
|
107
|
+
|
|
108
|
+
const contents = tag.param.contents = str.substring(match.start + 1, match.end - 1);
|
|
109
|
+
const idxEq = contents.indexOf('=');
|
|
110
|
+
|
|
111
|
+
tag.param.name = idxEq !== -1 ? contents.substring(0, idxEq) : contents;
|
|
112
|
+
} else {
|
|
113
|
+
j = skipText(str, i);
|
|
114
|
+
|
|
115
|
+
tag.param = {
|
|
116
|
+
start: i,
|
|
117
|
+
end: j,
|
|
118
|
+
name: str.substring(i, j),
|
|
119
|
+
value: str.substring(i, j)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
i = skipWhitespace(str, tag.param.end);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
j = findNewline(str, i);
|
|
127
|
+
|
|
128
|
+
tag.description = i !== j ? {
|
|
129
|
+
start: i,
|
|
130
|
+
end: j,
|
|
131
|
+
value: str.substring(i, j)
|
|
132
|
+
} : null;
|
|
133
|
+
|
|
134
|
+
tag.end = j;
|
|
135
|
+
|
|
136
|
+
tags.push(tag);
|
|
137
|
+
|
|
138
|
+
i = j + 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return tags;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function dbg(str, pos) {
|
|
145
|
+
|
|
146
|
+
const arr = Array.from(str);
|
|
147
|
+
|
|
148
|
+
arr.splice(pos, 0, '|');
|
|
149
|
+
|
|
150
|
+
return arr.join('');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function replace(str, range, contents) {
|
|
154
|
+
const arr = Array.from(str);
|
|
155
|
+
|
|
156
|
+
arr.splice(range.start, range.end - range.start, contents);
|
|
157
|
+
|
|
158
|
+
return arr.join('');
|
|
159
|
+
}
|
package/lib/post-transform.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { isArray, isNode, keys, parseDts, path, print } from './util.js';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
builders as b
|
|
5
|
+
} from 'ast-types';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
parse as parseJSDoc,
|
|
9
|
+
replace as replaceJSDoc
|
|
10
|
+
} from './parsers/jsdoc.js';
|
|
11
|
+
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* @param { { name: string, comments?: any[] } }
|
|
@@ -28,41 +37,265 @@ export default function transform(src) {
|
|
|
28
37
|
const ast = parseDts(src);
|
|
29
38
|
const body = path(ast).get('program', 'body');
|
|
30
39
|
|
|
40
|
+
const replacements = [];
|
|
41
|
+
|
|
42
|
+
function replace(path, ...args) {
|
|
43
|
+
replacements.push([ path, args ]);
|
|
44
|
+
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function keepCommentParams(commentPath, params) {
|
|
49
|
+
|
|
50
|
+
const replacements = [];
|
|
51
|
+
|
|
52
|
+
const comment = commentPath.value;
|
|
53
|
+
|
|
54
|
+
const doc = comment.value.replace(/\n\s+/g, '\n ');
|
|
55
|
+
const tags = parseJSDoc(doc);
|
|
56
|
+
|
|
57
|
+
for (const tag of tags) {
|
|
58
|
+
|
|
59
|
+
// remove param not mentioned anymore
|
|
60
|
+
if (tag.name === 'param' && params.every(p => p.name !== tag.param.name)) {
|
|
61
|
+
replacements.push([ { start: tag.start - 4, end: tag.end } ]);
|
|
62
|
+
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const newDoc = replacements.slice().reverse().reduce((newDoc, r) => {
|
|
68
|
+
|
|
69
|
+
const [ { start, end }, replacement = '' ] = r;
|
|
70
|
+
|
|
71
|
+
return replaceJSDoc(newDoc, { start, end }, replacement);
|
|
72
|
+
}, doc);
|
|
73
|
+
|
|
74
|
+
const builder = {
|
|
75
|
+
'CommentBlock': b.commentBlock,
|
|
76
|
+
'CommentLine': b.commentLine
|
|
77
|
+
}[comment.type];
|
|
78
|
+
|
|
79
|
+
return cleanComment(builder.from({
|
|
80
|
+
...comment,
|
|
81
|
+
value: newDoc
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fixOptionalArgsMethods(nodePath) {
|
|
86
|
+
|
|
87
|
+
const {
|
|
88
|
+
value: node
|
|
89
|
+
} = nodePath;
|
|
90
|
+
|
|
91
|
+
if (node.type !== 'TSDeclareMethod' && node.type !== 'TSDeclareFunction') {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const hostPath = nodePath.parentPath.value.type === 'ExportNamedDeclaration'
|
|
96
|
+
? nodePath.parentPath
|
|
97
|
+
: nodePath;
|
|
98
|
+
|
|
99
|
+
const {
|
|
100
|
+
variations
|
|
101
|
+
} = node.params.slice().reverse().reduce((res, param) => {
|
|
102
|
+
|
|
103
|
+
let {
|
|
104
|
+
required,
|
|
105
|
+
variations
|
|
106
|
+
} = res;
|
|
107
|
+
|
|
108
|
+
// required before optional
|
|
109
|
+
if (param.optional && required) {
|
|
110
|
+
variations = [
|
|
111
|
+
...variations.map(v => v.slice()),
|
|
112
|
+
...variations.map(v => v.slice())
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
variations.forEach((variation, idx) => {
|
|
116
|
+
|
|
117
|
+
if (idx < variations.length / 2) {
|
|
118
|
+
variation.push(b.identifier.from({
|
|
119
|
+
...param,
|
|
120
|
+
optional: false
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
|
|
126
|
+
if (!param.optional) {
|
|
127
|
+
required = true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
variations.forEach((variation) => {
|
|
131
|
+
const builder = {
|
|
132
|
+
'RestElement': b.restElement,
|
|
133
|
+
'Identifier': b.identifier
|
|
134
|
+
}[param.type];
|
|
135
|
+
|
|
136
|
+
variation.push(builder.from(param));
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
required,
|
|
142
|
+
variations
|
|
143
|
+
};
|
|
144
|
+
}, {
|
|
145
|
+
variations: [
|
|
146
|
+
[]
|
|
147
|
+
],
|
|
148
|
+
required: false
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (variations.length === 1) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const commentPaths = hostPath.get('comments');
|
|
156
|
+
|
|
157
|
+
// last comment is significant
|
|
158
|
+
const commentPath = commentPaths?.value && commentPaths.get(commentPaths.value.length - 1) || { value: null };
|
|
159
|
+
|
|
160
|
+
const replacements = variations.slice().reverse().map(variation => {
|
|
161
|
+
|
|
162
|
+
const builder = {
|
|
163
|
+
'TSDeclareFunction': b.tsDeclareFunction,
|
|
164
|
+
'TSDeclareMethod': b.tsDeclareMethod
|
|
165
|
+
}[node.type];
|
|
166
|
+
|
|
167
|
+
const variationComments = commentPath.value ? [ keepCommentParams(commentPath, variation) ] : [ ];
|
|
168
|
+
|
|
169
|
+
const newNode = builder.from({
|
|
170
|
+
...node,
|
|
171
|
+
params: variation.slice().reverse(),
|
|
172
|
+
comments: hostPath === nodePath ? variationComments : []
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
hostPath === nodePath
|
|
177
|
+
? newNode
|
|
178
|
+
: b.exportNamedDeclaration.from({
|
|
179
|
+
...hostPath.value,
|
|
180
|
+
declaration: newNode,
|
|
181
|
+
comments: variationComments
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
replace(hostPath, ...replacements);
|
|
187
|
+
|
|
188
|
+
return replacements;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
function cleanComment(comment) {
|
|
193
|
+
|
|
194
|
+
const replacements = [];
|
|
195
|
+
|
|
196
|
+
const doc = comment.value.replace(/\n\s+/g, '\n ');
|
|
197
|
+
const tags = parseJSDoc(doc);
|
|
198
|
+
|
|
199
|
+
for (const tag of tags) {
|
|
200
|
+
|
|
201
|
+
// remove full line including the non-TS tag
|
|
202
|
+
if (/class|constructor|template|method|typedef/.test(tag.name)) {
|
|
203
|
+
replacements.push([ { start: tag.start - 4, end: tag.end } ]);
|
|
204
|
+
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (tag.type) {
|
|
209
|
+
replacements.push([ { start: tag.type.start - 1, end: tag.type.end }, '' ]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (tag.param) {
|
|
213
|
+
|
|
214
|
+
// [foo=10] => foo
|
|
215
|
+
replacements.push([ tag.param, tag.param.name ]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const newDoc = replacements.slice().reverse().reduce((newDoc, r) => {
|
|
220
|
+
|
|
221
|
+
const [ { start, end }, replacement = '' ] = r;
|
|
222
|
+
|
|
223
|
+
return replaceJSDoc(newDoc, { start, end }, replacement);
|
|
224
|
+
}, doc);
|
|
225
|
+
|
|
226
|
+
// remove entirely, if empty
|
|
227
|
+
if (!newDoc.replace(/[/*\s]/g, '')) {
|
|
228
|
+
return null;
|
|
229
|
+
} else {
|
|
230
|
+
return b.commentBlock.from({
|
|
231
|
+
type: 'CommentBlock',
|
|
232
|
+
leading: true,
|
|
233
|
+
value: newDoc
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {import('./util.js').Path} nodePath
|
|
240
|
+
*/
|
|
241
|
+
function cleanComments(nodePath) {
|
|
242
|
+
|
|
243
|
+
// clean JSDoc comments: remove annotation, fixup type parameters
|
|
244
|
+
const comments = nodePath.get('comments') || { value: [] };
|
|
245
|
+
|
|
246
|
+
for (const key of keys(comments.value || []).reverse()) {
|
|
247
|
+
const comment = comments.get(key);
|
|
248
|
+
|
|
249
|
+
const newComment = cleanComment(comment.value);
|
|
250
|
+
if (newComment) {
|
|
251
|
+
replace(comment, newComment);
|
|
252
|
+
} else {
|
|
253
|
+
replace(comment);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
31
258
|
traverse(body, function(path) {
|
|
32
259
|
|
|
260
|
+
const {
|
|
261
|
+
value
|
|
262
|
+
} = path;
|
|
263
|
+
|
|
33
264
|
// filter private members
|
|
34
|
-
if (!isPublic(
|
|
35
|
-
|
|
265
|
+
if (!isPublic(value)) {
|
|
266
|
+
return replace(path);
|
|
36
267
|
}
|
|
37
268
|
|
|
38
|
-
|
|
269
|
+
if (fixOptionalArgsMethods(path)) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
39
272
|
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
// * @typedef
|
|
43
|
-
// * @class
|
|
44
|
-
// * @constructor
|
|
273
|
+
cleanComments(path);
|
|
45
274
|
});
|
|
46
275
|
|
|
276
|
+
for (const [ path, args ] of replacements) {
|
|
277
|
+
path.replace(...args);
|
|
278
|
+
}
|
|
279
|
+
|
|
47
280
|
return print(ast).code;
|
|
48
281
|
}
|
|
49
282
|
|
|
50
283
|
function traverse(path, cb) {
|
|
51
|
-
|
|
284
|
+
const val = path.value;
|
|
52
285
|
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!isNode(val) && !isArray(val)) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
286
|
+
if (!isNode(val) && !isArray(val)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
59
289
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
290
|
+
if (cb(path) === false) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
64
293
|
|
|
65
|
-
|
|
294
|
+
for (const key of keys(val).reverse()) {
|
|
295
|
+
if ([ 'loc', 'start', 'end' ].includes(key)) {
|
|
296
|
+
continue;
|
|
66
297
|
}
|
|
298
|
+
|
|
299
|
+
traverse(path.get(key), cb);
|
|
67
300
|
}
|
|
68
301
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bio-dts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Generate sane and clean types from JavaScript sources",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@babel/parser": "^7.21.2",
|
|
18
18
|
"recast": "^0.22.0",
|
|
19
|
-
"tiny-glob": "^0.2.9"
|
|
20
|
-
"typescript": "^4.9.5"
|
|
19
|
+
"tiny-glob": "^0.2.9"
|
|
21
20
|
},
|
|
22
21
|
"devDependencies": {
|
|
23
22
|
"chai": "^4.3.7",
|
|
24
23
|
"eslint": "^8.36.0",
|
|
25
24
|
"eslint-plugin-bpmn-io": "^1.0.0",
|
|
26
|
-
"mocha": "^10.2.0"
|
|
25
|
+
"mocha": "^10.2.0",
|
|
26
|
+
"typescript": "^5.0.2"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"bin",
|