bio-dts 0.13.0 → 0.14.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023-present Nico Rehwaldt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -58,3 +58,8 @@ generateTypes(files, {
58
58
  outDir: 'dist'
59
59
  }, typescript);
60
60
  ```
61
+
62
+
63
+ ## License
64
+
65
+ MIT
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,37 +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
71
  if (fileName.toLowerCase().endsWith('.d.ts')) {
68
72
  try {
69
- text = postTransform(text);
73
+ text = postTransform(text, generateOptions);
70
74
  } catch (err) {
71
75
  console.error(`[generate-types] [post] failed to parse ${fileName} with contents ${text}`, err);
72
76
  throw err;
73
77
  }
74
78
 
75
- isVerbose(options, fileName) && console.debug('[generate-types] [post] [generated]', fileName, text);
79
+ isVerbose(compilerOptions, fileName) && console.debug('[generate-types] [post] [generated]', fileName, text);
76
80
  }
77
81
 
78
82
  // @ts-expect-error
79
83
  host._writeFile(fileName, text, ...args);
80
84
  };
81
85
 
82
- const program = ts.createProgram(fileNames, options, host);
86
+ const program = ts.createProgram(fileNames, compilerOptions, host);
83
87
 
84
88
  return program.emit();
85
89
  }
86
90
 
87
- function isVerbose(options, fileName) {
91
+ /**
92
+ * @param { CompilerOptions } compilerOptions
93
+ * @param { any } fileName
94
+ */
95
+ function isVerbose(compilerOptions, fileName) {
88
96
 
89
- if (typeof options.verbose === 'boolean') {
90
- return options.verbose;
97
+ if (typeof compilerOptions.verbose === 'boolean') {
98
+ return compilerOptions.verbose;
91
99
  }
92
100
 
93
- if (typeof options.verbose === 'undefined') {
101
+ if (typeof compilerOptions.verbose === 'undefined') {
94
102
  return false;
95
103
  }
96
104
 
97
- return fileName.includes(options.verbose);
105
+ return fileName.includes(compilerOptions.verbose);
98
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 (isNamedParam(param) && 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
 
@@ -292,9 +298,9 @@ export default function transform(src) {
292
298
  * type Woop = import('./Woop').default;
293
299
  * ```
294
300
  *
295
- * @param {Path<any>} nodePath
301
+ * @param { Path<any> } nodePath
296
302
  *
297
- * @return {any[]} replacements
303
+ * @return { any[] } replacements
298
304
  */
