Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --metadataDecorator option #43542

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
551 changes: 409 additions & 142 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,22 @@ namespace ts {
description: Diagnostics.Include_modules_imported_with_json_extension
},

{
name: "metadataDecorator",
type: "string",
affectsEmit: true,
category: Diagnostics.Advanced_Options,
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
description: Diagnostics.Specify_the_name_of_the_metadata_decorator_function_to_use_when_emitDecoratorMetadata_is_set
},
{
name: "metadataDecoratorImportSource",
type: "string",
affectsEmit: true,
affectsModuleResolution: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Specify_the_module_specifier_to_be_used_to_import_the_metadata_decorator_provided_by_metadataDecorator
},

{
name: "out",
type: "string",
Expand Down
39 changes: 34 additions & 5 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,19 @@
"category": "Error",
"code": 1432
},
"Unable to resolve signature of implicit decorator '{0}' when called as an expression.": {
"category": "Error",
"code": 1433
},
"Unable to resolve signature of implicit decorator '{0}' from module '{1}' when called as an expression.": {
"category": "Error",
"code": 1434
},
"Unable to import implicit decorator '{0}' from module '{1}' as this file is not a module.": {
"category": "Error",
"code": 1435
},


"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down Expand Up @@ -3312,6 +3325,18 @@
"category": "Error",
"code": 2809
},
"Namespace '{0}' from module '{1}' has no exported member '{2}'.": {
"category": "Error",
"code": 2810
},
"'{0}' from module '{1}' has no exported member named '{2}'. Did you mean '{3}'?": {
"category": "Error",
"code": 2811
},
"Cannot find namespace '{0}'. Did you mean '{1}?": {
"category": "Error",
"code": 2812
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down Expand Up @@ -3818,7 +3843,7 @@
"category": "Error",
"code": 5066
},
"Invalid value for 'jsxFactory'. '{0}' is not a valid identifier or qualified-name.": {
"Invalid value for '{0}'. '{1}' is not a valid identifier or qualified-name.": {
"category": "Error",
"code": 5067
},
Expand Down Expand Up @@ -4825,6 +4850,14 @@
"category": "Error",
"code": 6238
},
"Specify the name of the metadata decorator function to use when '--emitDecoratorMetadata' is set": {
"category": "Message",
"code": 6239
},
"Specify the module specifier to be used to import the metadata decorator provided by '--metadataDecorator'.": {
"category": "Message",
"code": 6240
},

