bio-dts 0.12.0 → 0.14.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
@@ -34,7 +34,7 @@ Checkout the [test fixtures](./test/fixtures) for full coverage.
34
34
 
35
35
  ## API
36
36
 
37
- ```javascript
37
+ ```js
38
38
  import {
39
39
  preTransform,
40
40
  postTransform,
package/bin/cmd.js CHANGED
@@ -26,12 +26,14 @@ async function run() {
26
26
 
27
27
  const verbose = _verbose ? _verboseGrep || true : false;
28
28
  const recursive = args.includes('--recursive') || args.includes('-r');
29
+ const lax = args.includes('--lax');
29
30
 
30
31
  if (help) {
31
32
  console.log(`Usage: bio-dts [options] [...filesOrGlobs]
32
33
 
33
34
  Options:
34
35
  --recursive, -r recurse into directories
36
+ --lax relax certain checks (when running on generated code)
35
37
  --verbose enable verbose logging
36
38
 
37
39
  Additional options will be passed to the typescript generator.
@@ -73,7 +75,7 @@ async function run() {
73
75
 
74
76
  const {
75
77
  diagnostics
76
- } = generateTypes(files, generateOptions, ts);
78
+ } = generateTypes(files, generateOptions, ts, { lax });
77
79
 
78
80
  const errors = diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error);
79
81
 
@@ -16,18 +16,22 @@ import postTransform from './post-transform.js';
16
16
 
17
17
  /**
18
18
  * @param { string[] } fileNames
19
- * @param { CompilerOptions } options
19
+ * @param { CompilerOptions } compilerOptions
20
20
  * @param { TypeScript } ts
21
+ * @param { { lax: boolean } } generateOptions
21
22
  */
22
- export default function generateTypes(fileNames, options, ts) {
23
+ export default function generateTypes(fileNames, compilerOptions, ts, generateOptions) {
23
24
 
24
25
  if (!ts) {
25
26
  throw new Error('must provide <ts=TypeScript>');
26
27
  }
27
28
 
29
+ // CompilerOptions.JsxEmit.None = 0
30
+ const jsx = compilerOptions.jsx !== 0;
31
+
28
32
  const names = new Set(fileNames.map((p) => path.resolve(p)));
29
33
 
30
- const host = ts.createCompilerHost(options);
34
+ const host = ts.createCompilerHost(compilerOptions);
31
35
 
32
36
  // @ts-expect-error
33
37
  host._readFile = host.readFile;
@@ -43,16 +47,16 @@ export default function generateTypes(fileNames, options, ts) {
43
47
  let src = host._readFile(fileName);
44
48
 
45
49
  if (names.has(path.resolve(fileName))) {
46
- isVerbose(options, fileName) && console.debug('[generate-types] [pre]', fileName, src);
50
+ isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [pre]', fileName, src);
47
51
 
48
52
  try {
49
- src = preTransform(src);
53
+ src = preTransform(src, { jsx });
50
54
  } catch (err) {
51
55
  console.error(`[generate-types] [pre] failed to parse ${fileName} with contents ${src}`, err);
52
56
  throw err;
53
57
  }
54
58
 
55
- isVerbose(options, fileName) && console.debug('[generate-types] [pre] [generated]', fileName, src);
59
+ isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [pre] [generated]', fileName, src);
56
60
  }
57
61
 
58
62
  return src;
@@ -62,35 +66,41 @@ export default function generateTypes(fileNames, options, ts) {
62
66
  host._writeFile = host.writeFile;
63
67
 
64
68
  host.writeFile = (fileName, text, ...args) => {
65
- isVerbose(options, fileName) && console.debug('[generate-types] [post]', fileName, text);
69
+ isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [post]', fileName, text);
66
70
 
67
- try {
68
- text = postTransform(text);
69
- } catch (err) {
70
- console.error(`[generate-types] [post] failed to parse ${fileName} with contents ${text}`, err);
71
- throw err;
72
- }
71
+ if (fileName.toLowerCase().endsWith('.d.ts')) {
72
+ try {
73
+ text = postTransform(text, generateOptions);
74
+ } catch (err) {
75
+ console.error(`[generate-types] [post] failed to parse ${fileName} with contents ${text}`, err);
76
+ throw err;
77
+ }
73
78
 
74
- isVerbose(options, fileName) && console.debug('[generate-types] [post] [generated]', fileName, text);
79
+ isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [post] [generated]', fileName, text);
80
+ }
75
81
 
76
82
  // @ts-expect-error
77
83
  host._writeFile(fileName, text, ...args);
78
84
  };
79
85
 
80
- const program = ts.createProgram(fileNames, options, host);
86
+ const program = ts.createProgram(fileNames, compilerOptions, host);
81
87
 
82
88
  return program.emit();
83
89
  }
84
90
 
85
- function isVerbose(options, fileName) {
91
+ /**
92
+ * @param { CompilerOptions } compilerOptions
93
+ * @param { any } fileName
94
+ */
95
+ function isVerbose(compilerOptions, fileName) {
86
96
 
87
- if (typeof options.verbose === 'boolean') {
88
- return options.verbose;
97
+ if (typeof compilerOptions.verbose === 'boolean') {
98
+ return compilerOptions.verbose;
89
99
  }
90
100
 
91
- if (typeof options.verbose === 'undefined') {
101
+ if (typeof compilerOptions.verbose === 'undefined') {
92
102
  return false;
93
103
  }
94
104
 
95
- return fileName.includes(options.verbose);
105
+ return fileName.includes(compilerOptions.verbose);
96
106
  }
@@ -1 +1,57 @@
1
- export * from 'recast/parsers/typescript.js';
1
+ import { parser } from 'recast/parsers/babel.js';
2
+ import getBabelOptions from 'recast/parsers/_babel_options.js';
3
+
4
+ /**
5
+ * @typedef { import('recast/parsers/_babel_options.js').Overrides } Overrides
6
+ *
7
+ * @typedef { import('@babel/parser').ParserPlugin } ParserPlugin
8
+ *
9
+ * @typedef { { jsx: boolean } } ParseOptions
10
+ */
11
+
12
+ /**
13
+ * @param { string } source
14
+ * @param { ParserPlugin[] } plugins
15
+ * @param { Overrides } [options]
16
+ *
17
+ * @return { import('@babel/types').File }
18
+ */
19
+ function _parse(source, plugins, options) {
20
+ const babelOptions = getBabelOptions.default(options);
21
+
22
+ for (const plugin of plugins) {
23
+ babelOptions.plugins.push(plugin);
24
+ }
25
+
26
+ return parser.parse(source, babelOptions);
27
+ }
28
+
29
+ /**
30
+ * @param { string } source
31
+ * @param { Overrides } [options]
32
+ *
33
+ * @return { import('@babel/types').File }
34
+ */
35
+ export function js(source, options) {
36
+ return _parse(source, [ 'typescript' ], options);
37
+ }
38
+
39
+ /**
40
+ * @param { string } source
41
+ * @param { Overrides } [options]
42
+ *
43
+ * @return { import('@babel/types').File }
44
+ */
45
+ export function jsx(source, options) {
46
+ return _parse(source, [ 'typescript', 'jsx' ], options);
47
+ }
48
+
49
+ /**
50
+ * @param { string } source
51
+ * @param { Overrides } [options]
52
+ *
53
+ * @return { import('@babel/types').File }
54
+ */
55
+ export function dts(source, options) {
56
+ return _parse(source, [ [ 'typescript', { dts: true } ] ], options);
57
+ }
@@ -18,7 +18,7 @@ import {
18
18
  /**
19
19
  * @param { { name: string, key?: { name?: string }, comments?: any[] } } m
20
20
  *
21
- * @return {boolean}
21
+ * @return { boolean }
22
22
  */
23
23
  function isPublic(m) {
24
24
 
@@ -34,10 +34,16 @@ function isPublic(m) {
34
34
  }
35
35
 
36
36
  /**
37
- * @param {string} src
38
- * @return {string}
37
+ * @param { string } src
38
+ * @param { { lax: boolean } } [options]
39
+ *
40
+ * @return { string }
39
41
  */
40
- export default function transform(src) {
42
+ export default function transform(src, options) {
43
+
44
+ const {
45
+ lax = false
46
+ } = options || {};
41
47
 
42
48
  const ast = parseDts(src);
43
49
  const body = path(ast).get('program', 'body');
@@ -127,11 +133,11 @@ export default function transform(src) {
127
133
 
128
134
  const actualName = param.argument?.name || param.name;
129
135
 
130
- if (actualName !== expectedName) {
136
+ if (!lax && isNamedParam(param) && actualName !== expectedName) {
131
137
  throw error(node, `documented parameter <${ expectedName }> differs from actual parameter <${ actualName }>`);
132
138
  }
133
139
 
134
- filteredParams.push(param);
140
+ filteredParams.push({ ...param, name: expectedName });
135
141
  j++;
136
142
  }
137
143
 
@@ -141,9 +147,9 @@ export default function transform(src) {
141
147
  /**
142
148
  * Ensure optional args methods are properly escaped
143
149
  *
144
- * @param {Path<any>} nodePath
150
+ * @param { Path<any> } nodePath
145
151
  *
146
- * @return {any[]} replacements
152
+ * @return { any[] } replacements
147
153
  */
148
154
  function fixOptionalArgsMethods(nodePath) {
149
155
 
@@ -210,7 +216,8 @@ export default function transform(src) {
210
216
  variations.forEach((variation) => {
211
217
  const builder = {
212
218
  'RestElement': b.restElement,
213
- 'Identifier': b.identifier
219
+ 'Identifier': b.identifier,
220
+ 'ObjectPattern': b.objectPattern
214
221
  }[param.type];
215
222
 
216
223
  variation.push(builder.from(param));
@@ -291,9 +298,9 @@ export default function transform(src) {
291
298
  * type Woop = import('./Woop').default;
292
299
  * ```
293
300
  *
294
- * @param {Path<any>} nodePath
301
+ * @param { Path<any> } nodePath
295
302
  *
296
- * @return {any[]} replacements
303
+ * @return { any[] } replacements
297
304
  */
298
305
  function fixTypeExport(nodePath) {
299
306
 
@@ -314,9 +321,9 @@ export default function transform(src) {
314
321
  /**
315
322
  * Ensure that only documented method parameters are used.
316
323
  *
317
- * @param {Path<any>} nodePath
324
+ * @param { Path<any> } nodePath
318
325
  *
319
- * @return {boolean} true if modified
326
+ * @return { boolean } true if modified
320
327
  */
321
328
  function removeUnknownParams(nodePath) {
322
329
 
@@ -361,9 +368,9 @@ export default function transform(src) {
361
368
  * Our strategy is to parse for separate `@overlord` annotated tags,
362
369
  * use the meta-data, and generate a completely new method from it.
363
370
  *
364
- * @param {Path<any>} nodePath
371
+ * @param { Path<any> } nodePath
365
372
  *
366
- * @return {boolean} true if modified
373
+ * @return { boolean } true if modified
367
374
  */
368
375
  function generateOverloads(nodePath) {
369
376
 
@@ -502,10 +509,10 @@ declare function p${
502
509
 
503
510
  // remove full line including the non-TS tag
504
511
  if (
505
- /class|constructor|template|method|typedef|property|this|overlord|overload/.test(tag.name) ||
512
+ /class|function|extends|type|constructor|template|method|typedef|property|this|overlord|overload/.test(tag.name) ||
506
513
  tag.param?.name?.includes('.')
507
514
  ) {
508
- replacements.push([ { start: tag.start - 4, end: tag.end } ]);
515
+ replacements.push([ { start: Math.max(0, tag.start - 4), end: tag.end } ]);
509
516
 
510
517
  continue;
511
518
  }
@@ -552,7 +559,7 @@ declare function p${
552
559
  }
553
560
 
554
561
  /**
555
- * @param {Path<any>} nodePath
562
+ * @param { Path<any> } nodePath
556
563
  */
557
564
  function cleanComments(nodePath) {
558
565
 
@@ -645,8 +652,8 @@ function traverse(path, cb) {
645
652
 
646
653
 
647
654
  /**
648
- * @param {any} node
649
- * @return {'TSDeclareMethod' | 'TSDeclareFunction' | 'TSFunctionType' | null}
655
+ * @param { any } node
656
+ * @return { 'TSDeclareMethod' | 'TSDeclareFunction' | 'TSFunctionType' | null }
650
657
  */
651
658
  function getFunctionKind(node) {
652
659
 
@@ -672,9 +679,9 @@ function getFunctionKind(node) {
672
679
  /**
673
680
  * Return host path for node (with attached comments).
674
681
  *
675
- * @param {Path<any>} nodePath
682
+ * @param { Path<any> } nodePath
676
683
  *
677
- * @return {Path<any>}
684
+ * @return { Path<any> }
678
685
  */
679
686
  function getHostPath(nodePath) {
680
687
  return [ 'ExportDefaultDeclaration', 'ExportNamedDeclaration' ].includes(nodePath.parentPath.value.type)
@@ -682,10 +689,14 @@ function getHostPath(nodePath) {
682
689
  : nodePath;
683
690
  }
684
691
 
692
+ function isNamedParam(param) {
693
+ return param.type === 'Identifier' || param.type == 'RestElement';
694
+ }
695
+
685
696
  /**
686
697
  * @param { { loc: { start: { line: number, column: number } } } } node
687
698
  *
688
- * @return {Error}
699
+ * @return { Error }
689
700
  */
690
701
  function error(node, message) {
691
702
 
@@ -1,5 +1,14 @@
1
1
  import { matcher, parse, path, print } from './util.js';
2
2
 
3
+ /**
4
+ * @template T
5
+ * @typedef { import('./util.js').Path<T> } Path
6
+ */
7
+
8
+ import {
9
+ parse as parseJSDoc
10
+ } from './parsers/jsdoc.js';
11
+
3
12
  import {
4
13
  builders as b
5
14
  } from 'ast-types';
@@ -149,13 +158,14 @@ function splitComment(cls) {
149
158
  }
150
159
 
151
160
  /**
152
- * @param {string} src
161
+ * @param { string } src
162
+ * @param { { jsx?: boolean } } [parseOptions]
153
163
  *
154
164
  * @return {string}
155
165
  */
156
- export default function transform(src) {
166
+ export default function transform(src, parseOptions = {}) {
157
167
 
158
- const ast = parse(src);
168
+ const ast = parse(src, parseOptions);
159
169
  const body = path(ast).get('program', 'body');
160
170
 
161
171
  const inheritsImports = findInheritsImports(body);
@@ -316,7 +326,14 @@ function findConstructors(nodes) {
316
326
 
317
327
  const name = identifier.value.name;
318
328
 
319
- if (/^[A-Z]/.test(name)) {
329
+ // a class component
330
+ //
331
+ // * must start with upper letter
332
+ // * must not be tagged as @function
333
+ // * must not return something
334
+ //
335
+ if (isClassName(name) && !isFunctionTagged(node) && !isReturning(ctor)) {
336
+
320
337
  return {
321
338
  name,
322
339
  ctor,
@@ -326,6 +343,52 @@ function findConstructors(nodes) {
326
343
  }).filter(n => n);
327
344
  }
328
345
 
346
+ /**
347
+ * @param { string } name
348
+ *
349
+ * @return {boolean}
350
+ */
351
+ function isClassName(name) {
352
+
353
+ return /^[A-Z]/.test(name);
354
+ }
355
+
356
+ /**
357
+ * @param { Path<any> } nodePath
358
+ *
359
+ * @return { boolean }
360
+ */
361
+ function isFunctionTagged(nodePath) {
362
+ const commentPaths = nodePath.get('comments');
363
+
364
+ // last comment is significant
365
+ const commentPath = commentPaths?.value && commentPaths.get(commentPaths.value.length - 1) || { value: null };
366
+
367
+ const doc = commentPath?.value?.value?.replace(/\n\s+/g, '\n ') || '';
368
+
369
+ if (!doc) {
370
+ return false;
371
+ }
372
+
373
+ const functionTagged = parseJSDoc(doc).some(tag => tag.name === 'function');
374
+
375
+ return functionTagged;
376
+ }
377
+
378
+ /**
379
+ * @param { Path<any> } ctorPath
380
+ *
381
+ * @return { boolean }
382
+ */
383
+ function isReturning(ctorPath) {
384
+
385
+ const returnStatements = matcher`
386
+ return $1;
387
+ `;
388
+
389
+ return returnStatements(ctorPath.get('body', 'body')).length > 0;
390
+ }
391
+
329
392
  function findStaticMembers(cls, nodes) {
330
393
 
331
394
  const names = matcher`
package/lib/util.js CHANGED
@@ -7,7 +7,6 @@ import { Path as PathConstructor } from 'ast-types';
7
7
  */
8
8
 
9
9
  import * as typescriptParser from './parsers/typescript.js';
10
- import * as typescriptDtsParser from './parsers/typescript-dts.js';
11
10
 
12
11
  const DBG = /match/.test(process?.env?.LOG_DEBUG);
13
12
 
@@ -50,20 +49,30 @@ export function hasProperty(obj, property) {
50
49
  }
51
50
 
52
51
  /**
53
- * @param {string} code
52
+ * @param { string } code
53
+ * @param { { jsx?: boolean } } [parseOptions]
54
54
  *
55
55
  * @return {any}
56
56
  */
57
- export function parse(code) {
57
+ export function parse(code, parseOptions) {
58
+ const parser = {
59
+ parse: parseOptions?.jsx ? typescriptParser.jsx : typescriptParser.js
60
+ };
61
+
58
62
  return recastParse(code, {
59
- parser: typescriptParser,
63
+ parser,
60
64
  ...formatOptions
61
65
  });
62
66
  }
63
67
 
64
68
  export function parseDts(code) {
69
+
70
+ const parser = {
71
+ parse: typescriptParser.dts
72
+ };
73
+
65
74
  return recastParse(code, {
66
- parser: typescriptDtsParser,
75
+ parser,
67
76
  ...formatOptions
68
77
  });
69
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bio-dts",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Generate sane and clean types from JavaScript sources",
5
5
  "type": "module",
6
6
  "bin": "bin/cmd.js",
@@ -10,11 +10,14 @@
10
10
  "./package.json": "./package.json"
11
11
  },
12
12
  "scripts": {
13
- "all": "run-s lint check-types test test:dts",
13
+ "all": "run-s lint check-types test test:dts:*",
14
14
  "lint": "eslint .",
15
15
  "check-types": "tsc --noEmit",
16
16
  "test": "mocha test",
17
- "test:dts": "node bin/cmd.js --outDir test/fixtures/snapshots -r test/fixtures"
17
+ "test:dts:default": "node bin/cmd.js --outDir test/fixtures/snapshots/default -r test/fixtures/pre",
18
+ "test:dts:jsx": "node bin/cmd.js --jsx preserve --outDir test/fixtures/snapshots/jsx -r test/fixtures/jsx",
19
+ "test:dts:declaration-map": "node bin/cmd.js --declarationMap --outDir test/fixtures/snapshots/declaration-map -r test/fixtures/pre",
20
+ "test:dts:lax": "node bin/cmd.js --declarationMap --lax --outDir test/fixtures/snapshots/lax -r test/fixtures/lax"
18
21
  },
19
22
  "repository": {
20
23
  "type": "git",
@@ -23,20 +26,20 @@
23
26
  "author": "Nico Rehwaldt",
24
27
  "license": "MIT",
25
28
  "dependencies": {
26
- "@babel/parser": "^7.26.3",
27
- "recast": "^0.23.9",
29
+ "@babel/parser": "^7.28.4",
30
+ "recast": "^0.23.11",
28
31
  "tiny-glob": "^0.2.9"
29
32
  },
30
33
  "devDependencies": {
31
- "@babel/types": "^7.23.9",
34
+ "@babel/types": "^7.28.4",
32
35
  "@types/mocha": "^10.0.10",
33
36
  "@types/node": "^20.17.12",
34
37
  "chai": "^5.1.2",
35
38
  "eslint": "^8.56.0",
36
39
  "eslint-plugin-bpmn-io": "^1.0.0",
37
40
  "mocha": "^10.8.2",
38
- "npm-run-all2": "^7.0.2",
39
- "typescript": "^5.7.3"
41
+ "npm-run-all2": "^8.0.4",
42
+ "typescript": "^5.9.2"
40
43
  },
41
44
  "files": [
42
45
  "bin",
@@ -1,19 +0,0 @@
1
- import { parser } from 'recast/parsers/babel.js';
2
- import getBabelOptions from 'recast/parsers/_babel_options.js';
3
-
4
- /**
5
- * @typedef { import('recast/parsers/_babel_options.js').Overrides } Overrides
6
- */
7
-
8
- /**
9
- * @param {string} source
10
- * @param {Overrides} [options]
11
- *
12
- * @return {import('@babel/types').File}
13
- */
14
- export function parse(source, options) {
15
-
16
- const babelOptions = getBabelOptions.default(options);
17
- babelOptions.plugins.push([ 'typescript', { dts: true } ]);
18
- return parser.parse(source, babelOptions);
19
- }