Skip to content

Commit 6c9792a

Browse files
authored
Decorators normative changes (#52582)
1 parent 0ef9e8e commit 6c9792a

File tree

114 files changed

+1891
-1044
lines changed

Some content is hidden

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

114 files changed

+1891
-1044
lines changed

src/compiler/checker.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46445,6 +46445,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4644546445
let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastOverride: Node | undefined, firstDecorator: Decorator | undefined;
4644646446
let flags = ModifierFlags.None;
4644746447
let sawExportBeforeDecorators = false;
46448+
// We parse decorators and modifiers in four contiguous chunks:
46449+
// [...leadingDecorators, ...leadingModifiers, ...trailingDecorators, ...trailingModifiers]. It is an error to
46450+
// have both leading and trailing decorators.
46451+
let hasLeadingDecorators = false;
4644846452
for (const modifier of (node as HasModifiers).modifiers!) {
4644946453
if (isDecorator(modifier)) {
4645046454
if (!nodeCanBeDecorated(legacyDecorators, node, node.parent, node.parent.parent)) {
@@ -46461,13 +46465,35 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4646146465
return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name);
4646246466
}
4646346467
}
46468+
46469+
// if we've seen any modifiers aside from `export`, `default`, or another decorator, then this is an invalid position
4646446470
if (flags & ~(ModifierFlags.ExportDefault | ModifierFlags.Decorator)) {
4646546471
return grammarErrorOnNode(modifier, Diagnostics.Decorators_are_not_valid_here);
4646646472
}
46473+
46474+
// if we've already seen leading decorators and leading modifiers, then trailing decorators are an invalid position
46475+
if (hasLeadingDecorators && flags & ModifierFlags.Modifier) {
46476+
Debug.assertIsDefined(firstDecorator);
46477+
const sourceFile = getSourceFileOfNode(modifier);
46478+
if (!hasParseDiagnostics(sourceFile)) {
46479+
addRelatedInfo(
46480+
error(modifier, Diagnostics.Decorators_may_not_appear_after_export_or_export_default_if_they_also_appear_before_export),
46481+
createDiagnosticForNode(firstDecorator, Diagnostics.Decorator_used_before_export_here));
46482+
return true;
46483+
}
46484+
return false;
46485+
}
46486+
4646746487
flags |= ModifierFlags.Decorator;
46468-
if (flags & ModifierFlags.Export) {
46488+
46489+
// if we have not yet seen a modifier, then these are leading decorators
46490+
if (!(flags & ModifierFlags.Modifier)) {
46491+
hasLeadingDecorators = true;
46492+
}
46493+
else if (flags & ModifierFlags.Export) {
4646946494
sawExportBeforeDecorators = true;
4647046495
}
46496+
4647146497
firstDecorator ??= modifier;
4647246498
}
4647346499
else {

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,10 @@
15891589
"category": "Error",
15901590
"code": 1485
15911591
},
1592+
"Decorator used before 'export' here.": {
1593+
"category": "Error",
1594+
"code": 1486
1595+
},
15921596

15931597
"The types of '{0}' are incompatible between these types.": {
15941598
"category": "Error",
@@ -6560,7 +6564,7 @@
65606564
"category": "Error",
65616565
"code": 8037
65626566
},
6563-
"Decorators must come after 'export' or 'export default' in JavaScript files.": {
6567+
"Decorators may not appear after 'export' or 'export default' if they also appear before 'export'.": {
65646568
"category": "Error",
65656569
"code": 8038
65666570
},

src/compiler/factory/emitHelpers.ts

Lines changed: 93 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
isComputedPropertyName,
2626
isIdentifier,
2727
memoize,
28+
ObjectLiteralElementLike,
2829
PrivateIdentifier,
2930
ScriptTarget,
3031
setEmitFlags,
@@ -250,165 +251,105 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
250251
]);
251252
}
252253

