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 CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import ts from 'typescript';
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
- try {
48
- if (fs.statSync(fileName).isDirectory) {
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
- // ignore
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
+ }
@@ -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(path.value)) {
35
- path.replace();
265
+ if (!isPublic(value)) {
266
+ return replace(path);
36
267
  }
37
268
 
38
- // TODO(nikku): transform illegal typescript methods into override calls
269
+ if (fixOptionalArgsMethods(path)) {
270
+ return false;
271
+ }
39
272
 
40
- // TODO(nikku): remove annotations
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
- cb(path);
284
+ const val = path.value;
52
285
 
53
- if (path.parentPath) {
54
- const val = path.value;
55
-
56
- if (!isNode(val) && !isArray(val)) {
57
- return;
58
- }
286
+ if (!isNode(val) && !isArray(val)) {
287
+ return;
288
+ }
59
289
 
60
- for (const key of keys(val).reverse()) {
61
- if ([ 'loc', 'start', 'end' ].includes(key)) {
62
- continue;
63
- }
290
+ if (cb(path) === false) {
291
+ return;
292
+ }
64
293
 
65
- traverse(path.get(key), cb);
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.2.1",
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",