Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/ts-token-emission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'esrap': patch
---

fix: print valid TypeScript for `asserts` predicates, qualified namespaces, and computed signature keys

These were previously emitted as invalid output:

- `asserts` type predicates were printed in the wrong order (`asserts x is T` became `x asserts T`)
- qualified namespace/module names were mangled (`namespace A.B.C` became `namespace Anamespace Bnamespace C`)
- computed keys in method/property signatures lost their brackets (`interface I { [Symbol.iterator](): T }` became `Symbol.iterator(): T`)
32 changes: 20 additions & 12 deletions src/languages/ts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1872,7 +1872,9 @@ export default (options = {}) => {
},

TSPropertySignature(node, context) {
if (node.computed) context.write('[');
context.visit(node.key);
if (node.computed) context.write(']');
if (node.optional) context.write('?');
if (node.typeAnnotation) context.visit(node.typeAnnotation);
},
Expand Down Expand Up @@ -1985,19 +1987,14 @@ export default (options = {}) => {
},

TSTypePredicate(node, context) {
if (node.parameterName) {
context.visit(node.parameterName);
} else if (node.typeAnnotation) {
context.visit(node.typeAnnotation);
}
// `asserts` precedes the parameter name; `is <type>` follows it. Forms:
// `x is T`, `asserts x is T`, `asserts x`
if (node.asserts) context.write('asserts ');

if (node.asserts) {
context.write(' asserts ');
} else {
context.write(' is ');
}
if (node.parameterName) context.visit(node.parameterName);

if (node.typeAnnotation) {
context.write(' is ');
context.visit(node.typeAnnotation.typeAnnotation);
}
},
Expand Down Expand Up @@ -2083,7 +2080,9 @@ export default (options = {}) => {
},

TSMethodSignature(node, context) {
if (node.computed) context.write('[');
context.visit(node.key);
if (node.computed) context.write(']');

context.write('(');
sequence(
Expand Down Expand Up @@ -2241,8 +2240,17 @@ export default (options = {}) => {
context.visit(node.id);
}

if (!node.body) return;
context.visit(node.body);
// a qualified name (`namespace A.B.C`) is represented as nested
// `TSModuleDeclaration`s whose body is the next name part, not a block
let body = /** @type {any} */ (node.body);
while (body && body.type === 'TSModuleDeclaration') {
context.write('.');
context.visit(body.id);
body = body.body;
}

if (!body) return;
context.visit(body);
},

TSNonNullExpression(node, context) {
Expand Down
8 changes: 8 additions & 0 deletions test/samples/ts-module-declaration/expected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ declare global {
}
}

namespace A.B.C {
export const x = 1;
}

namespace D {
export const x = 1;
}

export {};
4 changes: 2 additions & 2 deletions test/samples/ts-module-declaration/expected.ts.map
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"input.js"
],
"sourcesContent": [
"declare global {\n\tnamespace App {\n\t\tinterface Error {\n\t\t\tfoo: string;\n\t\t}\n\t}\n}\n\nexport {};\n"
"declare global {\n\tnamespace App {\n\t\tinterface Error {\n\t\t\tfoo: string;\n\t\t}\n\t}\n}\n\nnamespace A.B.C {\n\texport const x = 1;\n}\n\nnamespace D {\n\texport const x = 1;\n}\n\nexport {};\n"
],
"mappings": "QAAQ,MAAM;WACH,GAAG;YACF,KAAK,GACd,GAAG,EAAE,MAAM;;;;AAKd,OAAO"
"mappings": "QAAQ,MAAM;WACH,GAAG;YACF,KAAK,GACd,GAAG,EAAE,MAAM;;;;UAKJ,CAAC,CAAC,CAAC,CAAC,CAAC;CACd,MAAM,CAAC,MAAM,AAAA,CAAC,GAAG,CAAC;;;UAGT,CAAC;CACV,MAAM,CAAC,MAAM,AAAA,CAAC,GAAG,CAAC;;;AAGnB,OAAO"
}
8 changes: 8 additions & 0 deletions test/samples/ts-module-declaration/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ declare global {
}
}

namespace A.B.C {
export const x = 1;
}

namespace D {
export const x = 1;
}

export {};
5 changes: 4 additions & 1 deletion test/samples/ts-signatures/expected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ interface Constructable {
(name: string): string
}

type Constructor = { new(): object; new(name: string, age: number): Person };
interface Signatures { [Symbol.iterator](): number; x(): void; [k: string]: number }