253-
// Per https://github.com/tc39/proposal-decorators/issues/494, we may need to change the emit for the `access` object
254-
// so that it does not need to be used via `.call`. The following two sections represent the options presented in
255-
// tc39/proposal-decorators#494
256-
//
257-
// === Current approach (`access.get.call(obj)`, `access.set.call(obj, value)`) ===
258-
//
259-
// function createESDecorateClassElementAccessGetMethod(elementName: ESDecorateName) {
260-
// const accessor = elementName.computed ?
261-
// factory.createElementAccessExpression(factory.createThis(), elementName.name) :
262-
// factory.createPropertyAccessExpression(factory.createThis(), elementName.name);
263-
//
264-
// return factory.createMethodDeclaration(
265-
// /*modifiers*/ undefined,
266-
// /*asteriskToken*/ undefined,
267-
// "get",
268-
// /*questionToken*/ undefined,
269-
// /*typeParameters*/ undefined,
270-
// [],
271-
// /*type*/ undefined,
272-
// factory.createBlock([factory.createReturnStatement(accessor)])
273-
// );
274-
// }
275-
//
276-
// function createESDecorateClassElementAccessSetMethod(elementName: ESDecorateName) {
277-
// const accessor = elementName.computed ?
278-
// factory.createElementAccessExpression(factory.createThis(), elementName.name) :
279-
// factory.createPropertyAccessExpression(factory.createThis(), elementName.name);
280-
//
281-
// return factory.createMethodDeclaration(
282-
// /*modifiers*/ undefined,
283-
// /*asteriskToken*/ undefined,
284-
// "set",
285-
// /*questionToken*/ undefined,
286-
// /*typeParameters*/ undefined,
287-
// [factory.createParameterDeclaration(
288-
// /*modifiers*/ undefined,
289-
// /*dotDotDotToken*/ undefined,
290-
// factory.createIdentifier("value")
291-
// )],
292-
// /*type*/ undefined,
293-
// factory.createBlock([
294-
// factory.createExpressionStatement(
295-
// factory.createAssignment(
296-
// accessor,
297-
// factory.createIdentifier("value")
298-
// )
299-
// )
300-
// ])
301-
// );
302-
// }
303-
//
304-
// function createESDecorateClassElementAccessObject(name: ESDecorateName, access: ESDecorateClassElementAccess) {
305-
// const properties: ObjectLiteralElementLike[] = [];
306-
// if (access.get) properties.push(createESDecorateClassElementAccessGetMethod(name));
307-
// if (access.set) properties.push(createESDecorateClassElementAccessSetMethod(name));
308-
// return factory.createObjectLiteralExpression(properties);
309-
// }
310-
//
311-
// === Suggested approach (`access.get(obj)`, `access.set(obj, value)`, `access.has(obj)`) ===
312-
//
313-
// function createESDecorateClassElementAccessGetMethod(elementName: ESDecorateName) {
314-
// const accessor = elementName.computed ?
315-
// factory.createElementAccessExpression(factory.createIdentifier("obj"), elementName.name) :
316-
// factory.createPropertyAccessExpression(factory.createIdentifier("obj"), elementName.name);
317-
//
318-
// return factory.createMethodDeclaration(
319-
// /*modifiers*/ undefined,
320-
// /*asteriskToken*/ undefined,
321-
// "get",
322-
// /*questionToken*/ undefined,
323-
// /*typeParameters*/ undefined,
324-
// [factory.createParameterDeclaration(
325-
// /*modifiers*/ undefined,
326-
// /*dotDotDotToken*/ undefined,
327-
// factory.createIdentifier("obj")
328-
// )],
329-
// /*type*/ undefined,
330-
// factory.createBlock([factory.createReturnStatement(accessor)])
331-
// );
332-
// }
333-
//
334-
// function createESDecorateClassElementAccessSetMethod(elementName: ESDecorateName) {
335-
// const accessor = elementName.computed ?
336-
// factory.createElementAccessExpression(factory.createIdentifier("obj"), elementName.name) :
337-
// factory.createPropertyAccessExpression(factory.createIdentifier("obj"), elementName.name);
338-
//
339-
// return factory.createMethodDeclaration(
340-
// /*modifiers*/ undefined,
341-
// /*asteriskToken*/ undefined,
342-
// "set",
343-
// /*questionToken*/ undefined,
344-
// /*typeParameters*/ undefined,
345-
// [factory.createParameterDeclaration(
346-
// /*modifiers*/ undefined,
347-
// /*dotDotDotToken*/ undefined,
348-
// factory.createIdentifier("obj")
349-
// ),
350-
// factory.createParameterDeclaration(
351-
// /*modifiers*/ undefined,
352-
// /*dotDotDotToken*/ undefined,
353-
// factory.createIdentifier("value")
354-
// )],
355-
// /*type*/ undefined,
356-
// factory.createBlock([
357-
// factory.createExpressionStatement(
358-
// factory.createAssignment(
359-
// accessor,
360-
// factory.createIdentifier("value")
361-
// )
362-
// )
363-
// ])
364-
// );
365-
// }
366-
//
367-
// function createESDecorateClassElementAccessHasMethod(elementName: ESDecorateName) {
368-
// const propertyName =
369-
// elementName.computed ? elementName.name :
370-
// isIdentifier(elementName.name) ? factory.createStringLiteralFromNode(elementName.name) :
371-
// elementName.name;
372-
//
373-
// return factory.createMethodDeclaration(
374-
// /*modifiers*/ undefined,
375-
// /*asteriskToken*/ undefined,
376-
// "has",
377-
// /*questionToken*/ undefined,
378-
// /*typeParameters*/ undefined,
379-
// [factory.createParameterDeclaration(
380-
// /*modifiers*/ undefined,
381-
// /*dotDotDotToken*/ undefined,
382-
// factory.createIdentifier("obj")
383-
// )],
384-
// /*type*/ undefined,
385-
// factory.createBlock([factory.createReturnStatement(
386-
// factory.createBinaryExpression(
387-
// propertyName,
388-
// SyntaxKind.InKeyword,
389-
// factory.createIdentifier("obj")
390-
// )
391-
// )])
392-
// );
393-
// }
394-
//
395-
// function createESDecorateClassElementAccessObject(name: ESDecorateName, access: ESDecorateClassElementAccess) {
396-
// const properties: ObjectLiteralElementLike[] = [];
397-
// if (access.get) properties.push(createESDecorateClassElementAccessGetMethod(name));
398-
// if (access.set) properties.push(createESDecorateClassElementAccessSetMethod(name));
399-
// property.push(createESDecorateClassElementAccessHasMethod(name));
400-
// return factory.createObjectLiteralExpression(properties);
401-
// }
254+
255+
function createESDecorateClassElementAccessGetMethod(elementName: ESDecorateName) {
256+
const accessor = elementName.computed ?
257+
factory.createElementAccessExpression(factory.createIdentifier("obj"), elementName.name) :
258+
factory.createPropertyAccessExpression(factory.createIdentifier("obj"), elementName.name);
259+
260+
return factory.createPropertyAssignment(
261+
"get",
262+
factory.createArrowFunction(
263+
/*modifiers*/ undefined,
264+
/*typeParameters*/ undefined,
265+
[factory.createParameterDeclaration(
266+
/*modifiers*/ undefined,
267+
/*dotDotDotToken*/ undefined,
268+
factory.createIdentifier("obj")
269+
)],
270+
/*type*/ undefined,
271+
/*equalsGreaterThanToken*/ undefined,
272+
accessor
273+
)
274+
);
275+
}
276+
277+
function createESDecorateClassElementAccessSetMethod(elementName: ESDecorateName) {
278+
const accessor = elementName.computed ?
279+
factory.createElementAccessExpression(factory.createIdentifier("obj"), elementName.name) :
280+
factory.createPropertyAccessExpression(factory.createIdentifier("obj"), elementName.name);
281+
282+
return factory.createPropertyAssignment(
283+
"set",
284+
factory.createArrowFunction(
285+
/*modifiers*/ undefined,
286+
/*typeParameters*/ undefined,
287+
[factory.createParameterDeclaration(
288+
/*modifiers*/ undefined,
289+
/*dotDotDotToken*/ undefined,
290+
factory.createIdentifier("obj")
291+
),
292+
factory.createParameterDeclaration(
293+
/*modifiers*/ undefined,
294+
/*dotDotDotToken*/ undefined,
295+
factory.createIdentifier("value")
296+
)],
297+
/*type*/ undefined,
298+
/*equalsGreaterThanToken*/ undefined,
299+
factory.createBlock([
300+
factory.createExpressionStatement(
301+
factory.createAssignment(
302+
accessor,
303+
factory.createIdentifier("value")
304+
)
305+
)
306+
])
307+
)
308+
);
309+
}
310+
311+
function createESDecorateClassElementAccessHasMethod(elementName: ESDecorateName) {
312+
const propertyName =
313+
elementName.computed ? elementName.name :
314+
isIdentifier(elementName.name) ? factory.createStringLiteralFromNode(elementName.name) :
315+
elementName.name;
316+
317+
return factory.createPropertyAssignment(
318+
"has",
319+
factory.createArrowFunction(
320+
/*modifiers*/ undefined,
321+
/*typeParameters*/ undefined,
322+
[factory.createParameterDeclaration(
323+
/*modifiers*/ undefined,
324+
/*dotDotDotToken*/ undefined,
325+
factory.createIdentifier("obj")
326+
)],
327+
/*type*/ undefined,
328+
/*equalsGreaterThanToken*/ undefined,
329+
factory.createBinaryExpression(
330+
propertyName,
331+
SyntaxKind.InKeyword,
332+
factory.createIdentifier("obj")
333+
)
334+
)
335+
);
336+
}
337+
338+
function createESDecorateClassElementAccessObject(name: ESDecorateName, access: ESDecorateClassElementAccess) {
339+
const properties: ObjectLiteralElementLike[] = [];
340+
properties.push(createESDecorateClassElementAccessHasMethod(name));
341+
if (access.get) properties.push(createESDecorateClassElementAccessGetMethod(name));
342+
if (access.set) properties.push(createESDecorateClassElementAccessSetMethod(name));
343+
return factory.createObjectLiteralExpression(properties);
344+
}
402345

