Skip to content

Commit 0c04351

Browse files
committed
Transform decorators that reference private names into a 'static {}' block
1 parent 8d0c72d commit 0c04351

13 files changed

+337
-26
lines changed

src/compiler/factory/nodeFactory.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2331,7 +2331,7 @@ namespace ts {
23312331
propagateChildFlags(node.expression) |
23322332
(isIdentifier(node.name) ?
23332333
propagateIdentifierNameFlags(node.name) :
2334-
propagateChildFlags(node.name));
2334+
propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression);
23352335
if (isSuperKeyword(expression)) {
23362336
// super method calls require a lexical 'this'
23372337
// super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators
@@ -2366,7 +2366,7 @@ namespace ts {
23662366
propagateChildFlags(node.questionDotToken) |
23672367
(isIdentifier(node.name) ?
23682368
propagateIdentifierNameFlags(node.name) :
2369-
propagateChildFlags(node.name));
2369+
propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression);
23702370
return node;
23712371
}
23722372

@@ -2851,6 +2851,9 @@ namespace ts {
28512851
else if (isLogicalOrCoalescingAssignmentOperator(operatorKind)) {
28522852
node.transformFlags |= TransformFlags.ContainsES2021;
28532853
}
2854+
if (operatorKind === SyntaxKind.InKeyword && isPrivateIdentifier(node.left)) {
2855+
node.transformFlags |= TransformFlags.ContainsPrivateIdentifierInExpression;
2856+
}
28542857
return node;
28552858
}
28562859