299
305
  function fixTypeExport(nodePath) {
300
306
 
@@ -315,9 +321,9 @@ export default function transform(src) {
315
321
  /**
316
322
  * Ensure that only documented method parameters are used.
317
323
  *
318
- * @param {Path<any>} nodePath
324
+ * @param { Path<any> } nodePath
319
325
  *
320
- * @return {boolean} true if modified
326
+ * @return { boolean } true if modified
321
327
  */
322
328
  function removeUnknownParams(nodePath) {
323
329
 
@@ -362,9 +368,9 @@ export default function transform(src) {
362
368
  * Our strategy is to parse for separate `@overlord` annotated tags,
363
369
  * use the meta-data, and generate a completely new method from it.
364
370
  *
365
- * @param {Path<any>} nodePath
371
+ * @param { Path<any> } nodePath
366
372
  *
367
- * @return {boolean} true if modified
373
+ * @return { boolean } true if modified
368
374
  */
369
375
  function generateOverloads(nodePath) {
370
376
 
@@ -503,10 +509,10 @@ declare function p${
503
509
 
504
510
  // remove full line including the non-TS tag
505
511
  if (
506
- /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) ||
507
513
  tag.param?.name?.includes('.')
508
514
  ) {
509
- replacements.push([ { start: tag.start - 4, end: tag.end } ]);
515
+ replacements.push([ { start: Math.max(0, tag.start - 4), end: tag.end } ]);
510
516
 
511
517
  continue;
512
518
  }
@@ -553,7 +559,7 @@ declare function p${
553
559
  }
554
560
 
555
561
  /**
556
- * @param {Path<any>} nodePath
562
+ * @param { Path<any> } nodePath
557
563
  */
558
564
  function cleanComments(nodePath) {
559
565
 
@@ -646,8 +652,8 @@ function traverse(path, cb) {
646
652
 
647
653
 
648
654
  /**
649
- * @param {any} node
650
- * @return {'TSDeclareMethod' | 'TSDeclareFunction' | 'TSFunctionType' | null}
655
+ * @param { any } node
656
+ * @return { 'TSDeclareMethod' | 'TSDeclareFunction' | 'TSFunctionType' | null }
651
657
  */
652
658
  function getFunctionKind(node) {
653
659
 
@@ -673,9 +679,9 @@ function getFunctionKind(node) {
673
679
  /**
674
680
  * Return host path for node (with attached comments).
675
681
  *
676
- * @param {Path<any>} nodePath
682
+ * @param { Path<any> } nodePath
677
683
  *
678
- * @return {Path<any>}
684
+ * @return { Path<any> }
679
685
  */
680
686
  function getHostPath(nodePath) {
681
687
  return [ 'ExportDefaultDeclaration', 'ExportNamedDeclaration' ].includes(nodePath.parentPath.value.type)
@@ -690,7 +696,7 @@ function isNamedParam(param) {
690
696
  /**
691
697
  * @param { { loc: { start: { line: number, column: number } } } } node
692
698
  *
693
- * @return {Error}
699
+ * @return { Error }
694
700
  */
695
701
  function error(node, message) {
696
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,9 +1,10 @@
1
1
  {
2
2
  "name": "bio-dts",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "Generate sane and clean types from JavaScript sources",
5
5
  "type": "module",
6
- "bin": "bin/cmd.js",
6
+ "bin": "./bin/cmd.js",
7
+ "main": "./index.js",
7
8
  "exports": {
8
9
  ".": "./index.js",
9
10
  "./lib/*.js": "./lib/*.js",
@@ -14,8 +15,10 @@
14
15
  "lint": "eslint .",
15
16
  "check-types": "tsc --noEmit",
16
17
  "test": "mocha test",
17
- "test:dts:default": "node bin/cmd.js --outDir test/fixtures/snapshots/default -r test/fixtures",
18
- "test:dts:declaration-map": "node bin/cmd.js --declarationMap --outDir test/fixtures/snapshots/declaration-map -r test/fixtures"
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"
19
22
  },
20
23
  "repository": {
21
24
  "type": "git",
@@ -24,21 +27,22 @@
24
27
  "author": "Nico Rehwaldt",
25
28
  "license": "MIT",
26
29
  "dependencies": {
27
- "@babel/parser": "^7.26.3",
28
- "recast": "^0.23.9",
30
+ "@babel/parser": "^7.28.5",
31
+ "recast": "^0.23.11",
29
32
  "tiny-glob": "^0.2.9"
30
33
  },
31
34
  "devDependencies": {
32
- "@babel/types": "^7.23.9",
35
+ "@babel/types": "^7.28.5",
33
36
  "@types/mocha": "^10.0.10",
34
- "@types/node": "^20.17.12",
35
- "chai": "^5.1.2",
37
+ "@types/node": "^24.10.4",
38
+ "chai": "^6.2.1",
36
39
  "eslint": "^8.56.0",
37
40
  "eslint-plugin-bpmn-io": "^1.0.0",
38
- "mocha": "^10.8.2",
39
- "npm-run-all2": "^7.0.2",
40
- "typescript": "^5.7.3"
41
+ "mocha": "^11.7.5",
42
+ "npm-run-all2": "^8.0.4",
43
+ "typescript": "^5.9.3"
41
44
  },
45
+ "sideEffects": false,
42
46
  "files": [
43
47
  "bin",
44
48
  "lib",
@@ -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
- }