"Projects to reference": {
"category": "Message",
Expand Down Expand Up @@ -6442,10 +6475,6 @@
"category": "Message",
"code": 18034
},
"Invalid value for 'jsxFragmentFactory'. '{0}' is not a valid identifier or qualified-name.": {
"category": "Error",
"code": 18035
},
"Class decorators can't be used with static private identifier. Consider removing the experimental decorator.": {
"category": "Error",
"code": 18036
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/factory/emitHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ namespace ts {

// ES2017 Helpers

function createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block) {
function createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityNameOrEntityNameExpression | undefined, body: Block) {
context.requestEmitHelper(awaiterHelper);

const generatorFunc = factory.createFunctionExpression(
Expand All @@ -256,7 +256,7 @@ namespace ts {
[
hasLexicalThis ? factory.createThis() : factory.createVoidZero(),
hasLexicalArguments ? factory.createIdentifier("arguments") : factory.createVoidZero(),
promiseConstructor ? createExpressionFromEntityName(factory, promiseConstructor) : factory.createVoidZero(),
promiseConstructor ? isEntityName(promiseConstructor) ? createExpressionFromEntityName(factory, promiseConstructor) : promiseConstructor : factory.createVoidZero(),
generatorFunc
]
);
Expand Down
20 changes: 20 additions & 0 deletions src/compiler/factory/emitNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,24 @@ namespace ts {
getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines;
return node;
}

/**
* For a synthetic import, specifies the synthesized import reference so that the import can be resolved during subsequent transformations.
*
* @param node Imported identifier
* @param importReference The `ImportSpecifier` to use for the reference, or `undefined`.
*/
/* @internal */
export function setGeneratedImportReference<T extends Identifier>(node: T, importReference: ImportSpecifier | undefined) {
getOrCreateEmitNode(node).generatedImportReference = importReference;
return node;
}

/**
* For a synthetic import, gets any associated synthesized import reference.
*/
/* @internal */
export function getGeneratedImportReference(node: Identifier) {
return node.emitNode?.generatedImportReference;
}
}
12 changes: 12 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ namespace ts {
createUnparsedSyntheticReference,
createInputFiles,
createSyntheticExpression,
createSyntheticCallExpression,
createSyntaxList,
createNotEmittedStatement,
createPartiallyEmittedExpression,
Expand Down Expand Up @@ -5104,6 +5105,17 @@ namespace ts {
return node;
}

// @api
function createSyntheticCallExpression(thisArg: LeftHandSideExpression | SyntheticExpression | undefined, expression: Expression | SyntheticExpression, typeArguments: readonly TypeNode[] | undefined, argumentList: readonly Expression[], containingMessageChain?: () => DiagnosticMessageChain | undefined): SyntheticCallExpression {
const node = createBaseNode<SyntheticCallExpression>(SyntaxKind.SyntheticCallExpression);
node.thisArg = thisArg;
node.expression = expression;
node.typeArguments = typeArguments;
node.arguments = argumentList;
node.containingMessageChain = containingMessageChain;
return node;
}

// @api
function createSyntaxList(children: Node[]) {
const node = createBaseNode<SyntaxList>(SyntaxKind.SyntaxList);
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,10 @@ namespace ts {
return node.kind === SyntaxKind.CommaListExpression;
}

export function isSyntheticCallExpression(node: Node): node is SyntheticCallExpression {
return node.kind === SyntaxKind.SyntheticCallExpression;
}

// Misc

export function isTemplateSpan(node: Node): node is TemplateSpan {
Expand Down Expand Up @@ -629,7 +633,7 @@ namespace ts {
}

/* @internal */
export function isSyntheticReference(node: Node): node is SyntheticReferenceExpression {
export function isSyntheticReferenceExpression(node: Node): node is SyntheticReferenceExpression {
return node.kind === SyntaxKind.SyntheticReferenceExpression;
}

Expand Down
25 changes: 18 additions & 7 deletions src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,27 @@ namespace ts {
}
}

export function createExpressionFromEntityName(factory: NodeFactory, node: EntityName | Expression): Expression {
export function createExpressionFromEntityName(factory: NodeFactory, node: EntityName, emulateParseTree = true): EntityNameExpression {
if (isQualifiedName(node)) {
const left = createExpressionFromEntityName(factory, node.left);
// TODO(rbuckton): Does this need to be parented?
const right = setParent(setTextRange(factory.cloneNode(node.right), node.right), node.right.parent);
return setTextRange(factory.createPropertyAccessExpression(left, right), node);
const left = createExpressionFromEntityName(factory, node.left, emulateParseTree);
const right = factory.cloneNode(node.right);
if (emulateParseTree) {
setTextRange(right, node.right);
setParent(right, node.right.parent);
}
const expression = factory.createPropertyAccessExpression(left, right);
if (emulateParseTree) {
setTextRange(expression, node);
}
return expression as PropertyAccessEntityNameExpression;
}
else {
// TODO(rbuckton): Does this need to be parented?
return setParent(setTextRange(factory.cloneNode(node), node), node.parent);
const name = factory.cloneNode(node);
if (emulateParseTree) {
setTextRange(name, node);
setParent(name, node.parent);
}
return name;
}
}

Expand Down
40 changes: 36 additions & 4 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2290,6 +2290,14 @@ namespace ts {
// synthesize `import "base/jsx-runtime"` declaration
(imports ||= []).push(createSyntheticImport(jsxImport, file));
}
if (file.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax &&
options.emitDecoratorMetadata &&
options.metadataDecorator) {
const metadataDecoratorImport = options.metadataDecoratorImportSource;
if (metadataDecoratorImport) {
(imports ||= []).push(createSyntheticImport(metadataDecoratorImport, file));
}
}
}

for (const node of file.statements) {
Expand Down Expand Up @@ -3244,6 +3252,30 @@ namespace ts {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators");
}

if (options.metadataDecorator) {
if (!options.experimentalDecorators) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "metadataDecorator", "experimentalDecorators");
}
if (!options.emitDecoratorMetadata) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "metadataDecorator", "emitDecoratorMetadata");
}
if (!parseIsolatedEntityName(options.metadataDecorator, languageVersion)) {
createOptionValueDiagnostic("metadataDecorator", Diagnostics.Invalid_value_for_0_1_is_not_a_valid_identifier_or_qualified_name, "metadataDecorator", options.metadataDecorator);
}
}

if (options.metadataDecoratorImportSource) {
if (!options.experimentalDecorators) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "metadataDecorator", "experimentalDecorators");
}
if (!options.emitDecoratorMetadata) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "metadataDecorator", "emitDecoratorMetadata");
}
if (!options.metadataDecorator) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "metadataDecoratorImportSource", "metadataDecorator");
}
}