403346
function createESDecorateClassElementContextObject(contextIn: ESDecorateClassElementContext) {
404347
return factory.createObjectLiteralExpression([
405348
factory.createPropertyAssignment(factory.createIdentifier("kind"), factory.createStringLiteral(contextIn.kind)),
406349
factory.createPropertyAssignment(factory.createIdentifier("name"), contextIn.name.computed ? contextIn.name.name : factory.createStringLiteralFromNode(contextIn.name.name)),
407350
factory.createPropertyAssignment(factory.createIdentifier("static"), contextIn.static ? factory.createTrue() : factory.createFalse()),
408351
factory.createPropertyAssignment(factory.createIdentifier("private"), contextIn.private ? factory.createTrue() : factory.createFalse()),
409-
410-
// Disabled, pending resolution of https://github.com/tc39/proposal-decorators/issues/494
411-
// factory.createPropertyAssignment(factory.createIdentifier("access"), createESDecorateClassElementAccessObject(contextIn.name, contextIn.access))
352+
factory.createPropertyAssignment(factory.createIdentifier("access"), createESDecorateClassElementAccessObject(contextIn.name, contextIn.access))
412353
]);
413354
}
414355

src/compiler/parser.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7710,20 +7710,36 @@ namespace Parser {
77107710
function parseModifiers(allowDecorators: boolean, permitConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray<ModifierLike> | undefined {
77117711
const pos = getNodePos();
77127712
let list: ModifierLike[] | undefined;
7713-
let modifier, hasSeenStaticModifier = false;
7713+
let decorator, modifier, hasSeenStaticModifier = false, hasLeadingModifier = false, hasTrailingDecorator = false;
7714+
7715+
// Decorators should be contiguous in a list of modifiers but can potentially appear in two places (i.e., `[...leadingDecorators, ...leadingModifiers, ...trailingDecorators, ...trailingModifiers]`).
7716+
// The leading modifiers *should* only contain `export` and `default` when trailingDecorators are present, but we'll handle errors for any other leading modifiers in the checker.
7717+
// It is illegal to have both leadingDecorators and trailingDecorators, but we will report that as a grammar check in the checker.
7718+
7719+
// parse leading decorators
7720+
if (allowDecorators && token() === SyntaxKind.AtToken) {
7721+
while (decorator = tryParseDecorator()) {
7722+
list = append(list, decorator);
7723+
}
7724+
}
7725+
7726+
// parse leading modifiers
77147727
while (modifier = tryParseModifier(hasSeenStaticModifier, permitConstAsModifier, stopOnStartOfClassStaticBlock)) {
77157728
if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStaticModifier = true;
77167729
list = append(list, modifier);
7730+
hasLeadingModifier = true;
77177731
}
77187732

7719-
// Decorators should be contiguous in a list of modifiers (i.e., `[...leadingModifiers, ...decorators, ...trailingModifiers]`).
7720-
// The leading modifiers *should* only contain `export` and `default` when decorators are present, but we'll handle errors for any other leading modifiers in the checker.
7721-
if (allowDecorators && token() === SyntaxKind.AtToken) {
7722-
let decorator;
7733+
// parse trailing decorators, but only if we parsed any leading modifiers
7734+
if (hasLeadingModifier && allowDecorators && token() === SyntaxKind.AtToken) {
77237735
while (decorator = tryParseDecorator()) {
77247736
list = append(list, decorator);
7737+
hasTrailingDecorator = true;
77257738
}
7739+
}
77267740

7741+
// parse trailing modifiers, but only if we parsed any trailing decorators
7742+
if (hasTrailingDecorator) {
77277743
while (modifier = tryParseModifier(hasSeenStaticModifier, permitConstAsModifier, stopOnStartOfClassStaticBlock)) {
77287744
if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStaticModifier = true;
77297745
list = append(list, modifier);

0 commit comments

Comments
 (0)