bio-dts 0.14.1 → 0.15.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 CHANGED
@@ -46,11 +46,11 @@ import * as typescript from 'typescript';
46
46
  // transform JS so it keeps the shape,
47
47
  // but is properly digestable by the typescript
48
48
  // compiler
49
- const transformedCode = preTransform(jsCode);
49
+ const { code } = preTransform(jsCode);
50
50
 
51
51
  // post process typescript compiler type code
52
52
  // removing internals, and fixing up the definitions
53
- const transformedCode = postTransform(tsCode);
53
+ const { code } = postTransform(tsCode);
54
54
 
55
55
  // execute the full pipeline, including invoking the
56
56
  // typescript compiler
package/bin/cmd.js CHANGED
@@ -9,6 +9,7 @@ import os from 'node:os';
9
9
  import fs from 'node:fs';
10
10
 
11
11
  import generateTypes from '../lib/generate-types.js';
12
+ import { readTsConfigOptions } from '../lib/tsconfig.js';
12
13
 
13
14
  async function run() {
14
15
 
@@ -63,14 +64,18 @@ async function run() {
63
64
 
64
65
  console.log(`Generating types for ${files.length} files...`);
65
66
 
67
+ const tsConfigOptions = readTsConfigOptions(ts);
68
+
66
69
  const generateOptions = {
67
70
  allowJs: true,
68
71
  declaration: true,
69
72
  emitDeclarationOnly: true,
73
+ ...tsConfigOptions,
70
74
  ...options,
71
75
  verbose
72
76
  };
73
77
 
78
+ verbose && Object.keys(tsConfigOptions).length && console.log('Using tsconfig options', tsConfigOptions);
74
79
  verbose && console.log('Using options ', generateOptions);
75
80
 
76
81
  const {
@@ -4,10 +4,23 @@ import preTransform from './pre-transform.js';
4
4
 
5
5
  import postTransform from './post-transform.js';
6
6
 
7
+ import remapping from '@jridgewell/remapping';
8
+
9
+
7
10
  /**
11
+ * @typedef { import('typescript').CompilerHost } CompilerHost
12
+ * @typedef { import('typescript').WriteFileCallback } WriteFileCallback
13
+ *
14
+ * @typedef { {
15
+ * _readFile: (fileName: string) => string,
16
+ * _writeFile: WriteFileCallback,
17
+ * ___cache: Record<string, string>
18
+ * } } CompilerHostExtension
19
+ *
8
20
  * @typedef { import('typescript').CompilerOptions } CompilerOptions
9
21
  * @typedef { import('typescript').createCompilerHost } createCompilerHost
10
22
  * @typedef { import('typescript').createProgram } createProgram
23
+ *
11
24
  * @typedef { {
12
25
  * createCompilerHost: createCompilerHost,
13
26
  * createProgram: createProgram
@@ -18,7 +31,7 @@ import postTransform from './post-transform.js';
18
31
  * @param { string[] } fileNames
19
32
  * @param { CompilerOptions } compilerOptions
20
33
  * @param { TypeScript } ts
21
- * @param { { lax: boolean } } generateOptions
34
+ * @param { { lax?: boolean } } [generateOptions]
22
35
  */
23
36
  export default function generateTypes(fileNames, compilerOptions, ts, generateOptions) {
24
37
 
@@ -29,13 +42,19 @@ export default function generateTypes(fileNames, compilerOptions, ts, generateOp
29
42
  // CompilerOptions.JsxEmit.None = 0
30
43
  const jsx = compilerOptions.jsx !== 0;
31
44
 
45
+ const declarationMap = compilerOptions.declarationMap;
46
+ const sourceRoot = compilerOptions.sourceRoot;
47
+
32
48
  const names = new Set(fileNames.map((p) => path.resolve(p)));
33
49
 
34
- const host = ts.createCompilerHost(compilerOptions);
50
+ const host = /** @type { CompilerHost & CompilerHostExtension } */ (
51
+ ts.createCompilerHost(compilerOptions)
52
+ );
35
53
 
36
- // @ts-expect-error
37
54
  host._readFile = host.readFile;
38
55
 
56
+ host.___cache = {};
57
+
39
58
  /**
40
59
  * @param {string} fileName
41
60
  *
@@ -43,44 +62,121 @@ export default function generateTypes(fileNames, compilerOptions, ts, generateOp
43
62
  */
44
63
  host.readFile = (fileName) => {
45
64
 
46
- // @ts-expect-error
47
65
  let src = host._readFile(fileName);
48
66
 
49
67
  if (names.has(path.resolve(fileName))) {
50
68
  isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [pre]', fileName, src);
51
69
 
52
70
  try {
53
- src = preTransform(src, { jsx });
71
+
72
+ const sourceMapOptions = declarationMap ? {
73
+ sourceFileName: fileName,
74
+ sourceMapName: fileName + '___pre',
75
+ sourceRoot
76
+ } : {};
77
+
78
+ const transformed = preTransform(src, {
79
+ ...sourceMapOptions,
80
+ jsx
81
+ });
82
+
83
+ if (transformed.map) {
84
+ host.___cache[fileName + '___pre'] = JSON.stringify(transformed.map);
85
+ }
86
+
87
+ src = transformed.code;
88
+
89
+ isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [pre] [generated]', fileName, src);
54
90
  } catch (err) {
55
91
  console.error(`[generate-types] [pre] failed to parse ${fileName} with contents ${src}`, err);
56
92
  throw err;
57
93
  }
58
94
 
59
- isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [pre] [generated]', fileName, src);
60
95
  }
61
96
 
62
97
  return src;
63
98
  };
64
99
 
65
- // @ts-expect-error
66
100
  host._writeFile = host.writeFile;
67
101
 
68
- host.writeFile = (fileName, text, ...args) => {
69
- isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [post]', fileName, text);
102
+ host.writeFile = (fileName, text, writeByteOrderMark, onError, sourceFiles, ...args) => {
103
+
104
+ const [ sourceFile ] = sourceFiles;
105
+
106
+ const originalFileName = /** @type {string|null} */ (
107
+ 'originalFileName' in sourceFile && sourceFile.originalFileName || null
108
+ );
109
+
110
+ if (fileName.toLowerCase().endsWith('.d.ts.map')) {
111
+
112
+ if (originalFileName) {
113
+ host.___cache[originalFileName + '___ts'] = text;
114
+ }
115
+
116
+ return;
117
+ }
70
118
 
71
119
  if (fileName.toLowerCase().endsWith('.d.ts')) {
120
+
121
+ isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [post]', fileName, text);
122
+
72
123
  try {
73
- text = postTransform(text, generateOptions);
124
+ const sourceMap_pre_json = host.___cache[originalFileName + '___pre'];
125
+ const sourceMap_ts_json = host.___cache[originalFileName + '___ts'];
126
+
127
+ const sourceMapOptions = sourceMap_pre_json && sourceMap_ts_json && {
128
+ sourceFileName: fileName + '___ts',
129
+ sourceMapName: fileName,
130
+ sourceRoot
131
+ } || {};
132
+
133
+ const transformed = postTransform(text, {
134
+ ...sourceMapOptions,
135
+ ...generateOptions
136
+ });
137
+
138
+ if (transformed.map) {
139
+ const sourceMap_pre = JSON.parse(sourceMap_pre_json);
140
+ const sourceMap_ts = JSON.parse(sourceMap_ts_json);
141
+ const sourceMap_post = transformed.map;
142
+
143
+ const combined = remapping(
144
+ [
145
+ sourceMap_post,
146
+ sourceMap_ts,
147
+ sourceMap_pre
148
+ ],
149
+ () => null
150
+ );
151
+
152
+ const combinedJSON = JSON.parse(JSON.stringify(combined));
153
+
154
+ // produce source maps with relative paths to sources,
155
+ // following typescript conventions
156
+ if (combinedJSON.sources) {
157
+ const mapDir = path.dirname(path.resolve(fileName));
158
+ combinedJSON.sources = combinedJSON.sources.map(src =>
159
+ path.relative(mapDir, path.resolve(src))
160
+ );
161
+ }
162
+
163
+ // keep maps small, do not inline source text in declaration maps
164
+ delete combinedJSON.sourcesContent;
165
+
166
+ host._writeFile(fileName + '.map', JSON.stringify(combinedJSON), writeByteOrderMark, onError, sourceFiles, ...args);
167
+ }
168
+
169
+ text = transformed.code;
170
+
171
+ isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [post] [generated]', fileName, text);
74
172
  } catch (err) {
75
173
  console.error(`[generate-types] [post] failed to parse ${fileName} with contents ${text}`, err);
76
174
  throw err;
77
175
  }
78
176
 
79
- isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [post] [generated]', fileName, text);
80
177
  }
81
178
 
82
- // @ts-expect-error
83
- host._writeFile(fileName, text, ...args);
179
+ host._writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, ...args);
84
180
  };
85
181
 
86
182
  const program = ts.createProgram(fileNames, compilerOptions, host);
@@ -15,6 +15,12 @@ import {
15
15
  * @typedef { import('./util.js').Path<T> } Path
16
16
  */
17
17
 
18
+ /**
19
+ * @typedef { import('./util.js').ParseOptions } ParseOptions
20
+ *
21
+ * @typedef { { lax?: boolean } & ParseOptions } TransformOptions
22
+ */
23
+
18
24
  /**
19
25
  * @param { { name: string, key?: { name?: string }, comments?: any[] } } m
20
26
  *
@@ -35,17 +41,18 @@ function isPublic(m) {
35
41
 
36
42
  /**
37
43
  * @param { string } src
38
- * @param { { lax: boolean } } [options]
44
+ * @param { TransformOptions } [transformOptions]
39
45
  *
40
- * @return { string }
46
+ * @return { import('./util.js').PrintResult }
41
47
  */
42
- export default function transform(src, options) {
48
+ export default function transform(src, transformOptions = {}) {
43
49
 
44
50
  const {
45
- lax = false
46
- } = options || {};
51
+ lax = false,
52
+ ...parseOptions
53
+ } = transformOptions;
47
54
 
48
- const ast = parseDts(src);
55
+ const ast = parseDts(src, parseOptions);
49
56
  const body = path(ast).get('program', 'body');
50
57
 
51
58
  const replacements = [];
@@ -627,7 +634,7 @@ declare function p${
627
634
  }
628
635
  }
629
636
 
630
- return print(ast).code;
637
+ return print(ast, parseOptions);
631
638
  }
632
639
 
633
640
  function traverse(path, cb) {
@@ -1,10 +1,32 @@
1
1
  import { matcher, parse, path, print } from './util.js';
2
2
 
3
+ import { SourceMapConsumer, SourceMapGenerator } from '@jridgewell/source-map';
4
+
3
5
  /**
4
6
  * @template T
5
7
  * @typedef { import('./util.js').Path<T> } Path
6
8
  */
7
9
 
10
+ /**
11
+ * @typedef { import('ast-types/lib/gen/kinds').PrintableKind } WithLocation
12
+ */
13
+
14
+ /**
15
+ * @typedef { import('./util.js').ParseOptions } ParseOptions
16
+ *
17
+ * @typedef { { jsx?: boolean } & ParseOptions } TransformOptions
18
+ */
19
+
20
+
21
+ /**
22
+ * @typedef { import('recast').types.namedTypes.ClassMethod } ClassMethod
23
+ * @typedef { import('recast').types.namedTypes.ClassDeclaration } ClassDeclaration
24
+ */
25
+
26
+ /**
27
+ * @typedef { import('ast-types').ASTNode } ASTNode
28
+ */
29
+
8
30
  import {
9
31
  parse as parseJSDoc
10
32
  } from './parsers/jsdoc.js';
@@ -15,32 +37,39 @@ import {
15
37
 
16
38
  function superCall() {
17
39
  return b.expressionStatement.from({
18
- 'expression': {
19
- 'type': 'CallExpression',
20
- 'callee': {
21
- 'type': 'Super'
22
- },
23
- 'arguments': []
24
- }
40
+ expression: b.callExpression.from({
41
+ callee: b.super(),
42
+ arguments: []
43
+ })
25
44
  });
26
45
  }
27
46
 
47
+ /**
48
+ * @return { ClassMethod }
49
+ */
28
50
  function clazzConstructor(cls, comments) {
29
51
  const constructor = cls.ctor.value;
30
52
 
31
53
  return b.classMethod.from({
32
- 'static': false,
33
- 'key': {
34
- 'type': 'Identifier',
35
- 'name': 'constructor'
36
- },
37
- 'kind': 'constructor',
38
- 'params': constructor.params,
39
- 'body': constructor.body,
40
- 'comments': comments
54
+ static: false,
55
+ key: b.identifier.from({
56
+ name: 'constructor',
57
+ loc: cls.identifier.loc
58
+ }),
59
+ kind: 'constructor',
60
+ params: constructor.params,
61
+ body: constructor.body,
62
+ comments,
63
+ loc: constructor.loc
41
64
  });
42
65
  }
43
66
 
67
+ /**
68
+ * @param { any } cls
69
+ * @param { ClassMethod } ctor
70
+ *
71
+ * @return { ClassDeclaration }
72
+ */
44
73
  function clazzDefinition(cls, ctor) {
45
74
 
46
75
  const members = [
@@ -49,79 +78,61 @@ function clazzDefinition(cls, ctor) {
49
78
 
50
79
  // class method
51
80
  if (m.member.type === 'FunctionExpression') {
52
- return {
53
- 'type': 'ClassMethod',
54
- 'static': false,
55
- 'key': {
56
- 'type': 'Identifier',
57
- 'name': m.name
58
- },
59
- 'kind': 'method',
60
- 'params': m.member.params,
61
- 'body': m.member.body,
62
- 'comments': m.comments
63
- };
81
+ return b.classMethod.from({
82
+ static: false,
83
+ key: m.identifier,
84
+ kind: 'method',
85
+ params: m.member.params,
86
+ body: m.member.body,
87
+ comments: m.comments || null,
88
+ loc: m.member.loc
89
+ });
64
90
  }
65
91
 
66
92
  // save to transform into class property
67
- return {
68
- 'type': 'ClassProperty',
69
- 'static': false,
70
- 'key': {
71
- 'type': 'Identifier',
72
- 'name': m.name
73
- },
74
- 'value': m.member,
75
- 'comments': m.comments
76
- };
93
+ return b.classProperty.from({
94
+ static: false,
95
+ key: m.identifier,
96
+ value: m.member,
97
+ comments: m.comments || null,
98
+ loc: m.member.loc
99
+ });
77
100
  })
78
101
  ];
79
102
 
80
103
  const staticMembers = cls.staticMembers.map(m => {
81
104
 
82
105
  if (m.member.type === 'FunctionExpression') {
83
- return {
84
- 'type': 'ClassMethod',
85
- 'static': true,
86
- 'key': {
87
- 'type': 'Identifier',
88
- 'name': m.name
89
- },
90
- 'kind': 'method',
91
- 'params': m.member.params,
92
- 'body': m.member.body,
93
- 'comments': m.comments
94
- };
106
+ return b.classMethod.from({
107
+ static: true,
108
+ key: m.identifier,
109
+ kind: 'method',
110
+ params: m.member.params,
111
+ body: m.member.body,
112
+ comments: m.comments || null,
113
+ loc: m.member.loc
114
+ });
95
115
  }
96
116
 
97
- return {
98
- 'type': 'ClassProperty',
99
- 'static': true,
100
- 'key': {
101
- 'type': 'Identifier',
102
- 'name': m.name
103
- },
104
- 'value': m.member,
105
- 'comments': m.comments
106
- };
117
+ return b.classProperty.from({
118
+ static: true,
119
+ key: m.identifier,
120
+ value: m.member,
121
+ comments: m.comments || null,
122
+ loc: m.member.loc
123
+ });
107
124
  });
108
125
 
109
126
  return b.classDeclaration.from({
110
- 'id': {
111
- 'type': 'Identifier',
112
- 'name': cls.name
113
- },
114
- 'superClass': cls.superDeclaration ? {
115
- 'type': 'Identifier',
116
- 'name': cls.superDeclaration.clazz
117
- } : null,
118
- 'body': {
119
- 'type': 'ClassBody',
120
- 'body': [
127
+ id: cls.identifier,
128
+ superClass: cls.superDeclaration ? cls.superDeclaration.clazz : null,
129
+ body: b.classBody.from({
130
+ body: [
121
131
  ...members,
122
132
  ...staticMembers
123
133
  ]
124
- }
134
+ }),
135
+ loc: cls.node.value.loc
125
136
  });
126
137
  }
127
138
 
@@ -136,21 +147,23 @@ function splitComment(cls) {
136
147
  const [ intro, ...rest ] = comment?.value?.split(/\* @param/) || [];
137
148
 
138
149
  return {
139
- ctorComments: rest.length ? [ {
140
- leading: true,
141
- trailing: false,
142
- type: 'CommentBlock',
143
- value: '*\n * @param' + rest.join('* @param'),
144
- } ] : [],
150
+ ctorComments: rest.length ? [
151
+ b.commentBlock.from({
152
+ leading: true,
153
+ trailing: false,
154
+ value: '*\n * @param' + rest.join('* @param'),
155
+ loc: comment.loc
156
+ })
157
+ ] : [],
145
158
  additionalComments: [
146
159
  ...additionalComments,
147
160
  ...(
148
161
  intro ? [
149
- {
162
+ b.commentBlock.from({
150
163
  leading: true,
151
- type: 'CommentBlock',
152
- value: intro
153
- }
164
+ value: intro,
165
+ loc: comment.loc
166
+ })
154
167
  ] : []
155
168
  )
156
169
  ]
@@ -159,13 +172,25 @@ function splitComment(cls) {
159
172
 
160
173
  /**
161
174
  * @param { string } src
162
- * @param { { jsx?: boolean } } [parseOptions]
175
+ * @param { TransformOptions } [transformOptions]
163
176
  *
164
- * @return {string}
177
+ * @return { import('./util.js').PrintResult }
165
178
  */
166
- export default function transform(src, parseOptions = {}) {
179
+ export default function transform(src, transformOptions = {}) {
180
+
181
+ const {
182
+ jsx,
183
+ ...parseOptions
184
+ } = transformOptions;
185
+
186
+ /**
187
+ * @type { ASTNode }
188
+ */
189
+ const ast = parse(src, transformOptions);
167
190
 
168
- const ast = parse(src, parseOptions);
191
+ /**
192
+ * @type {Path<ASTNode[]>}
193
+ */
169
194
  const body = path(ast).get('program', 'body');
170
195
 
171
196
  const inheritsImports = findInheritsImports(body);
@@ -229,9 +254,115 @@ export default function transform(src, parseOptions = {}) {
229
254
 
230
255
  inheritsImports.forEach(m => m.node.replace());
231
256
 
232
- return print(ast).code;
257
+ const {
258
+ code,
259
+ map
260
+ } = print(ast, parseOptions);
261
+
262
+ if (!map) {
263
+ return {
264
+ code
265
+ };
266
+ } else {
267
+
268
+ // source maps are broken through function -> class transformation
269
+ // we have to manually fix them
270
+ const generatedAst = parse(code, transformOptions);
271
+
272
+ return {
273
+ code,
274
+ map: fixSourceMap(generatedAst, map, classes)
275
+ };
276
+ }
233
277
  }
234
278
 
279
+
280
+ /**
281
+ * @param {ASTNode} generatedAst
282
+ * @param {any} map
283
+ * @param {any} originalClasses
284
+ *
285
+ * @return {any} fixed map
286
+ */
287
+ function fixSourceMap(generatedAst, map, originalClasses) {
288
+
289
+ /**
290
+ * @type {Path<ASTNode[]>}
291
+ */
292
+ const body = path(generatedAst).get('program', 'body');
293
+
294
+ const consumer = new SourceMapConsumer(map);
295
+
296
+ const source = consumer.sources[0];
297
+
298
+ const generator = SourceMapGenerator.fromSourceMap(consumer);
299
+
300
+ for (const originalCls of originalClasses) {
301
+
302
+ const clsName = originalCls.identifier.name;
303
+
304
+ // add mappings for classes and their constructors
305
+ // back to the original function
306
+ const generatedCls = findClass(body, clsName);
307
+
308
+ const generatedCtor = findClassCtor(generatedCls.clsDeclaration);
309
+
310
+ // add mapping from class constructor to original fn
311
+ generator.addMapping({
312
+ generated: {
313
+ line: generatedCtor.value.loc.start.line,
314
+ column: generatedCtor.value.loc.start.column,
315
+ },
316
+ name: `${clsName} constructor`,
317
+ source,
318
+ original: {
319
+ line: originalCls.loc.start.line,
320
+ column: originalCls.loc.start.column
321
+ }
322
+ });
323
+
324
+ // add mapping from class to original fn
325
+ generator.addMapping({
326
+ generated: {
327
+ line: generatedCls.node.value.loc.start.line,
328
+ column: generatedCls.node.value.loc.start.column,
329
+ },
330
+ name: `${clsName} class`,
331
+ source,
332
+ original: {
333
+ line: originalCls.loc.start.line,
334
+ column: originalCls.loc.start.column
335
+ }
336
+ });
337
+
338
+ // add mapping for static members
339
+ for (const originalMember of originalCls.staticMembers) {
340
+
341
+ const name = originalMember.identifier.name;
342
+
343
+ const generatedMember = findStaticMember(generatedCls.clsDeclaration, name);
344
+
345
+ // add mapping from member to original
346
+ generator.addMapping({
347
+ generated: {
348
+ line: generatedMember.value.loc.start.line,
349
+ column: generatedMember.value.loc.start.column,
350
+ },
351
+ name: `${clsName}#${name}`,
352
+ source,
353
+ original: {
354
+ line: originalMember.loc.start.line,
355
+ column: originalMember.loc.start.column
356
+ }
357
+ });
358
+ }
359
+ }
360
+
361
+
362
+ return generator.toJSON();
363
+ }
364
+
365
+
235
366
  function findInheritsImports(nodes) {
236
367
  const importsMatches = matcher`
237
368
  import $1 from 'inherits-browser';
@@ -261,14 +392,14 @@ function findSuperDeclarations(cls, nodes, importDeclarations) {
261
392
 
262
393
  if (inheritsExpressions.length === 1) {
263
394
 
264
- const clazz = inheritsExpressions[0][1].value.name;
395
+ const clazz = inheritsExpressions[0][1].value;
265
396
 
266
397
  const inheritsExpression = {
267
398
  node: inheritsExpressions[0][0]
268
399
  };
269
400
 
270
401
  const superCallMatches = matcher`
271
- ${clazz}.call($$$);
402
+ ${clazz.name}.call($$$);
272
403
  `;
273
404
 
274
405
  const superCalls = superCallMatches(cls.ctor.get('body', 'body'));
@@ -284,12 +415,14 @@ function findSuperDeclarations(cls, nodes, importDeclarations) {
284
415
  }
285
416
 
286
417
  /**
287
- * @param {any[]} nodes
418
+ * @param {Path<ASTNode[]>} nodes
288
419
  *
289
420
  * @return { {
290
- * name: string,
421
+ * identifier: any,
291
422
  * ctor: any,
292
- * node: any
423
+ * node: any,
424
+ * name: string,
425
+ * loc: any
293
426
  * }[] }
294
427
  */
295
428
  function findConstructors(nodes) {
@@ -335,14 +468,111 @@ function findConstructors(nodes) {
335
468
  if (isClassName(name) && !isFunctionTagged(node) && !isReturning(ctor)) {
336
469
 
337
470
  return {
471
+ identifier: identifier.value,
338
472
  name,
339
473
  ctor,
340
- node
474
+ node,
475
+ loc: node.value.loc
341
476
  };
342
477
  }
343
478
  }).filter(n => n);
344
479
  }
345
480
 
481
+ /**
482
+ * @param { Path<ASTNode> } clsNode
483
+ *
484
+ * @return { Path<ASTNode & WithLocation> }
485
+ */
486
+ function findClassCtor(clsNode) {
487
+
488
+ const ctors = clsNode.get('body', 'body').filter(node => {
489
+ const value = node.value;
490
+
491
+ return value.type === 'ClassMethod' && value.key?.name === 'constructor';
492
+ });
493
+
494
+ if (!ctors.length) {
495
+ throw new Error('no constructor');
496
+ }
497
+
498
+ return ctors[0];
499
+ }
500
+
501
+ /**
502
+ * @param { Path<ASTNode> } clsNode
503
+ * @param { string } name
504
+ *
505
+ * @return { Path<ASTNode & WithLocation> }> }
506
+ */
507
+ function findStaticMember(clsNode, name) {
508
+
509
+ const members = clsNode.get('body', 'body').filter(node => {
510
+ const value = node.value;
511
+
512
+ return [ 'ClassProperty', 'ClassMethod' ].includes(value.type) && value.static && value.key?.name === name;
513
+ });
514
+
515
+ if (!members.length) {
516
+ throw new Error(`no member ${name}`);
517
+ }
518
+
519
+ return members[0];
520
+ }
521
+
522
+ /**
523
+ * @param { Path<ASTNode[]> } nodes
524
+ * @param { string } name
525
+ *
526
+ * @return { {
527
+ * clsDeclaration: any,
528
+ * node: any
529
+ * } }
530
+ */
531
+ function findClass(nodes, name) {
532
+
533
+ const cls = matcher`
534
+ class ${name} extends $$$ {
535
+ $$$
536
+ }
537
+ `;
538
+
539
+ const namedExportCls = matcher`
540
+ export class ${name} extends $$$ {
541
+ $$$
542
+ }
543
+ `;
544
+
545
+ const defaultExportCls = matcher`
546
+ export default class ${name} extends $$$ {
547
+ $$$
548
+ }
549
+ `;
550
+
551
+ const matches = [
552
+ ...cls(nodes),
553
+ ...namedExportCls(nodes).map(
554
+ m => [ m[0].get('declaration'), m[0] ]
555
+ ),
556
+ ...defaultExportCls(nodes).map(
557
+ m => [ m[0].get('declaration'), m[0] ]
558
+ )
559
+ ].map(match => {
560
+
561
+ const [ clsDeclaration, node = clsDeclaration ] = match;
562
+
563
+ return {
564
+ clsDeclaration,
565
+ node
566
+ };
567
+ });
568
+
569
+ if (!matches.length) {
570
+ throw new Error(`not found: class ${name}`);
571
+ }
572
+
573
+ return matches[0];
574
+ }
575
+
346
576
  /**
347
577
  * @param { string } name
348
578
  *
@@ -389,6 +619,10 @@ function isReturning(ctorPath) {
389
619
  return returnStatements(ctorPath.get('body', 'body')).length > 0;
390
620
  }
391
621
 
622
+ /**
623
+ * @param {any} cls
624
+ * @param {Path<ASTNode[]>} nodes
625
+ */
392
626
  function findStaticMembers(cls, nodes) {
393
627
 
394
628
  const names = matcher`
@@ -399,14 +633,19 @@ function findStaticMembers(cls, nodes) {
399
633
  const [ node, identifier, member ] = match;
400
634
 
401
635
  return {
402
- name: identifier.value.name,
636
+ identifier: identifier.value,
403
637
  member: member.value,
404
638
  comments: node.get('comments').value,
405
- node
639
+ node,
640
+ loc: node.value.loc
406
641
  };
407
642
  });
408
643
  }
409
644
 
645
+ /**
646
+ * @param {any} cls
647
+ * @param {Path<ASTNode[]>} nodes
648
+ */
410
649
  function findProtoMembers(cls, nodes) {
411
650
 
412
651
  const names = matcher`
@@ -417,7 +656,7 @@ function findProtoMembers(cls, nodes) {
417
656
  const [ node, identifier, member ] = match;
418
657
 
419
658
  return {
420
- name: identifier.value.name,
659
+ identifier: identifier.value,
421
660
  member: member.value,
422
661
  comments: node.get('comments').value,
423
662
  node,
@@ -426,6 +665,9 @@ function findProtoMembers(cls, nodes) {
426
665
  });
427
666
  }
428
667
 
668
+ /**
669
+ * @param {any} cls
670
+ */
429
671
  function findEmbeddedMembers(cls) {
430
672
 
431
673
  const memberDeclarations = matcher`
@@ -436,7 +678,7 @@ function findEmbeddedMembers(cls) {
436
678
  const [ node, identifier, member ] = match;
437
679
 
438
680
  return {
439
- name: identifier.value.name,
681
+ identifier: identifier.value,
440
682
  member: member.value,
441
683
  comments: node.get('comments').value,
442
684
  node,
@@ -0,0 +1,43 @@
1
+ import path from 'node:path';
2
+
3
+ /**
4
+ * Read ESM-relevant compiler options from the nearest tsconfig.json,
5
+ * starting from `cwd`.
6
+ *
7
+ * Options are sourced selectively: only `module`, `moduleResolution`, and
8
+ * `target` are returned because these affect how declarations are generated.
9
+ * Other options (strict, lib, paths, …) are not meaningful here.
10
+ *
11
+ * @param { import('typescript') } ts
12
+ * @param { string } [cwd]
13
+ *
14
+ * @return { Partial<import('typescript').CompilerOptions> }
15
+ */
16
+ export function readTsConfigOptions(ts, cwd = process.cwd()) {
17
+
18
+ const configPath = ts.findConfigFile(cwd, ts.sys.fileExists);
19
+
20
+ if (!configPath) {
21
+ return {};
22
+ }
23
+
24
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
25
+
26
+ if (configFile.error) {
27
+ return {};
28
+ }
29
+
30
+ const { options } = ts.parseJsonConfigFileContent(
31
+ configFile.config,
32
+ ts.sys,
33
+ path.dirname(configPath)
34
+ );
35
+
36
+ const result = /** @type { Partial<import('typescript').CompilerOptions> } */ ({});
37
+
38
+ if (options.module !== undefined) result.module = options.module;
39
+ if (options.moduleResolution !== undefined) result.moduleResolution = options.moduleResolution;
40
+ if (options.target !== undefined) result.target = options.target;
41
+
42
+ return result;
43
+ }
package/lib/util.js CHANGED
@@ -6,8 +6,22 @@ import { Path as PathConstructor } from 'ast-types';
6
6
  * @typedef { import('ast-types/lib/path.js').Path<T> } Path
7
7
  */
8
8
 
9
+ /**
10
+ * @typedef { import('recast/lib/printer.js').PrintResultType } PrintResult
11
+ */
12
+
13
+ /**
14
+ * @typedef { {
15
+ * inputSourceMap?: string,
16
+ * sourceFileName?: string,
17
+ * sourceMapName?: string,
18
+ * sourceRoot?: string,
19
+ * } } ParseOptions
20
+ */
21
+
9
22
  import * as typescriptParser from './parsers/typescript.js';
10
23
 
24
+
11
25
  const DBG = /match/.test(process?.env?.LOG_DEBUG);
12
26
 
13
27
  const formatOptions = {
@@ -50,22 +64,35 @@ export function hasProperty(obj, property) {
50
64
 
51
65
  /**
52
66
  * @param { string } code
53
- * @param { { jsx?: boolean } } [parseOptions]
67
+ * @param { ParseOptions & { jsx?: boolean } } [parseOptions]
54
68
  *
55
69
  * @return {any}
56
70
  */
57
- export function parse(code, parseOptions) {
71
+ export function parse(code, parseOptions = {}) {
72
+
73
+ const {
74
+ jsx = false,
75
+ ...options
76
+ } = parseOptions;
77
+
58
78
  const parser = {
59
- parse: parseOptions?.jsx ? typescriptParser.jsx : typescriptParser.js
79
+ parse: jsx ? typescriptParser.jsx : typescriptParser.js
60
80
  };
61
81
 
62
82
  return recastParse(code, {
63
83
  parser,
64
- ...formatOptions
84
+ ...formatOptions,
85
+ ...options
65
86
  });
66
87
  }
67
88
 
68
- export function parseDts(code) {
89
+ /**
90
+ * @param { string } code
91
+ * @param { ParseOptions } [options]
92
+ *
93
+ * @return { any }
94
+ */
95
+ export function parseDts(code, options = {}) {
69
96
 
70
97
  const parser = {
71
98
  parse: typescriptParser.dts
@@ -73,12 +100,22 @@ export function parseDts(code) {
73
100
 
74
101
  return recastParse(code, {
75
102
  parser,
76
- ...formatOptions
103
+ ...formatOptions,
104
+ ...options
77
105
  });
78
106
  }
79
107
 
80
- export function print(node) {
81
- return recastPrint(node, formatOptions);
108
+ /**
109
+ * @param { any } node
110
+ * @param { ParseOptions } [options]
111
+ *
112
+ * @return { PrintResult }
113
+ */
114
+ export function print(node, options = {}) {
115
+ return recastPrint(node, {
116
+ ...formatOptions,
117
+ ...options
118
+ });
82
119
  }
83
120
 
84
121
  /**
@@ -139,6 +176,10 @@ export function matcher(strings, ...args) {
139
176
  node = node[0];
140
177
  }
141
178
 
179
+ if (node && node.type === 'ClassProperty') {
180
+ node = node.key;
181
+ }
182
+
142
183
  if (node && node.type === 'ExpressionStatement') {
143
184
  node = node.expression;
144
185
  }
@@ -182,6 +223,10 @@ export function matcher(strings, ...args) {
182
223
  node = node[0];
183
224
  }
184
225
 
226
+ if (node && node.type === 'ClassProperty') {
227
+ node = node.key;
228
+ }
229
+
185
230
  if (node && node.type === 'ExpressionStatement') {
186
231
  node = node.expression;
187
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bio-dts",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "Generate sane and clean types from JavaScript sources",
5
5
  "type": "module",
6
6
  "bin": "./bin/cmd.js",
@@ -11,14 +11,16 @@
11
11
  "./package.json": "./package.json"
12
12
  },
13
13
  "scripts": {
14
- "all": "run-s lint check-types test test:dts:*",
14
+ "all": "run-s lint check-types test test:dts",
15
15
  "lint": "eslint .",
16
16
  "check-types": "tsc --noEmit",
17
17
  "test": "mocha test",
18
- "test:dts:default": "node bin/cmd.js --outDir test/fixtures/snapshots/default -r test/fixtures/pre",
19
- "test:dts:jsx": "node bin/cmd.js --jsx preserve --outDir test/fixtures/snapshots/jsx -r test/fixtures/jsx",
20
- "test:dts:declaration-map": "node bin/cmd.js --declarationMap --outDir test/fixtures/snapshots/declaration-map -r test/fixtures/pre",
21
- "test:dts:lax": "node bin/cmd.js --declarationMap --lax --outDir test/fixtures/snapshots/lax -r test/fixtures/lax"
18
+ "test:dts": "run-s test:dts:*",
19
+ "test:dts:default": "node bin/cmd.js --module CommonJS --outDir test/fixtures/snapshots/default -r test/fixtures/pre",
20
+ "test:dts:jsx": "node bin/cmd.js --module CommonJS --jsx preserve --outDir test/fixtures/snapshots/jsx -r test/fixtures/jsx",
21
+ "test:dts:declaration-map": "node bin/cmd.js --module CommonJS --declarationMap --outDir test/fixtures/snapshots/declaration-map -r test/fixtures/pre",
22
+ "test:dts:lax": "node bin/cmd.js --module CommonJS --declarationMap --lax --outDir test/fixtures/snapshots/lax -r test/fixtures/lax",
23
+ "test:dts:esm": "node bin/cmd.js --outDir test/fixtures/snapshots/esm -r test/fixtures/esm"
22
24
  },
23
25
  "repository": {
24
26
  "type": "git",
@@ -27,19 +29,21 @@
27
29
  "author": "Nico Rehwaldt",
28
30
  "license": "MIT",
29
31
  "dependencies": {
30
- "@babel/parser": "^7.28.5",
32
+ "@babel/parser": "^7.29.7",
33
+ "@jridgewell/remapping": "^2.3.5",
34
+ "@jridgewell/source-map": "^0.3.11",
31
35
  "recast": "^0.23.11",
32
36
  "tiny-glob": "^0.2.9"
33
37
  },
34
38
  "devDependencies": {
35
- "@babel/types": "^7.28.5",
39
+ "@babel/types": "^7.29.7",
36
40
  "@types/mocha": "^10.0.10",
37
- "@types/node": "^24.10.4",
38
- "chai": "^6.2.1",
39
- "eslint": "^8.56.0",
40
- "eslint-plugin-bpmn-io": "^1.0.0",
41
- "mocha": "^11.7.5",
42
- "npm-run-all2": "^8.0.4",
41
+ "@types/node": "^24.13.0",
42
+ "chai": "^6.2.2",
43
+ "eslint": "^9.39.4",
44
+ "eslint-plugin-bpmn-io": "^2.3.1",
45
+ "mocha": "^11.7.6",
46
+ "npm-run-all2": "^9.0.1",
43
47
  "typescript": "^5.9.3"
44
48
  },
45
49
  "sideEffects": false,