if (options.jsxFactory) {
if (options.reactNamespace) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory");
Expand All @@ -3252,7 +3284,7 @@ namespace ts {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFactory", inverseJsxOptionMap.get("" + options.jsx));
}
if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) {
createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory);
createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_0_1_is_not_a_valid_identifier_or_qualified_name, "jsxFactory", options.jsxFactory);
}
}
else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) {
Expand All @@ -3267,7 +3299,7 @@ namespace ts {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFragmentFactory", inverseJsxOptionMap.get("" + options.jsx));
}
if (!parseIsolatedEntityName(options.jsxFragmentFactory, languageVersion)) {
createOptionValueDiagnostic("jsxFragmentFactory", Diagnostics.Invalid_value_for_jsxFragmentFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFragmentFactory);
createOptionValueDiagnostic("jsxFragmentFactory", Diagnostics.Invalid_value_for_0_1_is_not_a_valid_identifier_or_qualified_name, "jsxFragmentFactory", options.jsxFragmentFactory);
}
}

Expand Down Expand Up @@ -3553,8 +3585,8 @@ namespace ts {
createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3);
}

function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0: string) {
createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0);
function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0: string, arg1?: string) {
createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0, arg1);
}

function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) {
Expand Down
14 changes: 7 additions & 7 deletions src/compiler/transformers/es2020.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ namespace ts {
switch (node.kind) {
case SyntaxKind.CallExpression: {
const updated = visitNonOptionalCallExpression(node as CallExpression, /*captureThisArg*/ false);
Debug.assertNotNode(updated, isSyntheticReference);
Debug.assertNotNode(updated, isSyntheticReferenceExpression);
return updated;
}
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
if (isOptionalChain(node)) {
const updated = visitOptionalExpression(node, /*captureThisArg*/ false, /*isDelete*/ false);
Debug.assertNotNode(updated, isSyntheticReference);
Debug.assertNotNode(updated, isSyntheticReferenceExpression);
return updated;
}
return visitEachChild(node, visitor, context);
Expand Down Expand Up @@ -59,7 +59,7 @@ namespace ts {

function visitNonOptionalParenthesizedExpression(node: ParenthesizedExpression, captureThisArg: boolean, isDelete: boolean): Expression {
const expression = visitNonOptionalExpression(node.expression, captureThisArg, isDelete);
if (isSyntheticReference(expression)) {
if (isSyntheticReferenceExpression(expression)) {
// `(a.b)` -> { expression `((_a = a).b)`, thisArg: `_a` }
// `(a[b])` -> { expression `((_a = a)[b])`, thisArg: `_a` }
return factory.createSyntheticReferenceExpression(factory.updateParenthesizedExpression(node, expression.expression), expression.thisArg);
Expand All @@ -74,7 +74,7 @@ namespace ts {
}

let expression: Expression = visitNode(node.expression, visitor, isExpression);
Debug.assertNotNode(expression, isSyntheticReference);
Debug.assertNotNode(expression, isSyntheticReferenceExpression);

let thisArg: Expression | undefined;
if (captureThisArg) {
Expand Down Expand Up @@ -102,7 +102,7 @@ namespace ts {
// capture thisArg for calls of parenthesized optional chains like `(foo?.bar)()`
const expression = visitNonOptionalParenthesizedExpression(node.expression, /*captureThisArg*/ true, /*isDelete*/ false);
const args = visitNodes(node.arguments, visitor, isExpression);
if (isSyntheticReference(expression)) {
if (isSyntheticReferenceExpression(expression)) {
return setTextRange(factory.createFunctionCallCall(expression.expression, expression.thisArg, args), node);
}
return factory.updateCallExpression(node, expression, /*typeArguments*/ undefined, args);
Expand All @@ -123,8 +123,8 @@ namespace ts {
function visitOptionalExpression(node: OptionalChain, captureThisArg: boolean, isDelete: boolean): Expression {
const { expression, chain } = flattenChain(node);
const left = visitNonOptionalExpression(expression, isCallChain(chain[0]), /*isDelete*/ false);
const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined;
let leftExpression = isSyntheticReference(left) ? left.expression : left;
const leftThisArg = isSyntheticReferenceExpression(left) ? left.thisArg : undefined;
let leftExpression = isSyntheticReferenceExpression(left) ? left.expression : left;
let capturedLeft: Expression = leftExpression;
if (!isSimpleCopiableExpression(leftExpression)) {
capturedLeft = factory.createTempVariable(hoistVariableDeclaration);
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ namespace ts {
}
const generatedName = factory.createUniqueName(`_${name}`, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel | GeneratedIdentifierFlags.AllowNameSubstitution);
const specifier = factory.createImportSpecifier(factory.createIdentifier(name), generatedName);
generatedName.generatedImportReference = specifier;
setGeneratedImportReference(generatedName, specifier);
specifierSourceImports.set(name, specifier);
return generatedName;
}
Expand Down Expand Up @@ -525,7 +525,7 @@ namespace ts {
return factory.createStringLiteral(idText(name));
}
else {
return createExpressionFromEntityName(factory, name);
return name;
}
}
}
Expand Down