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 support of spread enum member #40998

Closed
wants to merge 21 commits into from
Closed
172 changes: 155 additions & 17 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

31 changes: 30 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,35 @@
"category": "Error",
"code": 1391
},
"Spread enum member cannot reference to const enum without 'preserveConstEnums' flag.": {
"category": "Error",
"code": 1392
},
"Spread enum member can only reference to literal enum reference.": {
"category": "Error",
"code": 1393
},
"Enum expected.": {
"category": "Error",
"code": 1394
},
"Duplicated enum member referenced by spread enum member.": {
"category": "Error",
"code": 1395
},
"Spread enum member cannot reference to non-const enum inside const enum declaration without 'preserveConstEnums' flag.": {
"category": "Error",
"code": 1396
},
"Spread enum member has overlapped on '{0}'.": {
"category": "Error",
"code": 1397
},
"Spread enum member cannot reference to itself.": {
"category": "Error",
"code": 1398
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
"code": 2200
Expand Down Expand Up @@ -1913,7 +1942,7 @@
"category": "Error",
"code": 2474
},
"'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.": {
"'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query or spread enum member.": {
"category": "Error",
"code": 2475
},
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,8 @@ namespace ts {
// Enum
case SyntaxKind.EnumMember:
return emitEnumMember(<EnumMember>node);
case SyntaxKind.SpreadEnumMember:
return emitSpreadEnumMember(<SpreadEnumMember>node);

// JSDoc nodes (only used in codefixes currently)
case SyntaxKind.JSDocParameterTag:
Expand Down Expand Up @@ -3501,6 +3503,11 @@ namespace ts {
emitInitializer(node.initializer, node.name.end, node);
}

function emitSpreadEnumMember(node: SpreadEnumMember) {
emit(node.dotDotDotToken);
emitEntityName(node.name);
}

//
// JSDoc
//
Expand Down
26 changes: 24 additions & 2 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ namespace ts {
updateSpreadAssignment,
createEnumMember,
updateEnumMember,
createSpreadEnumMember,
updateSpreadEnumMember,
createSourceFile,
updateSourceFile,
createBundle,
Expand Down Expand Up @@ -3624,7 +3626,7 @@ namespace ts {
decorators: readonly Decorator[] | undefined,
modifiers: readonly Modifier[] | undefined,
name: string | Identifier,
members: readonly EnumMember[]
members: readonly EnumMemberLike[]
) {
const node = createBaseNamedDeclaration<EnumDeclaration>(
SyntaxKind.EnumDeclaration,
Expand All @@ -3646,7 +3648,7 @@ namespace ts {
decorators: readonly Decorator[] | undefined,
modifiers: readonly Modifier[] | undefined,
name: Identifier,
members: readonly EnumMember[]) {
members: readonly EnumMemberLike[]) {
return node.decorators !== decorators
|| node.modifiers !== modifiers
|| node.name !== name
Expand Down Expand Up @@ -4848,6 +4850,26 @@ namespace ts {
: node;
}

// @api
function createSpreadEnumMember(dotDotDotToken: DotDotDotToken, name: EntityName) {
const node = createBaseNode<SpreadEnumMember>(SyntaxKind.SpreadEnumMember);
node.dotDotDotToken = dotDotDotToken;
node.name = name;
node.transformFlags |=
propagateChildFlags(node.dotDotDotToken) |
propagateChildFlags(node.name) |
TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateSpreadEnumMember(node: SpreadEnumMember, dotDotDotToken: DotDotDotToken, name: EntityName) {
return node.dotDotDotToken !== dotDotDotToken
|| node.name !== name
? update(createSpreadEnumMember(dotDotDotToken, name), node)
: node;
}

//
// Top-level nodes
//
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,10 @@ namespace ts {
return node.kind === SyntaxKind.EnumMember;
}

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

// Unparsed

// TODO(rbuckton): isUnparsedPrologue
Expand Down
17 changes: 13 additions & 4 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,9 @@ namespace ts {
case SyntaxKind.EnumMember:
return visitNode(cbNode, (<EnumMember>node).name) ||
visitNode(cbNode, (<EnumMember>node).initializer);
case SyntaxKind.SpreadEnumMember:
return visitNode(cbNode, (<SpreadEnumMember>node).dotDotDotToken) ||
visitNode(cbNode, (<EnumMemberLike>node).name);
case SyntaxKind.ModuleDeclaration:
return visitNodes(cbNode, cbNodes, node.decorators) ||
visitNodes(cbNode, cbNodes, node.modifiers) ||
Expand Down Expand Up @@ -1839,7 +1842,7 @@ namespace ts {
case ParsingContext.EnumMembers:
// Include open bracket computed properties. This technically also lets in indexers,
// which would be a candidate for improved error reporting.
return token() === SyntaxKind.OpenBracketToken || isLiteralPropertyName();
return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName();
case ParsingContext.ObjectLiteralMembers:
switch (token()) {
case SyntaxKind.OpenBracketToken:
Expand Down Expand Up @@ -6752,9 +6755,15 @@ namespace ts {
// In a non-ambient declaration, the grammar allows uninitialized members only in a
// ConstantEnumMemberSection, which starts at the beginning of an enum declaration
// or any time an integer literal initializer is encountered.
function parseEnumMember(): EnumMember {
function parseEnumMember(): EnumMemberLike {
const pos = getNodePos();
const hasJSDoc = hasPrecedingJSDocComment();
const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
if (dotDotDotToken) {
const name = parseEntityName(/*allowReservedWords*/ false);
return withJSDoc(finishNode(factory.createSpreadEnumMember(dotDotDotToken, name), pos), hasJSDoc);
}

const name = parsePropertyName();
const initializer = allowInAnd(parseInitializer);
return withJSDoc(finishNode(factory.createEnumMember(name, initializer), pos), hasJSDoc);
Expand All @@ -6763,13 +6772,13 @@ namespace ts {
function parseEnumDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray<Decorator> | undefined, modifiers: NodeArray<Modifier> | undefined): EnumDeclaration {
parseExpected(SyntaxKind.EnumKeyword);
const name = parseIdentifier();
let members;
let members: NodeArray<EnumMemberLike> | undefined;
if (parseExpected(SyntaxKind.OpenBraceToken)) {
members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember));
parseExpected(SyntaxKind.CloseBraceToken);
}
else {
members = createMissingList<EnumMember>();
members = createMissingList<EnumMemberLike>();
}
const node = factory.createEnumDeclaration(decorators, modifiers, name, members);
return withJSDoc(finishNode(node, pos), hasJSDoc);
Expand Down
10 changes: 7 additions & 3 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1437,9 +1437,13 @@ namespace ts {
case SyntaxKind.EnumDeclaration: {
return cleanup(factory.updateEnumDeclaration(input, /*decorators*/ undefined, factory.createNodeArray(ensureModifiers(input)), input.name, factory.createNodeArray(mapDefined(input.members, m => {
if (shouldStripInternal(m)) return;
// Rewrite enum values to their constants, if available
const constValue = resolver.getConstantValue(m);
return preserveJsDoc(factory.updateEnumMember(m, m.name, constValue !== undefined ? typeof constValue === "string" ? factory.createStringLiteral(constValue) : factory.createNumericLiteral(constValue) : undefined), m);

if (isEnumMember(m)) {
// Rewrite enum values to their constants, if available
const constValue = resolver.getConstantValue(m);
return preserveJsDoc(factory.updateEnumMember(m, m.name, constValue !== undefined ? typeof constValue === "string" ? factory.createStringLiteral(constValue) : factory.createNumericLiteral(constValue) : undefined), m);
}
return m;
}))));
}
}
Expand Down
69 changes: 41 additions & 28 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2426,7 +2426,7 @@ namespace ts {

const statements: Statement[] = [];
startLexicalEnvironment();
const members = map(node.members, transformEnumMember);
const members = map(node.members, transformEnumMemberLike);
insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());
addRange(statements, members);

Expand All @@ -2442,37 +2442,50 @@ namespace ts {
*
* @param member The enum member node.
*/
function transformEnumMember(member: EnumMember): Statement {
// enums don't support computed properties
// we pass false as 'generateNameForComputedPropertyName' for a backward compatibility purposes
// old emitter always generate 'expression' part of the name as-is.
const name = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ false);
const valueExpression = transformEnumMemberDeclarationValue(member);
const innerAssignment = factory.createAssignment(
factory.createElementAccessExpression(
currentNamespaceContainerName,
name
),
valueExpression
);
const outerAssignment = valueExpression.kind === SyntaxKind.StringLiteral ?
innerAssignment :
factory.createAssignment(
function transformEnumMemberLike(member: EnumMemberLike): Statement {
if (isEnumMember(member)) {
// enums don't support computed properties
// we pass false as 'generateNameForComputedPropertyName' for a backward compatibility purposes
// old emitter always generate 'expression' part of the name as-is.
const name = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ false);
const valueExpression = transformEnumMemberDeclarationValue(member);
const innerAssignment = factory.createAssignment(
factory.createElementAccessExpression(
currentNamespaceContainerName,
innerAssignment
name
),
name
valueExpression
);
return setTextRange(
factory.createExpressionStatement(
setTextRange(
outerAssignment,
member
)
),
member
);
const outerAssignment = valueExpression.kind === SyntaxKind.StringLiteral ?
innerAssignment :
factory.createAssignment(
factory.createElementAccessExpression(
currentNamespaceContainerName,
innerAssignment
),
name
);
return setTextRange(
factory.createExpressionStatement(
setTextRange(
outerAssignment,
member
)
),
member
);
}
else {
return setTextRange(
factory.createExpressionStatement(
setTextRange(
emitHelpers().createAssignHelper([currentNamespaceContainerName, member.name as Identifier]),
member
)
),
member
);
}
}

/**
Expand Down
23 changes: 20 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ namespace ts {

// Enum
EnumMember,
SpreadEnumMember,
// Unparsed
UnparsedPrologue,
UnparsedPrepend,
Expand Down Expand Up @@ -883,6 +884,7 @@ namespace ts {
| InterfaceDeclaration
| TypeAliasDeclaration
| EnumMember
| SpreadEnumMember
| EnumDeclaration
| ModuleDeclaration
| ImportEqualsDeclaration
Expand Down Expand Up @@ -1355,6 +1357,7 @@ namespace ts {
| JsxAttribute
| ShorthandPropertyAssignment
| EnumMember
| SpreadEnumMember
| JSDocPropertyTag
| JSDocParameterTag;

Expand Down Expand Up @@ -2774,6 +2777,7 @@ namespace ts {
export type ObjectTypeDeclaration =
| ClassLikeDeclaration
| InterfaceDeclaration
| EnumDeclaration
| TypeLiteralNode
;

Expand Down Expand Up @@ -2857,10 +2861,19 @@ namespace ts {
readonly initializer?: Expression;
}

export interface SpreadEnumMember extends Declaration, JSDocContainer {
readonly kind: SyntaxKind.SpreadEnumMember;
readonly parent: EnumDeclaration;
readonly dotDotDotToken: DotDotDotToken;
readonly name: EntityName;
}

export type EnumMemberLike = EnumMember | SpreadEnumMember;

export interface EnumDeclaration extends DeclarationStatement, JSDocContainer {
readonly kind: SyntaxKind.EnumDeclaration;
readonly name: Identifier;
readonly members: NodeArray<EnumMember>;
readonly members: NodeArray<EnumMemberLike>;
}

export type ModuleName =
Expand Down Expand Up @@ -4037,6 +4050,7 @@ namespace ts {
/** Follow a *single* alias to get the immediately aliased symbol. */
/* @internal */ getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined;
getExportsOfModule(moduleSymbol: Symbol): Symbol[];
getExportsOfSymbol(symbol: Symbol): Symbol[];
/** Unlike `getExportsOfModule`, this includes properties of an `export =` value. */
/* @internal */ getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[];
getJsxIntrinsicTagNamesAt(location: Node): Symbol[];
Expand Down Expand Up @@ -4693,6 +4707,7 @@ namespace ts {
typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor
tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label
enumHasLateBoundMember?: boolean // True if enum declaration contains spread enum member
}

/* @internal */
Expand Down Expand Up @@ -6977,8 +6992,8 @@ namespace ts {
updateInterfaceDeclaration(node: InterfaceDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, heritageClauses: readonly HeritageClause[] | undefined, members: readonly TypeElement[]): InterfaceDeclaration;
createTypeAliasDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, type: TypeNode): TypeAliasDeclaration;
updateTypeAliasDeclaration(node: TypeAliasDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, type: TypeNode): TypeAliasDeclaration;
createEnumDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, members: readonly EnumMember[]): EnumDeclaration;
updateEnumDeclaration(node: EnumDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, members: readonly EnumMember[]): EnumDeclaration;
createEnumDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, members: readonly EnumMemberLike[]): EnumDeclaration;
updateEnumDeclaration(node: EnumDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, members: readonly EnumMemberLike[]): EnumDeclaration;
createModuleDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined, flags?: NodeFlags): ModuleDeclaration;
updateModuleDeclaration(node: ModuleDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: ModuleBody | undefined): ModuleDeclaration;
createModuleBlock(statements: readonly Statement[]): ModuleBlock;
Expand Down Expand Up @@ -7144,6 +7159,8 @@ namespace ts {

createEnumMember(name: string | PropertyName, initializer?: Expression): EnumMember;
updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined): EnumMember;
createSpreadEnumMember(dotDotDotToken: DotDotDotToken, name: EntityName): SpreadEnumMember;
updateSpreadEnumMember(node: SpreadEnumMember, dotDotDotToken: DotDotDotToken, name: EntityName): SpreadEnumMember;

//
// Top-level nodes
Expand Down
Loading