Skip to content

[Experiment] Enum annotation sugar #41110

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6603,6 +6603,7 @@ namespace ts {
/*decorators*/ undefined,
factory.createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0),
getInternalSymbolName(symbol, symbolName),
/*type*/ undefined,
map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => {
// TODO: Handle computed names
// I hate that to get the initialized value we need to walk back to the declarations here; but there's no
Expand Down Expand Up @@ -35696,14 +35697,14 @@ namespace ts {
nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed;
let autoValue: number | undefined = 0;
for (const member of node.members) {
const value = computeMemberValue(member, autoValue);
const value = computeMemberValue(node, member, autoValue);
getNodeLinks(member).enumMemberValue = value;
autoValue = typeof value === "number" ? value + 1 : undefined;
}
}
}

function computeMemberValue(member: EnumMember, autoValue: number | undefined) {
function computeMemberValue(node: EnumDeclaration, member: EnumMember, autoValue: number | undefined) {
if (isComputedNonLiteralName(member.name)) {
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
}
Expand All @@ -35712,9 +35713,12 @@ namespace ts {
if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) {
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
}
if (node.type && !member.initializer) {
return text as string;
}
}
if (member.initializer) {
return computeConstantValue(member);
return computeConstantValue(node, member);
}
// In ambient non-const numeric enum declarations, enum members without initializers are
// considered computed members (as opposed to having auto-incremented values).
Expand All @@ -35732,16 +35736,21 @@ namespace ts {
return undefined;
}

function computeConstantValue(member: EnumMember): string | number | undefined {
function computeConstantValue(node: EnumDeclaration, member: EnumMember): string | number | undefined {
const enumKind = getEnumKind(getSymbolOfNode(member.parent));
const isConstEnum = isEnumConst(member.parent);
const initializer = member.initializer!;
const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer);
if (value !== undefined) {
if (isConstEnum && typeof value === "number" && !isFinite(value)) {
error(initializer, isNaN(value) ?
Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN :
Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
if (typeof value === "number") {
if (node.type) {
error(initializer, Diagnostics.Cannot_use_non_string_initializer_in_enum_with_string_annotation);
}
if (isConstEnum && !isFinite(value)) {
error(initializer, isNaN(value) ?
Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN :
Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value);
}
}
}
else if (enumKind === EnumKind.Literal) {
Expand Down Expand Up @@ -35862,13 +35871,20 @@ namespace ts {
isStringLiteralLike((<ElementAccessExpression>node).argumentExpression);
}

function checkGrammarEnumTypeAnnotation(node: EnumDeclaration) {
if (node.type && node.type.originalKeywordKind !== SyntaxKind.StringKeyword) {
error(node.type, Diagnostics.Enum_annotation_is_support_string_only);
}
}

function checkEnumDeclaration(node: EnumDeclaration) {
if (!produceDiagnostics) {
return;
}

// Grammar checking
checkGrammarDecoratorsAndModifiers(node);
checkGrammarEnumTypeAnnotation(node);

checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0);
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,14 @@
"code": 2205,
"elidedInCompatabilityPyramid": true
},
"Enum annotation supports 'string' only.": {
"category": "Error",
"code": 2206
},
"Cannot use non-string initializer in enum with 'string' annotation.": {
"category": "Error",
"code": 2207
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3110,6 +3110,12 @@ namespace ts {
writeKeyword("enum");
writeSpace();
emit(node.name);
if (node.type) {
writeSpace();
writePunctuation(":");
writeSpace();
emit(node.type);
}

writeSpace();
writePunctuation("{");
Expand Down
8 changes: 6 additions & 2 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3624,6 +3624,7 @@ namespace ts {
decorators: readonly Decorator[] | undefined,
modifiers: readonly Modifier[] | undefined,
name: string | Identifier,
type: Identifier | undefined,
members: readonly EnumMember[]
) {
const node = createBaseNamedDeclaration<EnumDeclaration>(
Expand All @@ -3632,6 +3633,7 @@ namespace ts {
modifiers,
name
);
node.type = type;
node.members = createNodeArray(members);
node.transformFlags |=
propagateChildrenFlags(node.members) |
Expand All @@ -3646,12 +3648,14 @@ namespace ts {
decorators: readonly Decorator[] | undefined,
modifiers: readonly Modifier[] | undefined,
name: Identifier,
type: Identifier | undefined,
members: readonly EnumMember[]) {
return node.decorators !== decorators
|| node.modifiers !== modifiers
|| node.name !== name
|| node.type !== type
|| node.members !== members
? update(createEnumDeclaration(decorators, modifiers, name, members), node)
? update(createEnumDeclaration(decorators, modifiers, name, type, members), node)
: node;
}

Expand Down Expand Up @@ -5773,7 +5777,7 @@ namespace ts {
isClassDeclaration(node) ? updateClassDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) :
isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) :
isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.type) :
isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifiers, node.name, node.members) :
isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifiers, node.name, node.type, node.members) :
isModuleDeclaration(node) ? updateModuleDeclaration(node, node.decorators, modifiers, node.name, node.body) :
isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifiers, node.name, node.moduleReference) :
isImportDeclaration(node) ? updateImportDeclaration(node, node.decorators, modifiers, node.importClause, node.moduleSpecifier) :
Expand Down
8 changes: 7 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ namespace ts {
return visitNodes(cbNode, cbNodes, node.decorators) ||
visitNodes(cbNode, cbNodes, node.modifiers) ||
visitNode(cbNode, (<EnumDeclaration>node).name) ||
visitNode(cbNode, (<EnumDeclaration>node).type) ||
visitNodes(cbNode, cbNodes, (<EnumDeclaration>node).members);
case SyntaxKind.EnumMember:
return visitNode(cbNode, (<EnumMember>node).name) ||
Expand Down Expand Up @@ -6763,6 +6764,11 @@ namespace ts {
function parseEnumDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray<Decorator> | undefined, modifiers: NodeArray<Modifier> | undefined): EnumDeclaration {
parseExpected(SyntaxKind.EnumKeyword);
const name = parseIdentifier();
let type: Identifier | undefined;
if (parseOptionalToken(SyntaxKind.ColonToken)) {
type = parseIdentifierName();
}

let members;
if (parseExpected(SyntaxKind.OpenBraceToken)) {
members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember));
Expand All @@ -6771,7 +6777,7 @@ namespace ts {
else {
members = createMissingList<EnumMember>();
}
const node = factory.createEnumDeclaration(decorators, modifiers, name, members);
const node = factory.createEnumDeclaration(decorators, modifiers, name, type, members);
return withJSDoc(finishNode(node, pos), hasJSDoc);
}

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1435,7 +1435,7 @@ namespace ts {
return cleanup(transformVariableStatement(input));
}
case SyntaxKind.EnumDeclaration: {
return cleanup(factory.updateEnumDeclaration(input, /*decorators*/ undefined, factory.createNodeArray(ensureModifiers(input)), input.name, factory.createNodeArray(mapDefined(input.members, m => {
return cleanup(factory.updateEnumDeclaration(input, /*decorators*/ undefined, factory.createNodeArray(ensureModifiers(input)), input.name, /*type*/ undefined, factory.createNodeArray(mapDefined(input.members, m => {
if (shouldStripInternal(m)) return;
// Rewrite enum values to their constants, if available
const constValue = resolver.getConstantValue(m);
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2861,6 +2861,7 @@ namespace ts {
readonly kind: SyntaxKind.EnumDeclaration;
readonly name: Identifier;
readonly members: NodeArray<EnumMember>;
readonly type?: Identifier
}

export type ModuleName =
Expand Down Expand Up @@ -6980,8 +6981,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, type: Identifier | undefined, members: readonly EnumMember[]): EnumDeclaration;
updateEnumDeclaration(node: EnumDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, type: Identifier | undefined, members: readonly EnumMember[]): 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
1 change: 1 addition & 0 deletions src/compiler/visitorPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,7 @@ namespace ts {
nodesVisitor((<EnumDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<EnumDeclaration>node).modifiers, visitor, isModifier),
nodeVisitor((<EnumDeclaration>node).name, visitor, isIdentifier),
nodeVisitor((<EnumDeclaration>node).type, visitor, isIdentifier),
nodesVisitor((<EnumDeclaration>node).members, visitor, isEnumMember));

case SyntaxKind.ModuleDeclaration:
Expand Down
1 change: 1 addition & 0 deletions src/services/codefixes/fixAddMissingMember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ namespace ts.codefix {
enumDeclaration.decorators,
enumDeclaration.modifiers,
enumDeclaration.name,
enumDeclaration.type,
concatenate(enumDeclaration.members, singleElementArray(enumMember))
), {
leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll,
Expand Down
2 changes: 1 addition & 1 deletion src/services/refactors/moveToNewFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ namespace ts.refactor {
case SyntaxKind.ModuleDeclaration:
return factory.updateModuleDeclaration(d, d.decorators, modifiers, d.name, d.body);
case SyntaxKind.EnumDeclaration:
return factory.updateEnumDeclaration(d, d.decorators, modifiers, d.name, d.members);
return factory.updateEnumDeclaration(d, d.decorators, modifiers, d.name, d.type, d.members);
case SyntaxKind.TypeAliasDeclaration:
return factory.updateTypeAliasDeclaration(d, d.decorators, modifiers, d.name, d.typeParameters, d.type);
case SyntaxKind.InterfaceDeclaration:
Expand Down
9 changes: 5 additions & 4 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1564,6 +1564,7 @@ declare namespace ts {
readonly kind: SyntaxKind.EnumDeclaration;
readonly name: Identifier;
readonly members: NodeArray<EnumMember>;
readonly type?: Identifier;
}
export type ModuleName = Identifier | StringLiteral;
export type ModuleBody = NamespaceBody | JSDocNamespaceBody;
Expand Down Expand Up @@ -3396,8 +3397,8 @@ declare 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, type: Identifier | undefined, members: readonly EnumMember[]): EnumDeclaration;
updateEnumDeclaration(node: EnumDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, type: Identifier | undefined, members: readonly EnumMember[]): 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 @@ -10466,9 +10467,9 @@ declare namespace ts {
/** @deprecated Use `factory.updateTypeAliasDeclaration` or the factory supplied by your transformation context instead. */
const updateTypeAliasDeclaration: (node: TypeAliasDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, typeParameters: readonly TypeParameterDeclaration[] | undefined, type: TypeNode) => TypeAliasDeclaration;
/** @deprecated Use `factory.createEnumDeclaration` or the factory supplied by your transformation context instead. */
const createEnumDeclaration: (decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, members: readonly EnumMember[]) => EnumDeclaration;
const createEnumDeclaration: (decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: string | Identifier, type: Identifier | undefined, members: readonly EnumMember[]) => EnumDeclaration;
/** @deprecated Use `factory.updateEnumDeclaration` or the factory supplied by your transformation context instead. */
const updateEnumDeclaration: (node: EnumDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, members: readonly EnumMember[]) => EnumDeclaration;
const updateEnumDeclaration: (node: EnumDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: Identifier, type: Identifier | undefined, members: readonly EnumMember[]) => EnumDeclaration;
/** @deprecated Use `factory.createModuleDeclaration` or the factory supplied by your transformation context instead. */
const createModuleDeclaration: (decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, name: ModuleName, body: Identifier | ModuleBlock | NamespaceDeclaration | JSDocNamespaceDeclaration | undefined, flags?: NodeFlags | undefined) => ModuleDeclaration;
/** @deprecated Use `factory.updateModuleDeclaration` or the factory supplied by your transformation context instead. */
Expand Down
Loading