Skip to content

Commit 88a1e3a

Browse files
authored
Transform decorators that reference private names into a 'static {}' block (#50074)
1 parent 5374fd9 commit 88a1e3a

13 files changed

+508
-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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//// [decoratorOnClassMethod19.ts]
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
5+
class C1 {
6+
#x
7+
8+
@decorator((x: C1) => x.#x)
9+
y() {}
10+
}
11+
12+
class C2 {
13+
#x
14+
15+
y(@decorator((x: C2) => x.#x) p) {}
16+
}
17+
18+
19+
//// [decoratorOnClassMethod19.js]
20+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
21+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
22+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
23+
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;
24+
return c > 3 && r && Object.defineProperty(target, key, r), r;
25+
};
26+
var __metadata = (this && this.__metadata) || function (k, v) {
27+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
28+
};
29+
var __param = (this && this.__param) || function (paramIndex, decorator) {
30+
return function (target, key) { decorator(target, key, paramIndex); }
31+
};
32+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
33+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
34+
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");
35+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
36+
};
37+
var _C1_x, _C2_x;
38+
class C1 {
39+
constructor() {
40+
_C1_x.set(this, void 0);
41+
}
42+
y() { }
43+
}
44+
_C1_x = new WeakMap();
45+
(() => {
46+
__decorate([
47+
decorator((x) => __classPrivateFieldGet(x, _C1_x, "f")),
48+
__metadata("design:type", Function),
49+
__metadata("design:paramtypes", []),
50+
__metadata("design:returntype", void 0)
51+
], C1.prototype, "y", null);
52+
})();
53+
class C2 {
54+
constructor() {
55+
_C2_x.set(this, void 0);
56+
}
57+
y(p) { }
58+
}
59+
_C2_x = new WeakMap();
60+
(() => {
61+
__decorate([
62+
__param(0, decorator((x) => __classPrivateFieldGet(x, _C2_x, "f"))),
63+
__metadata("design:type", Function),
64+
__metadata("design:paramtypes", [Object]),
65+
__metadata("design:returntype", void 0)
66+
], C2.prototype, "y", null);
67+
})();
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 C1 {
7+
>C1 : Symbol(C1, Decl(decoratorOnClassMethod19.ts, 1, 27))
8+
9+
#x
10+
>#x : Symbol(C1.#x, Decl(decoratorOnClassMethod19.ts, 3, 10))
11+
12+
@decorator((x: C1) => x.#x)
13+
>decorator : Symbol(decorator, Decl(decoratorOnClassMethod19.ts, 1, 11))
14+
>x : Symbol(x, Decl(decoratorOnClassMethod19.ts, 6, 16))
15+
>C1 : Symbol(C1, Decl(decoratorOnClassMethod19.ts, 1, 27))
16+
>x.#x : Symbol(C1.#x, Decl(decoratorOnClassMethod19.ts, 3, 10))
17+
>x : Symbol(x, Decl(decoratorOnClassMethod19.ts, 6, 16))
18+
19+
y() {}
20+
>y : Symbol(C1.y, Decl(decoratorOnClassMethod19.ts, 4, 6))
21+
}
22+
23+
class C2 {
24+
>C2 : Symbol(C2, Decl(decoratorOnClassMethod19.ts, 8, 1))
25+
26+
#x
27+
>#x : Symbol(C2.#x, Decl(decoratorOnClassMethod19.ts, 10, 10))
28+
29+
y(@decorator((x: C2) => x.#x) p) {}
30+
>y : Symbol(C2.y, Decl(decoratorOnClassMethod19.ts, 11, 6))
31+
>decorator : Symbol(decorator, Decl(decoratorOnClassMethod19.ts, 1, 11))
32+
>x : Symbol(x, Decl(decoratorOnClassMethod19.ts, 13, 18))
33+
>C2 : Symbol(C2, Decl(decoratorOnClassMethod19.ts, 8, 1))
34+
>x.#x : Symbol(C2.#x, Decl(decoratorOnClassMethod19.ts, 10, 10))
35+
>x : Symbol(x, Decl(decoratorOnClassMethod19.ts, 13, 18))
36+
>p : Symbol(p, Decl(decoratorOnClassMethod19.ts, 13, 6))
37+
}
38+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 C1 {
7+
>C1 : C1
8+
9+
#x
10+
>#x : any
11+
12+
@decorator((x: C1) => x.#x)
13+
>decorator((x: C1) => x.#x) : any
14+
>decorator : any
15+
>(x: C1) => x.#x : (x: C1) => any
16+
>x : C1
17+
>x.#x : any
18+
>x : C1
19+
20+
y() {}
21+
>y : () => void
22+
}
23+
24+
class C2 {
25+
>C2 : C2
26+
27+
#x
28+
>#x : any
29+
30+
y(@decorator((x: C2) => x.#x) p) {}
31+
>y : (p: any) => void
32+
>decorator((x: C2) => x.#x) : any
33+
>decorator : any
34+
>(x: C2) => x.#x : (x: C2) => any
35+
>x : C2
36+
>x.#x : any
37+
>x : C2
38+
>p : any
39+
}
40+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//// [decoratorOnClassMethod19.ts]
2+
// https://github.com/microsoft/TypeScript/issues/48515
3+
declare var decorator: any;
4+
5+
class C1 {
6+
#x
7+
8+
@decorator((x: C1) => x.#x)
9+
y() {}
10+
}
11+
12+
class C2 {
13+
#x
14+
15+
y(@decorator((x: C2) => x.#x) p) {}
16+
}
17+
18+
19+
//// [decoratorOnClassMethod19.js]
20+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
21+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
22+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
23+
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;
24+
return c > 3 && r && Object.defineProperty(target, key, r), r;
25+
};
26+
var __metadata = (this && this.__metadata) || function (k, v) {
27+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
28+
};
29+
var __param = (this && this.__param) || function (paramIndex, decorator) {
30+
return function (target, key) { decorator(target, key, paramIndex); }
31+
};
32+
class C1 {
33+
#x;
34+
y() { }
35+
static {
36+
__decorate([
37+
decorator((x) => x.#x),
38+
__metadata("design:type", Function),
39+
__metadata("design:paramtypes", []),
40+
__metadata("design:returntype", void 0)
41+
], C1.prototype, "y", null);
42+
}
43+
}
44+
class C2 {
45+
#x;
46+
y(p) { }
47+
static {
48+
__decorate([
49+
__param(0, decorator((x) => x.#x)),
50+
__metadata("design:type", Function),
51+
__metadata("design:paramtypes", [Object]),
52+
__metadata("design:returntype", void 0)
53+
], C2.prototype, "y", null);
54+
}
55+
}

0 commit comments

Comments
 (0)