diff --git a/internal/transformers/tstransforms/runtimesyntax.go b/internal/transformers/tstransforms/runtimesyntax.go index a7ed95f520..3745c98588 100644 --- a/internal/transformers/tstransforms/runtimesyntax.go +++ b/internal/transformers/tstransforms/runtimesyntax.go @@ -1,7 +1,5 @@ package tstransforms -// !!! Unqualified enum member references across merged enum declarations are not currently supported (e.g `enum E {A}; enum E {B=A}`) -// !!! Unqualified namespace member references across merged namespace declarations are not currently supported (e.g `namespace N { export var x = 1; }; namespace N { x; }`). // !!! SourceMaps and Comments need to be validated import ( @@ -1048,7 +1046,18 @@ func (tx *RuntimeSyntaxTransformer) visitExpressionIdentifier(node *ast.Identifi tx.resolver = binder.NewReferenceResolver(tx.compilerOptions, binder.ReferenceResolverHooks{}) } container := tx.resolver.GetReferencedExportContainer(location, false /*prefixLocals*/) - if container != nil && (ast.IsEnumDeclaration(container) || ast.IsModuleDeclaration(container)) && container.Contains(location) { + // Get symbols from the original nodes (before transformation) since transformed nodes may not have symbols + var currentNamespaceSymbol *ast.Symbol + var currentEnumSymbol *ast.Symbol + if tx.currentNamespace != nil { + currentNamespaceSymbol = tx.EmitContext().MostOriginal(tx.currentNamespace).Symbol() + } + if tx.currentEnum != nil { + currentEnumSymbol = tx.EmitContext().MostOriginal(tx.currentEnum).Symbol() + } + if container != nil && + ((ast.IsModuleDeclaration(container) && currentNamespaceSymbol != nil && container.Symbol() == currentNamespaceSymbol) || + (ast.IsEnumDeclaration(container) && currentEnumSymbol != nil && container.Symbol() == currentEnumSymbol)) { containerName := tx.getNamespaceContainerName(container) memberName := node.Clone(tx.Factory()) diff --git a/internal/transformers/tstransforms/runtimesyntax_test.go b/internal/transformers/tstransforms/runtimesyntax_test.go index deb8704666..7be92119e5 100644 --- a/internal/transformers/tstransforms/runtimesyntax_test.go +++ b/internal/transformers/tstransforms/runtimesyntax_test.go @@ -207,7 +207,7 @@ var E; E[E["A"] = 0] = "A"; })(E || (E = {})); (function (E) { - E["B"] = A; + E["B"] = E.A; if (typeof E.B !== "string") E[E.B] = "B"; })(E || (E = {}));`}, @@ -268,7 +268,7 @@ func TestNamespaceTransformer(t *testing.T) { N.x = 1; })(N || (N = {})); (function (N) { - x; + N.x; })(N || (N = {}));`}, {title: "exported array binding pattern", input: "namespace N { export var [x] = [1]; }", output: `var N; @@ -342,6 +342,15 @@ func TestNamespaceTransformer(t *testing.T) { N.f = f; })(N || (N = {}));`}, + {title: "exported function call across namespaces", input: "namespace N { export function Foo() {} } namespace N { Foo(); }", output: `var N; +(function (N) { + function Foo() { } + N.Foo = Foo; +})(N || (N = {})); +(function (N) { + N.Foo(); +})(N || (N = {}));`}, + {title: "export class", input: "namespace N { export class C {} }", output: `var N; (function (N) { class C { @@ -349,6 +358,34 @@ func TestNamespaceTransformer(t *testing.T) { N.C = C; })(N || (N = {}));`}, + {title: "class extends across namespaces", input: "namespace A { export class TypeA {} } namespace A { export class TypeB extends TypeA {} }", output: `var A; +(function (A) { + class TypeA { + } + A.TypeA = TypeA; +})(A || (A = {})); +(function (A) { + class TypeB extends A.TypeA { + } + A.TypeB = TypeB; +})(A || (A = {}));`}, + + {title: "three namespace blocks with class inheritance", input: "namespace N { export class A {} } namespace N { export class B extends A {} } namespace N { class C extends B {} }", output: `var N; +(function (N) { + class A { + } + N.A = A; +})(N || (N = {})); +(function (N) { + class B extends N.A { + } + N.B = B; +})(N || (N = {})); +(function (N) { + class C extends N.B { + } +})(N || (N = {}));`}, + {title: "export enum", input: "namespace N { export enum E {A} }", output: `var N; (function (N) { let E; diff --git a/testdata/baselines/reference/compiler/mergedNamespaceExportReference.js b/testdata/baselines/reference/compiler/mergedNamespaceExportReference.js new file mode 100644 index 0000000000..027b744d33 --- /dev/null +++ b/testdata/baselines/reference/compiler/mergedNamespaceExportReference.js @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/mergedNamespaceExportReference.ts] //// + +//// [mergedNamespaceExportReference.ts] +// Test that references to exported namespace members across merged namespace +// declarations are correctly qualified in the emitted JavaScript. + +namespace N { + export function foo() { return 1; } + export var x = 1; + export class C {} +} + +namespace N { + // These should emit as N.foo(), N.x, and N.C + foo(); + x; + class D extends C {} +} + + +//// [mergedNamespaceExportReference.js] +// Test that references to exported namespace members across merged namespace +// declarations are correctly qualified in the emitted JavaScript. +var N; +(function (N) { + function foo() { return 1; } + N.foo = foo; + N.x = 1; + class C { + } + N.C = C; +})(N || (N = {})); +(function (N) { + // These should emit as N.foo(), N.x, and N.C + N.foo(); + N.x; + class D extends N.C { + } +})(N || (N = {})); diff --git a/testdata/baselines/reference/compiler/mergedNamespaceExportReference.symbols b/testdata/baselines/reference/compiler/mergedNamespaceExportReference.symbols new file mode 100644 index 0000000000..9b8a604e0c --- /dev/null +++ b/testdata/baselines/reference/compiler/mergedNamespaceExportReference.symbols @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/mergedNamespaceExportReference.ts] //// + +=== mergedNamespaceExportReference.ts === +// Test that references to exported namespace members across merged namespace +// declarations are correctly qualified in the emitted JavaScript. + +namespace N { +>N : Symbol(N, Decl(mergedNamespaceExportReference.ts, 0, 0), Decl(mergedNamespaceExportReference.ts, 7, 1)) + + export function foo() { return 1; } +>foo : Symbol(foo, Decl(mergedNamespaceExportReference.ts, 3, 13)) + + export var x = 1; +>x : Symbol(x, Decl(mergedNamespaceExportReference.ts, 5, 14)) + + export class C {} +>C : Symbol(C, Decl(mergedNamespaceExportReference.ts, 5, 21)) +} + +namespace N { +>N : Symbol(N, Decl(mergedNamespaceExportReference.ts, 0, 0), Decl(mergedNamespaceExportReference.ts, 7, 1)) + + // These should emit as N.foo(), N.x, and N.C + foo(); +>foo : Symbol(foo, Decl(mergedNamespaceExportReference.ts, 3, 13)) + + x; +>x : Symbol(x, Decl(mergedNamespaceExportReference.ts, 5, 14)) + + class D extends C {} +>D : Symbol(D, Decl(mergedNamespaceExportReference.ts, 12, 6)) +>C : Symbol(C, Decl(mergedNamespaceExportReference.ts, 5, 21)) +} + diff --git a/testdata/baselines/reference/compiler/mergedNamespaceExportReference.types b/testdata/baselines/reference/compiler/mergedNamespaceExportReference.types new file mode 100644 index 0000000000..9b61e84d9c --- /dev/null +++ b/testdata/baselines/reference/compiler/mergedNamespaceExportReference.types @@ -0,0 +1,37 @@ +//// [tests/cases/compiler/mergedNamespaceExportReference.ts] //// + +=== mergedNamespaceExportReference.ts === +// Test that references to exported namespace members across merged namespace +// declarations are correctly qualified in the emitted JavaScript. + +namespace N { +>N : typeof N + + export function foo() { return 1; } +>foo : () => number +>1 : 1 + + export var x = 1; +>x : number +>1 : 1 + + export class C {} +>C : C +} + +namespace N { +>N : typeof N + + // These should emit as N.foo(), N.x, and N.C + foo(); +>foo() : number +>foo : () => number + + x; +>x : number + + class D extends C {} +>D : D +>C : C +} + diff --git a/testdata/tests/cases/compiler/mergedNamespaceExportReference.ts b/testdata/tests/cases/compiler/mergedNamespaceExportReference.ts new file mode 100644 index 0000000000..51fe698b97 --- /dev/null +++ b/testdata/tests/cases/compiler/mergedNamespaceExportReference.ts @@ -0,0 +1,17 @@ +// @target: esnext + +// Test that references to exported namespace members across merged namespace +// declarations are correctly qualified in the emitted JavaScript. + +namespace N { + export function foo() { return 1; } + export var x = 1; + export class C {} +} + +namespace N { + // These should emit as N.foo(), N.x, and N.C + foo(); + x; + class D extends C {} +}