type Constructor = { new(): object; new(name: string, age: number): Person };
type Computed = { [Symbol.iterator](): number };
4 changes: 2 additions & 2 deletions test/samples/ts-signatures/expected.ts.map
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"input.js"
],
"sourcesContent": [
"interface Constructable {\n\tnew (): any;\n\tnew (value: string): MyClass;\n\tnew <T>(value: T): GenericClass<T>;\n\t(name: string): string;\n}\n\ntype Constructor = {\n\tnew (): object;\n\tnew (name: string, age: number): Person;\n};\n"
"interface Constructable {\n\tnew (): any;\n\tnew (value: string): MyClass;\n\tnew <T>(value: T): GenericClass<T>;\n\t(name: string): string;\n}\n\ninterface Signatures {\n\t[Symbol.iterator](): number;\n\tx(): void;\n\t[k: string]: number;\n}\n\ntype Constructor = {\n\tnew (): object;\n\tnew (name: string, age: number): Person;\n};\n\ntype Computed = {\n\t[Symbol.iterator](): number;\n};\n"
],
"mappings": "UAAU,aAAa;QACd,GAAG;KACN,KAAa,EAAN,MAAM,GAAG,OAAO;KACvB,CAAC,EAAE,KAAQ,EAAD,CAAC,GAAG,YAAY,CAAC,CAAC;EAChC,IAAY,EAAN,MAAM,GAAG,MAAM;;;KAGlB,WAAW,YACP,MAAM,MACT,IAAY,EAAN,MAAM,EAAE,GAAW,EAAN,MAAM,GAAG,MAAM"
"mappings": "UAAU,aAAa;QACd,GAAG;KACN,KAAa,EAAN,MAAM,GAAG,OAAO;KACvB,CAAC,EAAE,KAAQ,EAAD,CAAC,GAAG,YAAY,CAAC,CAAC;EAChC,IAAY,EAAN,MAAM,GAAG,MAAM;;;UAGb,UAAU,IAClB,MAAM,CAAC,QAAQ,KAAK,MAAM,EAC3B,CAAC,IAAI,IAAI,GACR,CAAS,EAAN,MAAM,GAAG,MAAM;;KAGf,WAAW,YACP,MAAM,MACT,IAAY,EAAN,MAAM,EAAE,GAAW,EAAN,MAAM,GAAG,MAAM;KAGnC,QAAQ,MACX,MAAM,CAAC,QAAQ,KAAK,MAAM"
}
10 changes: 10 additions & 0 deletions test/samples/ts-signatures/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ interface Constructable {
(name: string): string;
}

interface Signatures {
[Symbol.iterator](): number;
x(): void;
[k: string]: number;
}

type Constructor = {
new (): object;
new (name: string, age: number): Person;
};

type Computed = {
[Symbol.iterator](): number;
};
7 changes: 7 additions & 0 deletions test/samples/ts-types/expected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ type H = this;

type I = `Hello, ${keyof C}`;
type J = () => this is string;
type P = (x: unknown) => asserts x is string;

function assertsString(x): asserts x is string {}
function assertsValue(x): asserts x {}
function assertsThis(x): asserts this is Foo {}
function isString(x): x is string {}

