diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ec3ce..a24f778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # dts-buddy changelog +## 0.5.3 + +- Support `import * as X` imports ([#89](https://github.com/Rich-Harris/dts-buddy/pull/89)) +- Fix deduplication logic to rename exports in fewer cases ([#89](https://github.com/Rich-Harris/dts-buddy/pull/89)) +- Recognize import dependencies within namespaces ([#89](https://github.com/Rich-Harris/dts-buddy/pull/89)) + ## 0.5.2 - Support stripping internal types using `@internal` JSDoc tags ([#87](https://github.com/Rich-Harris/dts-buddy/pull/87)) diff --git a/package.json b/package.json index d578e86..3d89b18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dts-buddy", - "version": "0.5.2", + "version": "0.5.3", "description": "A tool for creating .d.ts bundles", "repository": { "type": "git", diff --git a/src/create-module-declaration.js b/src/create-module-declaration.js index 3af3939..084316e 100644 --- a/src/create-module-declaration.js +++ b/src/create-module-declaration.js @@ -239,6 +239,24 @@ export function create_module_declaration(id, entry, created, resolve, options) const index = module.dts.indexOf('//# sourceMappingURL='); if (index !== -1) result.remove(index, module.dts.length); + if (module.import_all.size > 0) { + // remove the leading `Foo.` from references to `import * as Foo` namespace imports + walk(module.ast, (node) => { + if (is_reference(node) && ts.isQualifiedName(node.parent)) { + const binding = module.import_all.get(node.getText(module.ast)); + if (binding) { + result.remove(node.pos, result.original.indexOf('.', node.end) + 1); + const declaration = bundle + .get(binding.id) + ?.declarations.get(node.parent.right.getText(module.ast)); + if (declaration?.alias) { + result.overwrite(node.parent.right.pos, node.parent.right.end, declaration.alias); + } + } + } + }); + } + ts.forEachChild(module.ast, (node) => { if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { result.remove(node.pos, node.end); @@ -366,7 +384,8 @@ export function create_module_declaration(id, entry, created, resolve, options) return false; } - if (is_reference(node)) { + // We need to include the declarations because if references to them have changed, we need to update the declarations, too + if (is_reference(node, true)) { const name = node.getText(module.ast); const declaration = trace(module.file, name); diff --git a/src/types.d.ts b/src/types.d.ts index ab4a6c2..8a8e9c1 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -15,7 +15,8 @@ interface Declaration { external: boolean; included: boolean; dependencies: Reference[]; - /** The name we'd like to use to refer to this binding. + /** + * The name we'd like to use to refer to this binding. * Only applies to default imports from external modules */ preferred_alias: string; diff --git a/src/utils.js b/src/utils.js index c8aa435..55fd943 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,4 @@ -/** @import { Declaration, Module, Namespace } from './types' */ +/** @import { Binding, Declaration, Module, Namespace } from './types' */ import fs from 'node:fs'; import path from 'node:path'; import glob from 'tiny-glob/sync.js'; @@ -388,7 +388,11 @@ export function get_dts(file, created, resolve, options) { if (tsu.isNamespaceDeclaration(node)) { const previous = current; - current = { declarations: new Map(), references: new Set(), exports: new Map() }; + current = { + declarations: new Map(), + references: new Set(), + exports: new Map() + }; node.body.forEachChild(scan); @@ -398,6 +402,18 @@ export function get_dts(file, created, resolve, options) { } } + for (const inner of current.declarations.values()) { + for (const inner_dep of inner.dependencies) { + if ( + !declaration.dependencies.some( + (dep) => dep.name === inner_dep.name && dep.module === inner_dep.module + ) + ) { + declaration.dependencies.push(inner_dep); + } + } + } + current = previous; } else { walk(node, (node) => { @@ -417,9 +433,15 @@ export function get_dts(file, created, resolve, options) { module.dependencies.push(resolved); if (node.qualifier) { + // In the case of `import('./foo').Foo.Bar`, this contains `Foo.Bar`, + // but we only want `Foo` (because we don't traverse into namespaces) + let id = node.qualifier; + while (ts.isQualifiedName(id)) { + id = id.left; + } declaration.dependencies.push({ module: resolved ?? node.argument.literal.text, - name: node.qualifier.getText(module.ast) + name: id.getText(module.ast) }); } } @@ -432,10 +454,18 @@ export function get_dts(file, created, resolve, options) { current.references.add(name); if (name !== declaration.name) { - declaration.dependencies.push({ - module: file, - name - }); + // If this references an import * as X statement, we add a dependency to Y of the X.Y access + if (module.import_all.has(name) && ts.isQualifiedName(node.parent)) { + declaration.dependencies.push({ + module: /** @type {Binding} */ (module.import_all.get(name)).id, + name: node.parent.right.getText(module.ast) + }); + } else { + declaration.dependencies.push({ + module: file, + name + }); + } } } }); @@ -523,23 +553,25 @@ export function is_declaration(node) { /** * @param {ts.Node} node + * @param {boolean} [include_declarations] * @returns {node is ts.Identifier} */ -export function is_reference(node) { +export function is_reference(node, include_declarations = false) { if (!ts.isIdentifier(node)) return false; if (node.parent) { if (is_declaration(node.parent)) { if (ts.isVariableStatement(node.parent)) { - return node === node.parent.declarationList.declarations[0].name; + return false; } - return node === node.parent.name; + return include_declarations && node.parent.name === node; } if (ts.isPropertyAccessExpression(node.parent)) return node === node.parent.expression; if (ts.isPropertyDeclaration(node.parent)) return node === node.parent.initializer; if (ts.isPropertyAssignment(node.parent)) return node === node.parent.initializer; + if (ts.isMethodSignature(node.parent)) return node !== node.parent.name; if (ts.isImportTypeNode(node.parent)) return false; if (ts.isPropertySignature(node.parent)) return false; @@ -548,6 +580,12 @@ export function is_reference(node) { if (ts.isLabeledStatement(node.parent)) return false; if (ts.isBreakOrContinueStatement(node.parent)) return false; if (ts.isEnumMember(node.parent)) return false; + if (ts.isModuleDeclaration(node.parent)) return false; + + // Only X in X.Y.Z is a reference we care about + if (ts.isQualifiedName(node.parent)) { + return node.parent.left === node && ts.isIdentifier(node.parent.right); + } // `const = { x: 1 }` inexplicably becomes `namespace a { let x: number; }` if (ts.isVariableDeclaration(node.parent)) { diff --git a/test/samples/import-all/input/index.ts b/test/samples/import-all/input/index.ts new file mode 100644 index 0000000..e4e8ad3 --- /dev/null +++ b/test/samples/import-all/input/index.ts @@ -0,0 +1,5 @@ +import * as X from './namespace'; +import * as Types from './type'; + +export type Y = X.Namespace.X; +export type Z = Types.Z; diff --git a/test/samples/import-all/input/namespace.ts b/test/samples/import-all/input/namespace.ts new file mode 100644 index 0000000..5279988 --- /dev/null +++ b/test/samples/import-all/input/namespace.ts @@ -0,0 +1,5 @@ +export namespace Namespace { + export interface X { + error(): string; + } +} diff --git a/test/samples/import-all/input/type.ts b/test/samples/import-all/input/type.ts new file mode 100644 index 0000000..199165f --- /dev/null +++ b/test/samples/import-all/input/type.ts @@ -0,0 +1 @@ +export type Z = true; diff --git a/test/samples/import-all/output/index.d.ts b/test/samples/import-all/output/index.d.ts new file mode 100644 index 0000000..61235d9 --- /dev/null +++ b/test/samples/import-all/output/index.d.ts @@ -0,0 +1,14 @@ +declare module 'import-all' { + export type Y =Namespace.X; + export type Z =Z_1; + namespace Namespace { + interface X { + error(): string; + } + } + type Z_1 = true; + + export {}; +} + +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/test/samples/import-all/output/index.d.ts.map b/test/samples/import-all/output/index.d.ts.map new file mode 100644 index 0000000..5059447 --- /dev/null +++ b/test/samples/import-all/output/index.d.ts.map @@ -0,0 +1,20 @@ +{ + "version": 3, + "file": "index.d.ts", + "names": [ + "Y", + "Z", + "Namespace" + ], + "sources": [ + "../input/index.ts", + "../input/type.ts", + "../input/namespace.ts" + ], + "sourcesContent": [ + null, + null, + null + ], + "mappings": ";aAGYA,CAACA;aCHDC,CAACA;WCAIC,SAASA" +} \ No newline at end of file diff --git a/test/samples/namespace-exports/input/dependency.ts b/test/samples/namespace-exports/input/dependency.ts new file mode 100644 index 0000000..42b5133 --- /dev/null +++ b/test/samples/namespace-exports/input/dependency.ts @@ -0,0 +1,3 @@ +export interface Dependency { + name: string; +} diff --git a/test/samples/namespace-exports/input/index.ts b/test/samples/namespace-exports/input/index.ts index 611d817..03aec31 100644 --- a/test/samples/namespace-exports/input/index.ts +++ b/test/samples/namespace-exports/input/index.ts @@ -1,3 +1,4 @@ -import { type Namespace } from './namespace'; +import { type Namespace, type NamespaceWithDeps } from './namespace'; export type X = Namespace.X; +export { NamespaceWithDeps }; diff --git a/test/samples/namespace-exports/input/namespace.ts b/test/samples/namespace-exports/input/namespace.ts index 1094481..988a3d0 100644 --- a/test/samples/namespace-exports/input/namespace.ts +++ b/test/samples/namespace-exports/input/namespace.ts @@ -1,3 +1,5 @@ +import { Dependency } from './dependency'; + export namespace Namespace { export interface X { x: string; @@ -7,3 +9,9 @@ export namespace Namespace { y: string; } } + +export namespace NamespaceWithDeps { + export interface Z { + z: Dependency; + } +} diff --git a/test/samples/namespace-exports/output/index.d.ts b/test/samples/namespace-exports/output/index.d.ts index f37960c..c66e5d9 100644 --- a/test/samples/namespace-exports/output/index.d.ts +++ b/test/samples/namespace-exports/output/index.d.ts @@ -8,6 +8,14 @@ declare module 'namespace-exports' { y: string; } } + export namespace NamespaceWithDeps { + interface Z { + z: Dependency; + } + } + interface Dependency { + name: string; + } export {}; } diff --git a/test/samples/namespace-exports/output/index.d.ts.map b/test/samples/namespace-exports/output/index.d.ts.map index d534ceb..f76f11c 100644 --- a/test/samples/namespace-exports/output/index.d.ts.map +++ b/test/samples/namespace-exports/output/index.d.ts.map @@ -3,15 +3,19 @@ "file": "index.d.ts", "names": [ "X", - "Namespace" + "Namespace", + "NamespaceWithDeps", + "Dependency" ], "sources": [ "../input/index.ts", - "../input/namespace.ts" + "../input/namespace.ts", + "../input/dependency.ts" ], "sourcesContent": [ + null, null, null ], - "mappings": ";aAEYA,CAACA;WCFIC,SAASA" -} + "mappings": ";aAEYA,CAACA;WCAIC,SAASA;;;;;;;;kBAUTC,iBAAiBA;;;;;WCZjBC,UAAUA" +} \ No newline at end of file diff --git a/test/samples/no-renames/input/index.ts b/test/samples/no-renames/input/index.ts new file mode 100644 index 0000000..3a5d7b1 --- /dev/null +++ b/test/samples/no-renames/input/index.ts @@ -0,0 +1,4 @@ +export { Y } from './type'; +export { Namespace } from './namespace'; +export type X = true; +export function error(): void {} diff --git a/test/samples/no-renames/input/namespace.ts b/test/samples/no-renames/input/namespace.ts new file mode 100644 index 0000000..5279988 --- /dev/null +++ b/test/samples/no-renames/input/namespace.ts @@ -0,0 +1,5 @@ +export namespace Namespace { + export interface X { + error(): string; + } +} diff --git a/test/samples/no-renames/input/type.ts b/test/samples/no-renames/input/type.ts new file mode 100644 index 0000000..14d3faf --- /dev/null +++ b/test/samples/no-renames/input/type.ts @@ -0,0 +1,3 @@ +import { Namespace } from './namespace'; + +export type Y = Namespace.X; diff --git a/test/samples/no-renames/output/index.d.ts b/test/samples/no-renames/output/index.d.ts new file mode 100644 index 0000000..cba2d3c --- /dev/null +++ b/test/samples/no-renames/output/index.d.ts @@ -0,0 +1,14 @@ +declare module 'no-renames' { + export type X = true; + export function error(): void; + export type Y = Namespace.X; + export namespace Namespace { + interface X { + error(): string; + } + } + + export {}; +} + +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/test/samples/no-renames/output/index.d.ts.map b/test/samples/no-renames/output/index.d.ts.map new file mode 100644 index 0000000..a4d12a8 --- /dev/null +++ b/test/samples/no-renames/output/index.d.ts.map @@ -0,0 +1,21 @@ +{ + "version": 3, + "file": "index.d.ts", + "names": [ + "X", + "error", + "Y", + "Namespace" + ], + "sources": [ + "../input/index.ts", + "../input/type.ts", + "../input/namespace.ts" + ], + "sourcesContent": [ + null, + null, + null + ], + "mappings": ";aAEYA,CAACA;iBACGC,KAAKA;aCDTC,CAACA;kBCFIC,SAASA" +} \ No newline at end of file diff --git a/test/tsconfig.json b/test/tsconfig.json index ec204c2..b0d796d 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -12,5 +12,6 @@ "paths": { "#lib": ["./samples/path-config/input/lib.d.ts"] } - } + }, + "exclude": ["**/actual/"] }