src/compiler/transformers/legacyDecorators.ts

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,52 +68,88 @@ namespace ts {
6868
function visitClassDeclaration(node: ClassDeclaration): VisitResult<Statement> {
6969
if (!(classOrConstructorParameterIsDecorated(node) || childIsDecorated(node))) return visitEachChild(node, visitor, context);
7070

71-
const classStatement = hasDecorators(node) ?
72-
createClassDeclarationHeadWithDecorators(node, node.name) :
73-
createClassDeclarationHeadWithoutDecorators(node, node.name);
74-
75-
const statements: Statement[] = [classStatement];
76-
77-
// Write any decorators of the node.
78-
addClassElementDecorationStatements(statements, node, /*isStatic*/ false);
79-
addClassElementDecorationStatements(statements, node, /*isStatic*/ true);
80-
addConstructorDecorationStatement(statements, node);
71+
const statements = hasDecorators(node) ?
72+
transformClassDeclarationWithClassDecorators(node, node.name) :
73+
transformClassDeclarationWithoutClassDecorators(node, node.name);
8174

8275
if (statements.length > 1) {
8376
// Add a DeclarationMarker as a marker for the end of the declaration
8477
statements.push(factory.createEndOfDeclarationMarker(node));
85-
setEmitFlags(classStatement, getEmitFlags(classStatement) | EmitFlags.HasEndOfDeclarationMarker);
78+
setEmitFlags(statements[0], getEmitFlags(statements[0]) | EmitFlags.HasEndOfDeclarationMarker);
8679
}
8780

8881
return singleOrMany(statements);
8982
}
9083

84+
function decoratorContainsPrivateIdentifierInExpression(decorator: Decorator) {
85+
return !!(decorator.transformFlags & TransformFlags.ContainsPrivateIdentifierInExpression);
86+
}
87+
88+
function parameterDecoratorsContainPrivateIdentifierInExpression(parameterDecorators: readonly Decorator[] | undefined) {
89+
return some(parameterDecorators, decoratorContainsPrivateIdentifierInExpression);
90+
}
91+
92+
function hasClassElementWithDecoratorContainingPrivateIdentifierInExpression(node: ClassDeclaration) {
93+
for (const member of node.members) {
94+
if (!canHaveDecorators(member)) continue;
95+
const allDecorators = getAllDecoratorsOfClassElement(member, node);
96+
if (some(allDecorators?.decorators, decoratorContainsPrivateIdentifierInExpression)) return true;
97+
if (some(allDecorators?.parameters, parameterDecoratorsContainPrivateIdentifierInExpression)) return true;
98+
}
99+
return false;
100+
}
101+
102+
function transformDecoratorsOfClassElements(node: ClassDeclaration, members: NodeArray<ClassElement>) {
103+
let decorationStatements: Statement[] | undefined = [];
104+
addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ false);
105+
addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ true);
106+
if (hasClassElementWithDecoratorContainingPrivateIdentifierInExpression(node)) {
107+
members = setTextRange(factory.createNodeArray([
108+
...members,
109+
factory.createClassStaticBlockDeclaration(
110+
factory.createBlock(decorationStatements, /*multiLine*/ true)
111+
)
112+
]), members);
113+
decorationStatements = undefined;
114+
}
115+
return { decorationStatements, members };
116+
}
117+
91118
/**
92119
* Transforms a non-decorated class declaration.
93120
*
94121
* @param node A ClassDeclaration node.
95122
* @param name The name of the class.
96123
*/
97-
function createClassDeclarationHeadWithoutDecorators(node: ClassDeclaration, name: Identifier | undefined) {
124+
function transformClassDeclarationWithoutClassDecorators(node: ClassDeclaration, name: Identifier | undefined) {
98125
// ${modifiers} class ${name} ${heritageClauses} {
99126
// ${members}
100127
// }
101128

102-
return factory.updateClassDeclaration(
129+
const modifiers = visitNodes(node.modifiers, modifierVisitor, isModifier);
130+
const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
131+
let members = visitNodes(node.members, visitor, isClassElement);
132+
133+
let decorationStatements: Statement[] | undefined = [];
134+
({ members, decorationStatements } = transformDecoratorsOfClassElements(node, members));
135+
136+
const updated = factory.updateClassDeclaration(
103137
node,
104-
visitNodes(node.modifiers, modifierVisitor, isModifier),
138+
modifiers,
105139
name,
106140
/*typeParameters*/ undefined,
107-
visitNodes(node.heritageClauses, visitor, isHeritageClause),
108-
visitNodes(node.members, visitor, isClassElement)
141+
heritageClauses,
142+
members
109143
);
144+
145+
return addRange([updated], decorationStatements);
110146
}
111147

112148
/**
113149
* Transforms a decorated class declaration and appends the resulting statements. If
114150
* the class requires an alias to avoid issues with double-binding, the alias is returned.
115151
*/
116-
function createClassDeclarationHeadWithDecorators(node: ClassDeclaration, name: Identifier | undefined) {
152+
function transformClassDeclarationWithClassDecorators(node: ClassDeclaration, name: Identifier | undefined) {
117153
// When we emit an ES6 class that has a class decorator, we must tailor the
118154
// emit to certain specific cases.
119155
//
@@ -213,8 +249,18 @@ namespace ts {
213249
// ${members}
214250
// }
215251
const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
216-
const members = visitNodes(node.members, visitor, isClassElement);
217-
const classExpression = factory.createClassExpression(/*modifiers*/ undefined, name, /*typeParameters*/ undefined, heritageClauses, members);
252+
let members = visitNodes(node.members, visitor, isClassElement);
253+
254+
let decorationStatements: Statement[] | undefined = [];
255+
({ members, decorationStatements } = transformDecoratorsOfClassElements(node, members));
256+
257+
const classExpression = factory.createClassExpression(
258+
/*modifiers*/ undefined,
259+
name,
260+
/*typeParameters*/ undefined,
261+
heritageClauses,
262+
members);
263+
218264
setOriginalNode(classExpression, node);
219265
setTextRange(classExpression, location);
220266

@@ -234,7 +280,11 @@ namespace ts {
234280
setOriginalNode(statement, node);
235281
setTextRange(statement, location);
236282
setCommentRange(statement, node);
237-
return statement;
283+
284+
const statements: Statement[] = [statement];
285+
addRange(statements, decorationStatements);
286+
addConstructorDecorationStatement(statements, node);
287+
return statements;
238288
}
239289

240290
function visitClassExpression(node: ClassExpression) {

src/compiler/types.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7041,10 +7041,9 @@ namespace ts {
70417041
ContainsPossibleTopLevelAwait = 1 << 26,
70427042
ContainsLexicalSuper = 1 << 27,
70437043
ContainsUpdateExpressionForIdentifier = 1 << 28,
7044-
// Please leave this as 1 << 29.
7045-
// It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system.
7046-
// It is a good reminder of how much room we have left
7047-
HasComputedFlags = 1 << 29, // Transform flags have been computed.
7044+
ContainsPrivateIdentifierInExpression = 1 << 29,
7045+
7046+
HasComputedFlags = 1 << 31, // Transform flags have been computed.
70487047

70497048
// Assertions
70507049
// - Bitmasks that are used to assert facts about the syntax of a node and its subtree.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//// [decoratorOnClassMethod19.ts]
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
5+
class Foo {
6+
#bar
7+
8+
@decorator((x: Foo) => x.#bar)
9+
baz() {}
10+
}
11+
12+
13+
//// [decoratorOnClassMethod19.js]
14+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
15+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18+
return c > 3 && r && Object.defineProperty(target, key, r), r;
19+
};
20+
var __metadata = (this && this.__metadata) || function (k, v) {
21+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22+
};
23+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
24+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
25+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
26+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
27+
};
28+
var _Foo_bar;
29+
class Foo {
30+
constructor() {
31+
_Foo_bar.set(this, void 0);
32+
}
33+
baz() { }
34+
}
35+
_Foo_bar = new WeakMap();
36+
(() => {
37+
__decorate([
38+
decorator((x) => __classPrivateFieldGet(x, _Foo_bar, "f")),
39+
__metadata("design:type", Function),
40+
__metadata("design:paramtypes", []),
41+
__metadata("design:returntype", void 0)
42+
], Foo.prototype, "baz", null);
43+
})();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/conformance/decorators/class/method/decoratorOnClassMethod19.ts ===
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
>decorator : Symbol(decorator, Decl(decoratorOnClassMethod19.ts, 1, 11))
5+
6+
class Foo {
7+
>Foo : Symbol(Foo, Decl(decoratorOnClassMethod19.ts, 1, 27))
8+
9+
#bar
10+
>#bar : Symbol(Foo.#bar, Decl(decoratorOnClassMethod19.ts, 3, 11))
11+
12+
@decorator((x: Foo) => x.#bar)
13+
>decorator : Symbol(decorator, Decl(decoratorOnClassMethod19.ts, 1, 11))
14+
>x : Symbol(x, Decl(decoratorOnClassMethod19.ts, 6, 16))
15+
>Foo : Symbol(Foo, Decl(decoratorOnClassMethod19.ts, 1, 27))
16+
>x.#bar : Symbol(Foo.#bar, Decl(decoratorOnClassMethod19.ts, 3, 11))
17+
>x : Symbol(x, Decl(decoratorOnClassMethod19.ts, 6, 16))
18+
19+
baz() {}
20+
>baz : Symbol(Foo.baz, Decl(decoratorOnClassMethod19.ts, 4, 8))
21+
}
22+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/decorators/class/method/decoratorOnClassMethod19.ts ===
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
>decorator : any
5+
6+
class Foo {
7+
>Foo : Foo
8+
9+
#bar
10+
>#bar : any
11+
12+
@decorator((x: Foo) => x.#bar)
13+
>decorator((x: Foo) => x.#bar) : any
14+
>decorator : any
15+
>(x: Foo) => x.#bar : (x: Foo) => any
16+
>x : Foo
17+
>x.#bar : any
18+
>x : Foo
19+
20+
baz() {}
21+
>baz : () => void
22+
}
23+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//// [decoratorOnClassMethod19.ts]
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
5+
class Foo {
6+
#bar
7+
8+
@decorator((x: Foo) => x.#bar)
9+
baz() {}
10+
}
11+
12+
13+
//// [decoratorOnClassMethod19.js]
14+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
15+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18+
return c > 3 && r && Object.defineProperty(target, key, r), r;
19+
};
20+
var __metadata = (this && this.__metadata) || function (k, v) {
21+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22+
};
23+
class Foo {
24+
#bar;
25+
baz() { }
26+
static {
27+
__decorate([
28+
decorator((x) => x.#bar),
29+
__metadata("design:type", Function),
30+
__metadata("design:paramtypes", []),
31+
__metadata("design:returntype", void 0)
32+
], Foo.prototype, "baz", null);
33+
}
34+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/conformance/decorators/class/method/decoratorOnClassMethod19.ts ===
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
>decorator : Symbol(decorator, Decl(decoratorOnClassMethod19.ts, 1, 11))
5+
6+
class Foo {
7+
>Foo : Symbol(Foo, Decl(decoratorOnClassMethod19.ts, 1, 27))
8+
9+
#bar
10+
>#bar : Symbol(Foo.#bar, Decl(decoratorOnClassMethod19.ts, 3, 11))
11+
12+
@decorator((x: Foo) => x.#bar)
13+
>decorator : Symbol(decorator, Decl(decoratorOnClassMethod19.ts, 1, 11))
14+
>x : Symbol(x, Decl(decoratorOnClassMethod19.ts, 6, 16))
15+
>Foo : Symbol(Foo, Decl(decoratorOnClassMethod19.ts, 1, 27))
16+
>x.#bar : Symbol(Foo.#bar, Decl(decoratorOnClassMethod19.ts, 3, 11))
17+
>x : Symbol(x, Decl(decoratorOnClassMethod19.ts, 6, 16))
18+
19+
baz() {}
20+
>baz : Symbol(Foo.baz, Decl(decoratorOnClassMethod19.ts, 4, 8))
21+
}
22+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/decorators/class/method/decoratorOnClassMethod19.ts ===
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
>decorator : any
5+
6+
class Foo {
7+
>Foo : Foo
8+
9+
#bar
10+
>#bar : any
11+
12+
@decorator((x: Foo) => x.#bar)
13+
>decorator((x: Foo) => x.#bar) : any
14+
>decorator : any
15+
>(x: Foo) => x.#bar : (x: Foo) => any
16+
>x : Foo
17+
>x.#bar : any
18+
>x : Foo
19+
20+
baz() {}
21+
>baz : () => void
22+
}
23+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//// [decoratorOnClassMethod19.ts]
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
5+
class Foo {
6+
#bar
7+
8+
@decorator((x: Foo) => x.#bar)
9+
baz() {}
10+
}
11+
12+
13+
//// [decoratorOnClassMethod19.js]
14+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
15+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18+
return c > 3 && r && Object.defineProperty(target, key, r), r;
19+
};
20+
var __metadata = (this && this.__metadata) || function (k, v) {
21+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22+
};
23+
class Foo {
24+
#bar;
25+
baz() { }
26+
static {
27+
__decorate([
28+
decorator((x) => x.#bar),
29+
__metadata("design:type", Function),
30+
__metadata("design:paramtypes", []),
31+
__metadata("design:returntype", void 0)
32+
], Foo.prototype, "baz", null);
33+
}
34+
}

0 commit comments

Comments
 (0)