diff --git a/tsc/internal/checker/nodebuilderimpl.go b/tsc/internal/checker/nodebuilderimpl.go index 53007b500e8c6..b56b2f211cc89 100644 --- a/tsc/internal/checker/nodebuilderimpl.go +++ b/tsc/internal/checker/nodebuilderimpl.go @@ -2680,7 +2680,7 @@ func (b *NodeBuilderImpl) createTypeNodesFromResolvedType(resolvedType *Structur continue } if getDeclarationModifierFlagsFromSymbol(propertySymbol)&(ast.ModifierFlagsPrivate|ast.ModifierFlagsProtected) != 0 { - b.ctx.tracker.ReportPrivateInBaseOfClassExpression(propertySymbol.Name) + b.ctx.tracker.ReportPrivateInBaseOfClassExpression(escapeInternalNameForTS4094(propertySymbol.Name)) } if IsPrivateIdentifierSymbol(propertySymbol) { b.ctx.tracker.ReportPrivateInBaseOfClassExpression(ast.SymbolName(propertySymbol)) @@ -3635,3 +3635,19 @@ func (b *NodeBuilderImpl) lookupExpressionChainTypeArgumentNodes(chain []*ast.Sy func (b *NodeBuilderImpl) shouldWriteTypeParametersInQualifiedName(chain []*ast.Symbol, index int) bool { return b.ctx.flags&nodebuilder.FlagsWriteTypeParametersInQualifiedName != 0 && index < len(chain)-1 } + +// escapeInternalNameForTS4094 prints unique-symbol names as "__@brand", not the "\xFE" sentinel +// and not the process-global "@" suffix (ast.nextSymbolId), which would flake baselines. +func escapeInternalNameForTS4094(name string) string { + escaped := ast.EscapeInternalSymbolName(name) + at := strings.LastIndexByte(escaped, '@') + if at <= 0 || at+1 >= len(escaped) { + return escaped + } + for i := at + 1; i < len(escaped); i++ { + if escaped[i] < '0' || escaped[i] > '9' { + return escaped + } + } + return escaped[:at] +} diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.errors.txt b/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.errors.txt new file mode 100644 index 0000000000000..df00a4fc5dc6a --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.errors.txt @@ -0,0 +1,25 @@ +index.ts(3,14): error TS4023: Exported variable 'f' has or is using name 'brand' from external module "helper" but cannot be named. +index.ts(3,14): error TS4094: Property '__@brand' of exported anonymous class type may not be private or protected. + + +==== helper.ts (0 errors) ==== + declare const brand: unique symbol; + + class Foo { + private [brand]: number = 1; + } + + export function makeFoo() { + return new Foo(); + } + +==== index.ts (2 errors) ==== + import { makeFoo } from "./helper"; + + export const f = () => makeFoo(); + ~ +!!! error TS4023: Exported variable 'f' has or is using name 'brand' from external module "helper" but cannot be named. + ~ +!!! error TS4094: Property '__@brand' of exported anonymous class type may not be private or protected. +!!! related TS9027 index.ts:3:14: Add a type annotation to the variable f. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.js b/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.js new file mode 100644 index 0000000000000..a7acc10af417c --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.js @@ -0,0 +1,38 @@ +//// [tests/cases/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.ts] //// + +//// [helper.ts] +declare const brand: unique symbol; + +class Foo { + private [brand]: number = 1; +} + +export function makeFoo() { + return new Foo(); +} + +//// [index.ts] +import { makeFoo } from "./helper"; + +export const f = () => makeFoo(); + + +//// [helper.js] +class Foo { + [brand] = 1; +} +export function makeFoo() { + return new Foo(); +} +//// [index.js] +import { makeFoo } from "./helper"; +export const f = () => makeFoo(); + + +//// [helper.d.ts] +declare const brand: unique symbol; +declare class Foo { + private [brand]; +} +export declare function makeFoo(): Foo; +export {}; diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.symbols b/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.symbols new file mode 100644 index 0000000000000..9505748314f46 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.symbols @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.ts] //// + +=== helper.ts === +declare const brand: unique symbol; +>brand : Symbol(brand, Decl(helper.ts, 0, 13)) + +class Foo { +>Foo : Symbol(Foo, Decl(helper.ts, 0, 35)) + + private [brand]: number = 1; +>[brand] : Symbol(Foo[brand], Decl(helper.ts, 2, 11)) +>brand : Symbol(brand, Decl(helper.ts, 0, 13)) +} + +export function makeFoo() { +>makeFoo : Symbol(makeFoo, Decl(helper.ts, 4, 1)) + + return new Foo(); +>Foo : Symbol(Foo, Decl(helper.ts, 0, 35)) +} + +=== index.ts === +import { makeFoo } from "./helper"; +>makeFoo : Symbol(makeFoo, Decl(index.ts, 0, 8)) + +export const f = () => makeFoo(); +>f : Symbol(f, Decl(index.ts, 2, 12)) +>makeFoo : Symbol(makeFoo, Decl(index.ts, 0, 8)) + diff --git a/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.types b/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.types new file mode 100644 index 0000000000000..e965d785347aa --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.types @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.ts] //// + +=== helper.ts === +declare const brand: unique symbol; +>brand : unique symbol + +class Foo { +>Foo : Foo + + private [brand]: number = 1; +>[brand] : number +>brand : unique symbol +>1 : 1 +} + +export function makeFoo() { +>makeFoo : () => Foo + + return new Foo(); +>new Foo() : Foo +>Foo : typeof Foo +} + +=== index.ts === +import { makeFoo } from "./helper"; +>makeFoo : () => Foo + +export const f = () => makeFoo(); +>f : () => Foo +>() => makeFoo() : () => Foo +>makeFoo() : Foo +>makeFoo : () => Foo + diff --git a/tsc/testdata/tests/cases/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.ts b/tsc/testdata/tests/cases/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.ts new file mode 100644 index 0000000000000..bc3d9f72f0b7d --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/declarationEmitAnonymousClassUniqueSymbolPrivate.ts @@ -0,0 +1,16 @@ +// @declaration: true +// @filename: helper.ts +declare const brand: unique symbol; + +class Foo { + private [brand]: number = 1; +} + +export function makeFoo() { + return new Foo(); +} + +// @filename: index.ts +import { makeFoo } from "./helper"; + +export const f = () => makeFoo();