type Bird = { legs: 2 };
type Dog = { legs: 4 };
type Wolf = { legs: 4 };
Expand Down
4 changes: 2 additions & 2 deletions test/samples/ts-types/expected.ts.map
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"input.js"
],
"sourcesContent": [
"type A = [x: number, y: string];\ntype B = { a: string; b: number };\n\ntype C = 'foo' | 'bar';\ntype D = C | A | B | 'foobar';\n\ntype E = A & B;\ntype F = C & 'foobar';\n\n// TODO: commented because acorn doesn't support intrinsic but oxc does\n// type G = { [a in C]: string };\n\ntype H = this;\ntype I = `Hello, ${keyof C}`;\ntype J = () => this is string;\n\ntype Bird = { legs: 2 };\ntype Dog = { legs: 4 };\ntype Wolf = { legs: 4 };\ntype Animals = Bird | Dog | Wolf;\ntype HasFourLegs<Animal> = Animal extends { legs: 4 } ? Animal : never;\ntype FourLegs = HasFourLegs<Animals>;\n\ntype T = [('a' | 'b')?];\n\ntype CT = new (tpl: TemplateStringsArray, ...args: Array<unknown>) => (replacements: B) => A;\n\ntype X = [...number[]];\ntype TupleWithRest = [number, ...(1 extends 2 ? string[] : number[])];\n\n// TODO: commented because acorn doesn't support intrinsic but oxc does\n// type Uppercase<S extends string> = intrinsic;\n"
"type A = [x: number, y: string];\ntype B = { a: string; b: number };\n\ntype C = 'foo' | 'bar';\ntype D = C | A | B | 'foobar';\n\ntype E = A & B;\ntype F = C & 'foobar';\n\n// TODO: commented because acorn doesn't support intrinsic but oxc does\n// type G = { [a in C]: string };\n\ntype H = this;\ntype I = `Hello, ${keyof C}`;\ntype J = () => this is string;\ntype P = (x: unknown) => asserts x is string;\n\nfunction assertsString(x): asserts x is string {}\nfunction assertsValue(x): asserts x {}\nfunction assertsThis(x): asserts this is Foo {}\nfunction isString(x): x is string {}\n\ntype Bird = { legs: 2 };\ntype Dog = { legs: 4 };\ntype Wolf = { legs: 4 };\ntype Animals = Bird | Dog | Wolf;\ntype HasFourLegs<Animal> = Animal extends { legs: 4 } ? Animal : never;\ntype FourLegs = HasFourLegs<Animals>;\n\ntype T = [('a' | 'b')?];\n\ntype CT = new (tpl: TemplateStringsArray, ...args: Array<unknown>) => (replacements: B) => A;\n\ntype X = [...number[]];\ntype TupleWithRest = [number, ...(1 extends 2 ? string[] : number[])];\n\n// TODO: commented because acorn doesn't support intrinsic but oxc does\n// type Uppercase<S extends string> = intrinsic;\n"
],
"mappings": "KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;KACzB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;KAE1B,CAAC,GAAG,KAAK,GAAG,KAAK;KACjB,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ;KAExB,CAAC,GAAG,CAAC,GAAG,CAAC;KACT,CAAC,GAAG,CAAC,GAAG,QAAQ;;;;KAKhB,CAAC,GAAG,IAAI;;KACR,CAAC,mBAAmB,CAAC;KACrB,CAAC,SAAS,IAAI,IAAI,MAAM;KAExB,IAAI,KAAK,IAAI,EAAE,CAAC;KAChB,GAAG,KAAK,IAAI,EAAE,CAAC;KACf,IAAI,KAAK,IAAI,EAAE,CAAC;KAChB,OAAO,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI;KAC3B,WAAW,CAAC,MAAM,IAAI,MAAM,WAAW,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,KAAK;KACjE,QAAQ,GAAG,WAAW,CAAC,OAAO;KAE9B,CAAC,KAAK,GAAG,GAAG,GAAG;KAEf,EAAE,QAAQ,GAAyB,EAApB,oBAAoB,KAAK,IAAI,EAAE,KAAK,CAAC,OAAO,OAAO,YAAe,EAAD,CAAC,KAAK,CAAC;KAEvF,CAAC,OAAO,MAAM;KACd,aAAa,IAAI,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,KAAK,MAAM;;;"
"mappings": "KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;KACzB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;KAE1B,CAAC,GAAG,KAAK,GAAG,KAAK;KACjB,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ;KAExB,CAAC,GAAG,CAAC,GAAG,CAAC;KACT,CAAC,GAAG,CAAC,GAAG,QAAQ;;;;KAKhB,CAAC,GAAG,IAAI;;KACR,CAAC,mBAAmB,CAAC;KACrB,CAAC,SAAS,IAAI,IAAI,MAAM;KACxB,CAAC,IAAI,CAAU,EAAP,OAAO,aAAa,CAAC,IAAI,MAAM;;AAE5C,QAAQ,CAAC,aAAa,CAAC,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,CAAC,AAAA,CAAC;AACjD,QAAQ,CAAC,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,AAAA,CAAC;AACtC,QAAQ,CAAC,WAAW,CAAC,CAAC,WAAW,IAAI,IAAI,GAAG,CAAC,CAAC,AAAA,CAAC;AAC/C,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,AAAA,CAAC;;KAE/B,IAAI,KAAK,IAAI,EAAE,CAAC;KAChB,GAAG,KAAK,IAAI,EAAE,CAAC;KACf,IAAI,KAAK,IAAI,EAAE,CAAC;KAChB,OAAO,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI;KAC3B,WAAW,CAAC,MAAM,IAAI,MAAM,WAAW,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,KAAK;KACjE,QAAQ,GAAG,WAAW,CAAC,OAAO;KAE9B,CAAC,KAAK,GAAG,GAAG,GAAG;KAEf,EAAE,QAAQ,GAAyB,EAApB,oBAAoB,KAAK,IAAI,EAAE,KAAK,CAAC,OAAO,OAAO,YAAe,EAAD,CAAC,KAAK,CAAC;KAEvF,CAAC,OAAO,MAAM;KACd,aAAa,IAAI,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,KAAK,MAAM;;;"
}
6 changes: 6 additions & 0 deletions test/samples/ts-types/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ type F = C & 'foobar';
type H = this;
type I = `Hello, ${keyof C}`;
type J = () => this is string;
type P = (x: unknown) => asserts x is string;

function assertsString(x): asserts x is string {}
function assertsValue(x): asserts x {}
function assertsThis(x): asserts this is Foo {}
function isString(x): x is string {}

type Bird = { legs: 2 };
type Dog = { legs: 4 };
Expand Down
Loading