From b36710eb278602fac01d55b30b391aba9b15c2f8 Mon Sep 17 00:00:00 2001 From: osamajvd <54687462+osamajvd@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:48:51 -0400 Subject: [PATCH 1/4] fix: preserve type arguments on tagged template expressions --- src/languages/ts/index.js | 1 + test/samples/ts-tagged-template/expected.ts | 3 +++ test/samples/ts-tagged-template/expected.ts.map | 11 +++++++++++ test/samples/ts-tagged-template/input.ts | 2 ++ 4 files changed, 17 insertions(+) create mode 100644 test/samples/ts-tagged-template/expected.ts create mode 100644 test/samples/ts-tagged-template/expected.ts.map create mode 100644 test/samples/ts-tagged-template/input.ts diff --git a/src/languages/ts/index.js b/src/languages/ts/index.js index 46f993b..fa1ded0 100644 --- a/src/languages/ts/index.js +++ b/src/languages/ts/index.js @@ -1779,6 +1779,7 @@ export default (options = {}) => { /** @type {string} */ (node.tag.type) === 'ChainExpression' || EXPRESSIONS_PRECEDENCE[node.tag.type] < EXPRESSIONS_PRECEDENCE.CallExpression; maybe_wrap(context, node.tag, wrap); + if (node.typeArguments) context.visit(node.typeArguments); context.visit(node.quasi); }, diff --git a/test/samples/ts-tagged-template/expected.ts b/test/samples/ts-tagged-template/expected.ts new file mode 100644 index 0000000..3e47d8e --- /dev/null +++ b/test/samples/ts-tagged-template/expected.ts @@ -0,0 +1,3 @@ +sql`SELECT * FROM table`; + +const res = tag`hello ${world}`; \ No newline at end of file diff --git a/test/samples/ts-tagged-template/expected.ts.map b/test/samples/ts-tagged-template/expected.ts.map new file mode 100644 index 0000000..b7687e3 --- /dev/null +++ b/test/samples/ts-tagged-template/expected.ts.map @@ -0,0 +1,11 @@ +{ + "version": 3, + "names": [], + "sources": [ + "input.js" + ], + "sourcesContent": [ + "sql`SELECT * FROM table`;\nconst res = tag`hello ${world}`;\n" + ], + "mappings": "AAAA,GAAG,CAAC,MAAM;;AACV,MAAM,AAAA,GAAG,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,UAAU,KAAK" +} \ No newline at end of file diff --git a/test/samples/ts-tagged-template/input.ts b/test/samples/ts-tagged-template/input.ts new file mode 100644 index 0000000..e6cd21e --- /dev/null +++ b/test/samples/ts-tagged-template/input.ts @@ -0,0 +1,2 @@ +sql`SELECT * FROM table`; +const res = tag`hello ${world}`; From a7bd5589c00072ec9ab1c742fb9acdf033de832a Mon Sep 17 00:00:00 2001 From: osamajvd <54687462+osamajvd@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:57:45 -0400 Subject: [PATCH 2/4] fix: resolve remaining TS printing gaps for array patterns, template literals, and JSDoc nullable types --- src/languages/ts/index.js | 12 ++++++- test/compat.test.js | 31 +++++++++++++++++++ test/samples/ts-array-patterns/expected.ts | 5 +++ .../samples/ts-array-patterns/expected.ts.map | 11 +++++++ test/samples/ts-array-patterns/input.ts | 5 +++ .../ts-template-literal-types/expected.ts | 4 +++ .../ts-template-literal-types/expected.ts.map | 11 +++++++ .../ts-template-literal-types/input.ts | 4 +++ 8 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 test/samples/ts-array-patterns/expected.ts create mode 100644 test/samples/ts-array-patterns/expected.ts.map create mode 100644 test/samples/ts-array-patterns/input.ts create mode 100644 test/samples/ts-template-literal-types/expected.ts create mode 100644 test/samples/ts-template-literal-types/expected.ts.map create mode 100644 test/samples/ts-template-literal-types/input.ts diff --git a/src/languages/ts/index.js b/src/languages/ts/index.js index fa1ded0..42aa747 100644 --- a/src/languages/ts/index.js +++ b/src/languages/ts/index.js @@ -594,6 +594,7 @@ export default (options = {}) => { false ); context.write(']', token_before(node.loc?.end)); + if ('typeAnnotation' in node && node.typeAnnotation) context.visit(node.typeAnnotation); }, /** @@ -2066,7 +2067,16 @@ export default (options = {}) => { if (/\n/.test(raw)) context.multiline = true; } - context.write('`'); + const raw = quasis[quasis.length - 1].value.raw; + context.write(raw + '`'); + if (/\n/.test(raw)) context.multiline = true; + }, + + // @ts-expect-error not in TSESTree types + TSJSDocNullableType(node, context) { + if (!node.postfix) context.write('?'); + context.visit(node.typeAnnotation); + if (node.postfix) context.write('?'); }, TSParameterProperty(node, context) { diff --git a/test/compat.test.js b/test/compat.test.js index d04289e..edcaef7 100644 --- a/test/compat.test.js +++ b/test/compat.test.js @@ -36,3 +36,34 @@ test('acorn TS printer preserves module and mapped-type keywords', () => { expect(code).toBe('declare module "svelte" {\n}\n\ntype M = {[K in keyof JSON]: K};'); }); + +test('TSJSDocNullableType prefix and postfix', () => { + /** @type {any} */ + const ast = { + type: 'Program', + body: [ + { + type: 'TSTypeAliasDeclaration', + id: { type: 'Identifier', name: 'A' }, + typeAnnotation: { + type: 'TSJSDocNullableType', + typeAnnotation: { type: 'TSNumberKeyword' }, + postfix: false + } + }, + { + type: 'TSTypeAliasDeclaration', + id: { type: 'Identifier', name: 'B' }, + typeAnnotation: { + type: 'TSJSDocNullableType', + typeAnnotation: { type: 'TSNumberKeyword' }, + postfix: true + } + } + ], + sourceType: 'module' + }; + + const { code } = print(ast, ts()); + expect(code).toBe('type A = ?number;\ntype B = number?;'); +}); diff --git a/test/samples/ts-array-patterns/expected.ts b/test/samples/ts-array-patterns/expected.ts new file mode 100644 index 0000000..cc931b0 --- /dev/null +++ b/test/samples/ts-array-patterns/expected.ts @@ -0,0 +1,5 @@ +const [a, b]: string[] = ['foo', 'bar']; + +function fn([first, second]: number[]) { + return first + second; +} \ No newline at end of file diff --git a/test/samples/ts-array-patterns/expected.ts.map b/test/samples/ts-array-patterns/expected.ts.map new file mode 100644 index 0000000..e36593b --- /dev/null +++ b/test/samples/ts-array-patterns/expected.ts.map @@ -0,0 +1,11 @@ +{ + "version": 3, + "names": [], + "sources": [ + "input.js" + ], + "sourcesContent": [ + "const [a, b]: string[] = ['foo', 'bar'];\n\nfunction fn([first, second]: number[]) {\n\treturn first + second;\n}\n" + ], + "mappings": "AAAA,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,MAAM,KAAK,EAAE,KAAK;;AAEtC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC;CACvC,MAAM,CAAC,KAAK,GAAG,MAAM;AACtB,CAAC" +} \ No newline at end of file diff --git a/test/samples/ts-array-patterns/input.ts b/test/samples/ts-array-patterns/input.ts new file mode 100644 index 0000000..3ebb105 --- /dev/null +++ b/test/samples/ts-array-patterns/input.ts @@ -0,0 +1,5 @@ +const [a, b]: string[] = ['foo', 'bar']; + +function fn([first, second]: number[]) { + return first + second; +} diff --git a/test/samples/ts-template-literal-types/expected.ts b/test/samples/ts-template-literal-types/expected.ts new file mode 100644 index 0000000..8cd48b0 --- /dev/null +++ b/test/samples/ts-template-literal-types/expected.ts @@ -0,0 +1,4 @@ +type Event = `on${string}`; +type Path = `/${T}/index.html`; +type Plain = `static_string`; +type Multi = `prefix_${A}_middle_${B}_suffix`; \ No newline at end of file diff --git a/test/samples/ts-template-literal-types/expected.ts.map b/test/samples/ts-template-literal-types/expected.ts.map new file mode 100644 index 0000000..9773782 --- /dev/null +++ b/test/samples/ts-template-literal-types/expected.ts.map @@ -0,0 +1,11 @@ +{ + "version": 3, + "names": [], + "sources": [ + "input.js" + ], + "sourcesContent": [ + "type Event = `on${string}`;\ntype Path = `/${T}/index.html`;\ntype Plain = `static_string`;\ntype Multi = `prefix_${A}_middle_${B}_suffix`;\n" + ], + "mappings": "KAAK,KAAK,QAAQ,MAAM;KACnB,IAAI,CAAC,CAAgB,SAAN,MAAM,QAAQ,CAAC;KAC9B,KAAK;KACL,KAAK,CAAC,CAAgB,SAAN,MAAM,EAAE,CAAgB,SAAN,MAAM,cAAc,CAAC,WAAW,CAAC" +} \ No newline at end of file diff --git a/test/samples/ts-template-literal-types/input.ts b/test/samples/ts-template-literal-types/input.ts new file mode 100644 index 0000000..bcbdc33 --- /dev/null +++ b/test/samples/ts-template-literal-types/input.ts @@ -0,0 +1,4 @@ +type Event = `on${string}`; +type Path = `/${T}/index.html`; +type Plain = `static_string`; +type Multi = `prefix_${A}_middle_${B}_suffix`; From 1be1ef58718f6f8ddf977e00bcfa9c12589edab7 Mon Sep 17 00:00:00 2001 From: osamajvd <54687462+osamajvd@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:07:26 -0400 Subject: [PATCH 3/4] chore: add changeset --- .changeset/six-pianos-know.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/six-pianos-know.md diff --git a/.changeset/six-pianos-know.md b/.changeset/six-pianos-know.md new file mode 100644 index 0000000..45bd061 --- /dev/null +++ b/.changeset/six-pianos-know.md @@ -0,0 +1,5 @@ +--- +'esrap': patch +--- + +fix: preserve type arguments on tagged templates, array pattern annotations, and template literal type quasis (fixes #174) From 0c097db2efb4bee32a21cdeaf409d234bc4ac928 Mon Sep 17 00:00:00 2001 From: Manuel <30698007+manuel3108@users.noreply.github.com> Date: Fri, 21 Aug 2026 17:57:54 +0200 Subject: [PATCH 4/4] Update .changeset/six-pianos-know.md --- .changeset/six-pianos-know.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/six-pianos-know.md b/.changeset/six-pianos-know.md index 45bd061..b096e9f 100644 --- a/.changeset/six-pianos-know.md +++ b/.changeset/six-pianos-know.md @@ -2,4 +2,4 @@ 'esrap': patch --- -fix: preserve type arguments on tagged templates, array pattern annotations, and template literal type quasis (fixes #174) +fix: preserve type arguments on tagged templates, array pattern annotations, and template literal type quasis