Skip to content

Commit d8e2c87

Browse files
committed
feat(7342): class expression decorators
1 parent b0474dd commit d8e2c87

File tree

189 files changed

+2944
-387
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

189 files changed

+2944
-387
lines changed

src/compiler/transformers/ts.ts

+37-19
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ namespace ts {
568568
/**
569569
* Tests whether we should emit a __decorate call for a class declaration.
570570
*/
571-
function shouldEmitDecorateCallForClass(node: ClassDeclaration) {
571+
function shouldEmitDecorateCallForClass(node: ClassDeclaration | ClassExpression) {
572572
if (node.decorators && node.decorators.length > 0) {
573573
return true;
574574
}
@@ -588,7 +588,7 @@ namespace ts {
588588
return parameter.decorators !== undefined && parameter.decorators.length > 0;
589589
}
590590

591-
function getClassFacts(node: ClassDeclaration, staticProperties: readonly PropertyDeclaration[]) {
591+
function getClassFacts(node: ClassDeclaration | ClassExpression, staticProperties: readonly PropertyDeclaration[]) {
592592
let facts = ClassFacts.None;
593593
if (some(staticProperties)) facts |= ClassFacts.HasStaticInitializedProperties;
594594
const extendsClauseElement = getEffectiveBaseTypeNode(node);
@@ -634,8 +634,8 @@ namespace ts {
634634

635635

636636
// Write any decorators of the node.
637-
addClassElementDecorationStatements(statements, node, /*isStatic*/ false);
638-
addClassElementDecorationStatements(statements, node, /*isStatic*/ true);
637+
addClassElementDecorationStatements(statements, createClassPrototype(factory.getDeclarationName(node)), node, /*isStatic*/ false);
638+
addClassElementDecorationStatements(statements, factory.getDeclarationName(node), node, /*isStatic*/ true);
639639
addConstructorDecorationStatement(statements, node);
640640

641641
if (facts & ClassFacts.UseImmediatelyInvokedFunctionExpression) {
@@ -879,6 +879,9 @@ namespace ts {
879879
return visitEachChild(node, visitor, context);
880880
}
881881

882+
const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true);
883+
const facts = getClassFacts(node, staticProperties);
884+
882885
const classExpression = factory.createClassExpression(
883886
/*decorators*/ undefined,
884887
/*modifiers*/ undefined,
@@ -888,6 +891,14 @@ namespace ts {
888891
transformClassMembers(node)
889892
);
890893

894+
if (facts & ClassFacts.HasMemberDecorators) {
895+
const temp = factory.createTempVariable(hoistVariableDeclaration);
896+
const expressions: Expression[] = [factory.createAssignment(temp, classExpression)];
897+
addClassElementDecorationExpressions(expressions, createClassPrototype(temp), node, /*isStatic*/ false);
898+
addClassElementDecorationExpressions(expressions, temp, node, /*isStatic*/ true);
899+
return factory.inlineExpressions([...expressions, temp]);
900+
}
901+
891902
setOriginalNode(classExpression, node);
892903
setTextRange(classExpression, node);
893904

@@ -1123,31 +1134,44 @@ namespace ts {
11231134
return decoratorExpressions;
11241135
}
11251136

1137+
/**
1138+
* Generates expressions used to apply decorators to either the static or instance member of a class.
1139+
*
1140+
* @param target
1141+
* @param node The class node.
1142+
* @param isStatic A value indicating whether to generate expressions for static or instance members.
1143+
*/
1144+
function addClassElementDecorationExpressions(expressions: Expression[], target: Identifier | PropertyAccessExpression, node: ClassLikeDeclaration, isStatic: boolean) {
1145+
addRange(expressions, map(generateClassElementDecorationExpressions(target, node, isStatic), expression => expression));
1146+
}
1147+
11261148
/**
11271149
* Generates statements used to apply decorators to either the static or instance members
11281150
* of a class.
11291151
*
1152+
* @param target
11301153
* @param node The class node.
11311154
* @param isStatic A value indicating whether to generate statements for static or
11321155
* instance members.
11331156
*/
1134-
function addClassElementDecorationStatements(statements: Statement[], node: ClassDeclaration, isStatic: boolean) {
1135-
addRange(statements, map(generateClassElementDecorationExpressions(node, isStatic), expressionToStatement));
1157+
function addClassElementDecorationStatements(statements: Statement[], target: Identifier | PropertyAccessExpression, node: ClassLikeDeclaration, isStatic: boolean) {
1158+
addRange(statements, map(generateClassElementDecorationExpressions(target, node, isStatic), expressionToStatement));
11361159
}
11371160

11381161
/**
11391162
* Generates expressions used to apply decorators to either the static or instance members
11401163
* of a class.
11411164
*
1165+
* @param target
11421166
* @param node The class node.
11431167
* @param isStatic A value indicating whether to generate expressions for static or
11441168
* instance members.
11451169
*/
1146-
function generateClassElementDecorationExpressions(node: ClassExpression | ClassDeclaration, isStatic: boolean) {
1170+
function generateClassElementDecorationExpressions(target: Identifier | PropertyAccessExpression, node: ClassExpression | ClassDeclaration, isStatic: boolean) {
11471171
const members = getDecoratedClassElements(node, isStatic);
11481172
let expressions: Expression[] | undefined;
11491173
for (const member of members) {
1150-
const expression = generateClassElementDecorationExpression(node, member);
1174+
const expression = generateClassElementDecorationExpression(target, node, member);
11511175
if (expression) {
11521176
if (!expressions) {
11531177
expressions = [expression];
@@ -1163,10 +1187,11 @@ namespace ts {
11631187
/**
11641188
* Generates an expression used to evaluate class element decorators at runtime.
11651189
*
1190+
* @param target
11661191
* @param node The class node that contains the member.
11671192
* @param member The class member.
11681193
*/
1169-
function generateClassElementDecorationExpression(node: ClassExpression | ClassDeclaration, member: ClassElement) {
1194+
function generateClassElementDecorationExpression(target: Identifier | PropertyAccessExpression, node: ClassExpression | ClassDeclaration, member: ClassElement) {
11701195
const allDecorators = getAllDecoratorsOfClassElement(node, member);
11711196
const decoratorExpressions = transformAllDecoratorsOfDeclaration(member, node, allDecorators);
11721197
if (!decoratorExpressions) {
@@ -1204,7 +1229,6 @@ namespace ts {
12041229
// ], C.prototype, "prop");
12051230
//
12061231

1207-
const prefix = getClassMemberPrefix(node, member);
12081232
const memberName = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ true);
12091233
const descriptor = languageVersion > ScriptTarget.ES3
12101234
? member.kind === SyntaxKind.PropertyDeclaration
@@ -1219,7 +1243,7 @@ namespace ts {
12191243

12201244
const helper = emitHelpers().createDecorateHelper(
12211245
decoratorExpressions,
1222-
prefix,
1246+
target,
12231247
memberName,
12241248
descriptor
12251249
);
@@ -3151,14 +3175,8 @@ namespace ts {
31513175
}
31523176
}
31533177

3154-
function getClassPrototype(node: ClassExpression | ClassDeclaration) {
3155-
return factory.createPropertyAccessExpression(factory.getDeclarationName(node), "prototype");
3156-
}
3157-
3158-
function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) {
3159-
return hasSyntacticModifier(member, ModifierFlags.Static)
3160-
? factory.getDeclarationName(node)
3161-
: getClassPrototype(node);
3178+
function createClassPrototype(expression: Expression) {
3179+
return factory.createPropertyAccessExpression(expression, "prototype");
31623180
}
31633181

31643182
function enableSubstitutionForNonQualifiedEnumMembers() {

src/compiler/utilities.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -1775,23 +1775,23 @@ namespace ts {
17751775
return true;
17761776

17771777
case SyntaxKind.PropertyDeclaration:
1778-
// property declarations are valid if their parent is a class declaration.
1779-
return parent!.kind === SyntaxKind.ClassDeclaration;
1778+
// property declarations are valid if their parent is a class declaration/expression.
1779+
return isClassLike(parent!);
17801780

17811781
case SyntaxKind.GetAccessor:
17821782
case SyntaxKind.SetAccessor:
17831783
case SyntaxKind.MethodDeclaration:
1784-
// if this method has a body and its parent is a class declaration, this is a valid target.
1784+
// if this method has a body and its parent is a class declaration/expression, this is a valid target.
17851785
return (<FunctionLikeDeclaration>node).body !== undefined
1786-
&& parent!.kind === SyntaxKind.ClassDeclaration;
1786+
&& isClassLike(parent!);
17871787

17881788
case SyntaxKind.Parameter:
1789-
// if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target;
1789+
// if the parameter's parent has a body and its grandparent is a class declaration/expression, this is a valid target;
17901790
return (<FunctionLikeDeclaration>parent).body !== undefined
17911791
&& (parent!.kind === SyntaxKind.Constructor
17921792
|| parent!.kind === SyntaxKind.MethodDeclaration
17931793
|| parent!.kind === SyntaxKind.SetAccessor)
1794-
&& grandparent!.kind === SyntaxKind.ClassDeclaration;
1794+
&& isClassLike(grandparent!);
17951795
}
17961796

17971797
return false;
@@ -1812,12 +1812,13 @@ namespace ts {
18121812
return nodeIsDecorated(node, parent!, grandparent!) || childIsDecorated(node, parent!); // TODO: GH#18217
18131813
}
18141814

1815-
export function childIsDecorated(node: ClassDeclaration): boolean;
1815+
export function childIsDecorated(node: ClassLikeDeclaration): boolean;
18161816
export function childIsDecorated(node: Node, parent: Node): boolean;
18171817
export function childIsDecorated(node: Node, parent?: Node): boolean {
18181818
switch (node.kind) {
1819+
case SyntaxKind.ClassExpression:
18191820
case SyntaxKind.ClassDeclaration:
1820-
return some((<ClassDeclaration>node).members, m => nodeOrChildIsDecorated(m, node, parent!)); // TODO: GH#18217
1821+
return some((<ClassDeclaration | ClassExpression>node).members, m => nodeOrChildIsDecorated(m, node, parent!)); // TODO: GH#18217
18211822
case SyntaxKind.MethodDeclaration:
18221823
case SyntaxKind.SetAccessor:
18231824
return some((<FunctionLikeDeclaration>node).parameters, p => nodeIsDecorated(p, node, parent!)); // TODO: GH#18217

tests/baselines/reference/decoratorChecksFunctionBodies.errors.txt

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
tests/cases/conformance/decorators/class/decoratorChecksFunctionBodies.ts(8,14): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
2+
tests/cases/conformance/decorators/class/decoratorChecksFunctionBodies.ts(19,14): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
23

34

4-
==== tests/cases/conformance/decorators/class/decoratorChecksFunctionBodies.ts (1 errors) ====
5+
==== tests/cases/conformance/decorators/class/decoratorChecksFunctionBodies.ts (2 errors) ====
56
// from #2971
67
function func(s: string): void {
78
}
@@ -17,4 +18,18 @@ tests/cases/conformance/decorators/class/decoratorChecksFunctionBodies.ts(8,14):
1718
m() {
1819

1920
}
20-
}
21+
}
22+
23+
const A1 = class {
24+
@((x, p) => {
25+
var a = 3;
26+
func(a);
27+
~
28+
!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
29+
return x;
30+
})
31+
m() {
32+
33+
}
34+
}
35+

tests/baselines/reference/decoratorChecksFunctionBodies.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,19 @@ class A {
1212
m() {
1313

1414
}
15-
}
15+
}
16+
17+
const A1 = class {
18+
@((x, p) => {
19+
var a = 3;
20+
func(a);
21+
return x;
22+
})
23+
m() {
24+
25+
}
26+
}
27+
1628

1729
//// [decoratorChecksFunctionBodies.js]
1830
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
@@ -21,6 +33,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
2133
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;
2234
return c > 3 && r && Object.defineProperty(target, key, r), r;
2335
};
36+
var _a;
2437
// from #2971
2538
function func(s) {
2639
}
@@ -38,3 +51,16 @@ var A = /** @class */ (function () {
3851
], A.prototype, "m", null);
3952
return A;
4053
}());
54+
var A1 = (_a = /** @class */ (function () {
55+
function class_1() {
56+
}
57+
class_1.prototype.m = function () {
58+
};
59+
return class_1;
60+
}()), __decorate([
61+
(function (x, p) {
62+
var a = 3;
63+
func(a);
64+
return x;
65+
})
66+
], _a.prototype, "m", null), _a);

tests/baselines/reference/decoratorChecksFunctionBodies.symbols

+25
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,28 @@ class A {
2828

2929
}
3030
}
31+
32+
const A1 = class {
33+
>A1 : Symbol(A1, Decl(decoratorChecksFunctionBodies.ts, 15, 5))
34+
35+
@((x, p) => {
36+
>x : Symbol(x, Decl(decoratorChecksFunctionBodies.ts, 16, 7))
37+
>p : Symbol(p, Decl(decoratorChecksFunctionBodies.ts, 16, 9))
38+
39+
var a = 3;
40+
>a : Symbol(a, Decl(decoratorChecksFunctionBodies.ts, 17, 11))
41+
42+
func(a);
43+
>func : Symbol(func, Decl(decoratorChecksFunctionBodies.ts, 0, 0))
44+
>a : Symbol(a, Decl(decoratorChecksFunctionBodies.ts, 17, 11))
45+
46+
return x;
47+
>x : Symbol(x, Decl(decoratorChecksFunctionBodies.ts, 16, 7))
48+
49+
})
50+
m() {
51+
>m : Symbol(A1.m, Decl(decoratorChecksFunctionBodies.ts, 15, 18))
52+
53+
}
54+
}
55+

tests/baselines/reference/decoratorChecksFunctionBodies.types

+30
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,33 @@ class A {
3232

3333
}
3434
}
35+
36+
const A1 = class {
37+
>A1 : typeof A1
38+
>class { @((x, p) => { var a = 3; func(a); return x; }) m() { }} : typeof A1
39+
40+
@((x, p) => {
41+
>((x, p) => { var a = 3; func(a); return x; }) : (x: any, p: any) => any
42+
>(x, p) => { var a = 3; func(a); return x; } : (x: any, p: any) => any
43+
>x : any
44+
>p : any
45+
46+
var a = 3;
47+
>a : number
48+
>3 : 3
49+
50+
func(a);
51+
>func(a) : void
52+
>func : (s: string) => void
53+
>a : number
54+
55+
return x;
56+
>x : any
57+
58+
})
59+
m() {
60+
>m : () => void
61+
62+
}
63+
}
64+

tests/baselines/reference/decoratorInstantiateModulesInFunctionBodies.js

+26-7
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,20 @@ function filter(handler: any) {
1313
};
1414
}
1515

16-
class Wat {
16+
class A {
1717
@filter(() => test == 'abc')
1818
static whatever() {
1919
// ...
2020
}
21-
}
21+
}
22+
23+
const A1 = class {
24+
@filter(() => test == 'abc')
25+
static whatever() {
26+
// ...
27+
}
28+
}
29+
2230

2331
//// [a.js]
2432
"use strict";
@@ -34,21 +42,32 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
3442
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;
3543
return c > 3 && r && Object.defineProperty(target, key, r), r;
3644
};
45+
var _a;
3746
Object.defineProperty(exports, "__esModule", { value: true });
3847
var a_1 = require("./a");
3948
function filter(handler) {
4049
return function (target, propertyKey) {
4150
// ...
4251
};
4352
}
44-
var Wat = /** @class */ (function () {
45-
function Wat() {
53+
var A = /** @class */ (function () {
54+
function A() {
4655
}
47-
Wat.whatever = function () {
56+
A.whatever = function () {
4857
// ...
4958
};
5059
__decorate([
5160
filter(function () { return a_1.test == 'abc'; })
52-
], Wat, "whatever", null);
53-
return Wat;
61+
], A, "whatever", null);
62+
return A;
5463
}());
64+
var A1 = (_a = /** @class */ (function () {
65+
function class_1() {
66+
}
67+
class_1.whatever = function () {
68+
// ...
69+
};
70+
return class_1;
71+
}()), __decorate([
72+
filter(function () { return a_1.test == 'abc'; })
73+
], _a, "whatever", null), _a);

0 commit comments

Comments
 (0)