From 8486126dc1473b864a2c7a969595bf9614c96016 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 22 Aug 2024 00:44:11 +0200 Subject: [PATCH] feat: support stripping internal types ... using `@internal` JSDoc tags, if the `stripInternal` tsconfig compiler option is enabled. This means types annotated with `@internal` will be stripped both from .js/ts files going through the TS program aswell as d.ts files going through our logic part of https://github.com/sveltejs/svelte/issues/12292 --- CHANGELOG.md | 4 +++ package.json | 2 +- src/create-module-declaration.js | 24 ++++++++++++-- src/index.js | 3 +- src/utils.js | 32 ++++++++++++++++--- test/samples/strip-internal/input/index.ts | 6 ++++ .../strip-internal/input/internal.d.ts | 1 + test/samples/strip-internal/input/others.d.ts | 7 ++++ test/samples/strip-internal/options.json | 3 ++ test/samples/strip-internal/output/index.d.ts | 9 ++++++ .../strip-internal/output/index.d.ts.map | 14 ++++++++ test/test.js | 5 ++- 12 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 test/samples/strip-internal/input/index.ts create mode 100644 test/samples/strip-internal/input/internal.d.ts create mode 100644 test/samples/strip-internal/input/others.d.ts create mode 100644 test/samples/strip-internal/options.json create mode 100644 test/samples/strip-internal/output/index.d.ts create mode 100644 test/samples/strip-internal/output/index.d.ts.map diff --git a/CHANGELOG.md b/CHANGELOG.md index 098a34a..37ec3ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # dts-buddy changelog +## 0.5.2 + +- Support stripping internal types using `@internal` JSDoc tags ([#87](https://github.com/Rich-Harris/dts-buddy/pull/87)) + ## 0.5.1 - Use more TypeScript-friendly way of preventing unintended exports ([#85](https://github.com/Rich-Harris/dts-buddy/pull/85)) diff --git a/package.json b/package.json index 4fa6712..d578e86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dts-buddy", - "version": "0.5.1", + "version": "0.5.2", "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 c0e8144..3af3939 100644 --- a/src/create-module-declaration.js +++ b/src/create-module-declaration.js @@ -3,20 +3,29 @@ import path from 'node:path'; import ts from 'typescript'; import * as tsu from 'ts-api-utils'; import MagicString from 'magic-string'; -import { clean_jsdoc, get_dts, is_declaration, is_reference, resolve_dts, walk } from './utils.js'; +import { + clean_jsdoc, + get_dts, + is_declaration, + is_internal, + is_reference, + resolve_dts, + walk +} from './utils.js'; /** * @param {string} id * @param {string} entry * @param {Record} created * @param {(file: string, specifier: string) => string | null} resolve + * @param {{ stripInternal?: boolean }} options * @returns {{ * content: string; * mappings: Map; * ambient: ModuleReference[]; * }} */ -export function create_module_declaration(id, entry, created, resolve) { +export function create_module_declaration(id, entry, created, resolve, options) { let content = ''; /** @type {Map} */ @@ -57,7 +66,7 @@ export function create_module_declaration(id, entry, created, resolve) { const included = new Set([entry]); for (const file of included) { - const module = get_dts(file, created, resolve); + const module = get_dts(file, created, resolve, options); for (const name of module.globals) { globals.add(name); @@ -247,6 +256,10 @@ export function create_module_declaration(id, entry, created, resolve) { } if (is_declaration(node)) { + if (is_internal(node) && options.stripInternal) { + result.remove(node.pos, node.end); + } + const identifier = ts.isVariableStatement(node) ? ts.getNameOfDeclaration(node.declarationList.declarations[0]) : ts.getNameOfDeclaration(node); @@ -348,6 +361,11 @@ export function create_module_declaration(id, entry, created, resolve) { } walk(node, (node) => { + if (ts.isPropertySignature(node) && is_internal(node) && options.stripInternal) { + result.remove(node.pos, node.end); + return false; + } + if (is_reference(node)) { const name = node.getText(module.ast); diff --git a/src/index.js b/src/index.js index 7a66858..3697adc 100644 --- a/src/index.js +++ b/src/index.js @@ -229,7 +229,8 @@ export async function createBundle(options) { id, modules[id], created, - resolve + resolve, + compilerOptions ); types += content; diff --git a/src/utils.js b/src/utils.js index 4c257fa..c8aa435 100644 --- a/src/utils.js +++ b/src/utils.js @@ -16,6 +16,21 @@ export function get_jsdoc(node) { return jsDoc; } +/** @param {ts.Node} node */ +export function is_internal(node) { + const jsdoc = get_jsdoc(node); + + if (jsdoc) { + for (const jsDoc of jsdoc) { + if (jsDoc.tags?.some((tag) => tag.tagName.escapedText === 'internal')) { + return true; + } + } + } + + return false; +} + /** @param {ts.Node} node */ export function get_jsdoc_imports(node) { /** @type {import('typescript').TypeNode[]} */ @@ -180,8 +195,9 @@ export function write(file, contents) { * @param {string} file * @param {Record} created * @param {(file: string, specifier: string) => string | null} resolve + * @param {{ stripInternal?: boolean }} options */ -export function get_dts(file, created, resolve) { +export function get_dts(file, created, resolve, options) { const dts = created[file] ?? fs.readFileSync(file, 'utf8'); const ast = ts.createSourceFile(file, dts, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); const locator = getLocator(dts, { offsetLine: 1 }); @@ -325,6 +341,8 @@ export function get_dts(file, created, resolve) { } if (is_declaration(node)) { + if (is_internal(node) && options.stripInternal) return; + const identifier = ts.isVariableStatement(node) ? ts.getNameOfDeclaration(node.declarationList.declarations[0]) : ts.getNameOfDeclaration(node); @@ -383,6 +401,10 @@ export function get_dts(file, created, resolve) { current = previous; } else { walk(node, (node) => { + if (ts.isPropertySignature(node) && is_internal(node) && options.stripInternal) { + return false; + } + // `import('./foo').Foo` -> `Foo` if ( ts.isImportTypeNode(node) && @@ -466,11 +488,13 @@ export function resolve_dts(from, to) { /** * @param {ts.Node} node - * @param {(node: ts.Node) => void} callback + * @param {(node: ts.Node) => void | false} callback */ export function walk(node, callback) { - callback(node); - ts.forEachChild(node, (child) => walk(child, callback)); + const go_on = callback(node); + if (go_on !== false) { + ts.forEachChild(node, (child) => walk(child, callback)); + } } /** diff --git a/test/samples/strip-internal/input/index.ts b/test/samples/strip-internal/input/index.ts new file mode 100644 index 0000000..f5c88f4 --- /dev/null +++ b/test/samples/strip-internal/input/index.ts @@ -0,0 +1,6 @@ +export { Foo } from './others'; + +/** @internal TS itself will take care of stripping this */ +export interface TSdddd { + foo: boolean; +} diff --git a/test/samples/strip-internal/input/internal.d.ts b/test/samples/strip-internal/input/internal.d.ts new file mode 100644 index 0000000..04813ba --- /dev/null +++ b/test/samples/strip-internal/input/internal.d.ts @@ -0,0 +1 @@ +export type Internal = { foo: boolean }; diff --git a/test/samples/strip-internal/input/others.d.ts b/test/samples/strip-internal/input/others.d.ts new file mode 100644 index 0000000..387bea2 --- /dev/null +++ b/test/samples/strip-internal/input/others.d.ts @@ -0,0 +1,7 @@ +import { Internal } from './internal'; + +export interface Foo { + bar: string; + /** @internal */ + baz: Internal; +} diff --git a/test/samples/strip-internal/options.json b/test/samples/strip-internal/options.json new file mode 100644 index 0000000..d5bc0e4 --- /dev/null +++ b/test/samples/strip-internal/options.json @@ -0,0 +1,3 @@ +{ + "stripInternal": true +} diff --git a/test/samples/strip-internal/output/index.d.ts b/test/samples/strip-internal/output/index.d.ts new file mode 100644 index 0000000..3e5b026 --- /dev/null +++ b/test/samples/strip-internal/output/index.d.ts @@ -0,0 +1,9 @@ +declare module 'strip-internal' { + export interface Foo { + bar: string; + } + + export {}; +} + +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/test/samples/strip-internal/output/index.d.ts.map b/test/samples/strip-internal/output/index.d.ts.map new file mode 100644 index 0000000..41d1c2e --- /dev/null +++ b/test/samples/strip-internal/output/index.d.ts.map @@ -0,0 +1,14 @@ +{ + "version": 3, + "file": "index.d.ts", + "names": [ + "Foo" + ], + "sources": [ + "../input/others.d.ts" + ], + "sourcesContent": [ + null + ], + "mappings": ";kBAEiBA,GAAGA" +} \ No newline at end of file diff --git a/test/test.js b/test/test.js index aa786d2..6fbc139 100644 --- a/test/test.js +++ b/test/test.js @@ -20,7 +20,10 @@ for (const sample of fs.readdirSync('test/samples')) { const compilerOptions = { /** @type {Record} */ - paths: {} + paths: {}, + ...(fs.existsSync(`${dir}/options.json`) + ? JSON.parse(fs.readFileSync(`${dir}/options.json`, 'utf-8')) + : {}) }; for (const file of glob('**', { cwd: `${dir}/input`, filesOnly: true })) {