mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
Support string valued members in literal enums
This commit is contained in:
@@ -47,6 +47,7 @@ namespace ts {
|
||||
|
||||
let typeCount = 0;
|
||||
let symbolCount = 0;
|
||||
let enumCount = 0;
|
||||
let symbolInstantiationDepth = 0;
|
||||
|
||||
const emptyArray: any[] = [];
|
||||
@@ -210,8 +211,7 @@ namespace ts {
|
||||
const tupleTypes: GenericType[] = [];
|
||||
const unionTypes = createMap<UnionType>();
|
||||
const intersectionTypes = createMap<IntersectionType>();
|
||||
const stringLiteralTypes = createMap<LiteralType>();
|
||||
const numericLiteralTypes = createMap<LiteralType>();
|
||||
const literalTypes = createMap<LiteralType>();
|
||||
const indexedAccessTypes = createMap<IndexedAccessType>();
|
||||
const evolvingArrayTypes: EvolvingArrayType[] = [];
|
||||
|
||||
@@ -4904,34 +4904,36 @@ namespace ts {
|
||||
return links.declaredType;
|
||||
}
|
||||
|
||||
function isLiteralEnumMember(symbol: Symbol, member: EnumMember) {
|
||||
function isLiteralEnumMember(member: EnumMember) {
|
||||
const expr = member.initializer;
|
||||
if (!expr) {
|
||||
return !isInAmbientContext(member);
|
||||
}
|
||||
return expr.kind === SyntaxKind.NumericLiteral ||
|
||||
return expr.kind === SyntaxKind.StringLiteral || expr.kind === SyntaxKind.NumericLiteral ||
|
||||
expr.kind === SyntaxKind.PrefixUnaryExpression && (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
|
||||
(<PrefixUnaryExpression>expr).operand.kind === SyntaxKind.NumericLiteral ||
|
||||
expr.kind === SyntaxKind.Identifier && !!symbol.exports.get((<Identifier>expr).text);
|
||||
expr.kind === SyntaxKind.Identifier && (nodeIsMissing(expr) || !!getSymbolOfNode(member.parent).exports.get((<Identifier>expr).text));
|
||||
}
|
||||
|
||||
function enumHasLiteralMembers(symbol: Symbol) {
|
||||
function getEnumKind(symbol: Symbol): EnumKind {
|
||||
const links = getSymbolLinks(symbol);
|
||||
if (links.enumKind !== undefined) {
|
||||
return links.enumKind;
|
||||
}
|
||||
let hasNonLiteralMember = false;
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (declaration.kind === SyntaxKind.EnumDeclaration) {
|
||||
for (const member of (<EnumDeclaration>declaration).members) {
|
||||
if (!isLiteralEnumMember(symbol, member)) {
|
||||
return false;
|
||||
if (member.initializer && member.initializer.kind === SyntaxKind.StringLiteral) {
|
||||
return links.enumKind = EnumKind.Literal;
|
||||
}
|
||||
if (!isLiteralEnumMember(member)) {
|
||||
hasNonLiteralMember = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function createEnumLiteralType(symbol: Symbol, value: number) {
|
||||
const type = createLiteralType(TypeFlags.NumberLiteral | TypeFlags.EnumLiteral, value);
|
||||
type.symbol = symbol;
|
||||
return type;
|
||||
return links.enumKind = hasNonLiteralMember ? EnumKind.Numeric : EnumKind.Literal;
|
||||
}
|
||||
|
||||
function getBaseTypeOfEnumLiteralType(type: Type) {
|
||||
@@ -4943,17 +4945,14 @@ namespace ts {
|
||||
if (links.declaredType) {
|
||||
return links.declaredType;
|
||||
}
|
||||
if (enumHasLiteralMembers(symbol)) {
|
||||
if (getEnumKind(symbol) === EnumKind.Literal) {
|
||||
enumCount++;
|
||||
const memberTypeList: Type[] = [];
|
||||
const memberTypes: LiteralType[] = [];
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (declaration.kind === SyntaxKind.EnumDeclaration) {
|
||||
computeEnumMemberValues(<EnumDeclaration>declaration);
|
||||
for (const member of (<EnumDeclaration>declaration).members) {
|
||||
const memberSymbol = getSymbolOfNode(member);
|
||||
const value = getEnumMemberValue(member);
|
||||
if (!memberTypes[value]) {
|
||||
const memberType = memberTypes[value] = createEnumLiteralType(memberSymbol, value);
|
||||
const memberType = getLiteralType(getEnumMemberValue(member), enumCount, getSymbolOfNode(member));
|
||||
if (!contains(memberTypeList, memberType)) {
|
||||
memberTypeList.push(memberType);
|
||||
}
|
||||
}
|
||||
@@ -4963,7 +4962,7 @@ namespace ts {
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (declaration.kind === SyntaxKind.EnumDeclaration) {
|
||||
for (const member of (<EnumDeclaration>declaration).members) {
|
||||
getSymbolLinks(getSymbolOfNode(member)).declaredType = memberTypes[getEnumMemberValue(member)];
|
||||
getSymbolLinks(getSymbolOfNode(member)).declaredType = getLiteralType(getEnumMemberValue(member), enumCount, getSymbolOfNode(member));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7517,8 +7516,9 @@ namespace ts {
|
||||
return prop.flags & SymbolFlags.Method && find(prop.declarations, decl => isClassLike(decl.parent));
|
||||
}
|
||||
|
||||
function createLiteralType(flags: TypeFlags, value: string | number) {
|
||||
function createLiteralType(flags: TypeFlags, value: string | number, symbol: Symbol) {
|
||||
const type = <LiteralType>createType(flags);
|
||||
type.symbol = symbol;
|
||||
type.value = value;
|
||||
return type;
|
||||
}
|
||||
@@ -7526,7 +7526,7 @@ namespace ts {
|
||||
function getFreshTypeOfLiteralType(type: Type) {
|
||||
if (type.flags & TypeFlags.StringOrNumberLiteral && !(type.flags & TypeFlags.FreshLiteral)) {
|
||||
if (!(<LiteralType>type).freshType) {
|
||||
const freshType = <LiteralType>createLiteralType(type.flags | TypeFlags.FreshLiteral, (<LiteralType>type).value);
|
||||
const freshType = <LiteralType>createLiteralType(type.flags | TypeFlags.FreshLiteral, (<LiteralType>type).value, (<LiteralType>type).symbol);
|
||||
freshType.regularType = <LiteralType>type;
|
||||
(<LiteralType>type).freshType = freshType;
|
||||
}
|
||||
@@ -7539,12 +7539,17 @@ namespace ts {
|
||||
return type.flags & TypeFlags.StringOrNumberLiteral && type.flags & TypeFlags.FreshLiteral ? (<LiteralType>type).regularType : type;
|
||||
}
|
||||
|
||||
function getLiteralType(value: string | number) {
|
||||
const map = typeof value === "number" ? numericLiteralTypes : stringLiteralTypes;
|
||||
const text = "" + value;
|
||||
let type = map.get(text);
|
||||
function getLiteralType(value: string | number, enumId?: number, symbol?: Symbol) {
|
||||
// We store all literal types in a single map with keys of the form '#NNN' and '@SSS',
|
||||
// where NNN is the text representation of a numeric literal and SSS are the characters
|
||||
// of a string literal. For literal enum members we use 'EEE#NNN' and 'EEE@SSS', where
|
||||
// EEE is a unique id for the containing enum type.
|
||||
const qualifier = typeof value === "number" ? "#" : "@";
|
||||
const key = enumId ? enumId + qualifier + value : qualifier + value;
|
||||
let type = literalTypes.get(key);
|
||||
if (!type) {
|
||||
map.set(text, type = createLiteralType(typeof value === "number" ? TypeFlags.NumberLiteral : TypeFlags.StringLiteral, value));
|
||||
const flags = (typeof value === "number" ? TypeFlags.NumberLiteral : TypeFlags.StringLiteral) | (enumId ? TypeFlags.EnumLiteral : 0);
|
||||
literalTypes.set(key, type = createLiteralType(flags, value, symbol));
|
||||
}
|
||||
return type;
|
||||
}
|
||||
@@ -20716,17 +20721,22 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function computeConstantValue(member: EnumMember): number {
|
||||
function computeConstantValue(member: EnumMember): string | number {
|
||||
const enumKind = getEnumKind(getSymbolOfNode(member.parent));
|
||||
const isConstEnum = isConst(member.parent);
|
||||
const initializer = member.initializer;
|
||||
const value = evaluate(member.initializer);
|
||||
const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer);
|
||||
if (value !== undefined) {
|
||||
if (isConstEnum && !isFinite(value)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
else if (enumKind === EnumKind.Literal) {
|
||||
error(initializer, Diagnostics.Computed_values_are_not_permitted_in_an_enum_with_string_valued_members);
|
||||
return 0;
|
||||
}
|
||||
else if (isConstEnum) {
|
||||
error(initializer, Diagnostics.In_const_enum_declarations_member_initializer_must_be_constant_expression);
|
||||
}
|
||||
@@ -20739,7 +20749,7 @@ namespace ts {
|
||||
}
|
||||
return value;
|
||||
|
||||
function evaluate(expr: Expression): number {
|
||||
function evaluate(expr: Expression): string | number {
|
||||
switch (expr.kind) {
|
||||
case SyntaxKind.PrefixUnaryExpression:
|
||||
const value = evaluate((<PrefixUnaryExpression>expr).operand);
|
||||
@@ -20770,13 +20780,15 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.StringLiteral:
|
||||
return (<StringLiteral>expr).text;
|
||||
case SyntaxKind.NumericLiteral:
|
||||
checkGrammarNumericLiteral(<NumericLiteral>expr);
|
||||
return +(<NumericLiteral>expr).text;
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
return evaluate((<ParenthesizedExpression>expr).expression);
|
||||
case SyntaxKind.Identifier:
|
||||
return evaluateEnumMember(expr, getSymbolOfNode(member.parent), (<Identifier>expr).text);
|
||||
return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), (<Identifier>expr).text);
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
if (isConstantMemberAccess(expr)) {
|
||||
@@ -22476,7 +22488,7 @@ namespace ts {
|
||||
return getNodeLinks(node).flags;
|
||||
}
|
||||
|
||||
function getEnumMemberValue(node: EnumMember): number {
|
||||
function getEnumMemberValue(node: EnumMember): string | number {
|
||||
computeEnumMemberValues(<EnumDeclaration>node.parent);
|
||||
return getNodeLinks(node).enumMemberValue;
|
||||
}
|
||||
@@ -22491,7 +22503,7 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number {
|
||||
function getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number {
|
||||
if (node.kind === SyntaxKind.EnumMember) {
|
||||
return getEnumMemberValue(<EnumMember>node);
|
||||
}
|
||||
|
||||
@@ -1843,6 +1843,10 @@
|
||||
"category": "Error",
|
||||
"code": 2550
|
||||
},
|
||||
"Computed values are not permitted in an enum with string valued members.": {
|
||||
"category": "Error",
|
||||
"code": 2551
|
||||
},
|
||||
"JSX element attributes type '{0}' may not be a union type.": {
|
||||
"category": "Error",
|
||||
"code": 2600
|
||||
|
||||
@@ -1128,7 +1128,7 @@ namespace ts {
|
||||
// check if constant enum value is integer
|
||||
const constantValue = getConstantValue(expression);
|
||||
// isFinite handles cases when constantValue is undefined
|
||||
return isFinite(constantValue)
|
||||
return typeof constantValue === "number" && isFinite(constantValue)
|
||||
&& Math.floor(constantValue) === constantValue
|
||||
&& printerOptions.removeComments;
|
||||
}
|
||||
|
||||
@@ -2248,7 +2248,7 @@ namespace ts {
|
||||
/**
|
||||
* Sets the constant value to emit for an expression.
|
||||
*/
|
||||
export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: number) {
|
||||
export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number) {
|
||||
const emitNode = getOrCreateEmitNode(node);
|
||||
emitNode.constantValue = value;
|
||||
return node;
|
||||
|
||||
@@ -2498,22 +2498,27 @@ namespace ts {
|
||||
// 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 = createAssignment(
|
||||
createElementAccess(
|
||||
currentNamespaceContainerName,
|
||||
name
|
||||
),
|
||||
valueExpression
|
||||
);
|
||||
const outerAssignment = valueExpression.kind === SyntaxKind.StringLiteral ?
|
||||
innerAssignment :
|
||||
createAssignment(
|
||||
createElementAccess(
|
||||
currentNamespaceContainerName,
|
||||
innerAssignment
|
||||
),
|
||||
name
|
||||
);
|
||||
return setTextRange(
|
||||
createStatement(
|
||||
setTextRange(
|
||||
createAssignment(
|
||||
createElementAccess(
|
||||
currentNamespaceContainerName,
|
||||
createAssignment(
|
||||
createElementAccess(
|
||||
currentNamespaceContainerName,
|
||||
name
|
||||
),
|
||||
transformEnumMemberDeclarationValue(member)
|
||||
)
|
||||
),
|
||||
name
|
||||
),
|
||||
outerAssignment,
|
||||
member
|
||||
)
|
||||
),
|
||||
@@ -3351,7 +3356,7 @@ namespace ts {
|
||||
return node;
|
||||
}
|
||||
|
||||
function tryGetConstEnumValue(node: Node): number {
|
||||
function tryGetConstEnumValue(node: Node): string | number {
|
||||
if (compilerOptions.isolatedModules) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -2528,7 +2528,7 @@ namespace ts {
|
||||
isUnknownSymbol(symbol: Symbol): boolean;
|
||||
/* @internal */ getMergedSymbol(symbol: Symbol): Symbol;
|
||||
|
||||
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
|
||||
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number;
|
||||
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean;
|
||||
/** Follow all aliases to get the original symbol. */
|
||||
getAliasedSymbol(symbol: Symbol): Symbol;
|
||||
@@ -2731,7 +2731,7 @@ namespace ts {
|
||||
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult;
|
||||
isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult;
|
||||
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
|
||||
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
|
||||
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number;
|
||||
getReferencedValueDeclaration(reference: Identifier): Declaration;
|
||||
getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind;
|
||||
isOptionalParameter(node: ParameterDeclaration): boolean;
|
||||
@@ -2869,6 +2869,13 @@ namespace ts {
|
||||
isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration
|
||||
bindingElement?: BindingElement; // Binding element associated with property symbol
|
||||
exportsSomeValue?: boolean; // True if module exports some value (not just types)
|
||||
enumKind?: EnumKind; // Enum declaration classification
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum EnumKind {
|
||||
Numeric, // Numeric enum (each member has a TypeFlags.Enum type)
|
||||
Literal // Literal enum (each member has a TypeFlags.EnumLiteral type)
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -2941,7 +2948,7 @@ namespace ts {
|
||||
resolvedSymbol?: Symbol; // Cached name resolution result
|
||||
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
|
||||
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
|
||||
enumMemberValue?: number; // Constant value of enum member
|
||||
enumMemberValue?: string | number; // Constant value of enum member
|
||||
isVisible?: boolean; // Is this node visible
|
||||
containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference
|
||||
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
|
||||
@@ -3918,7 +3925,7 @@ namespace ts {
|
||||
commentRange?: TextRange; // The text range to use when emitting leading or trailing comments
|
||||
sourceMapRange?: TextRange; // The text range to use when emitting leading or trailing source mappings
|
||||
tokenSourceMapRanges?: TextRange[]; // The text range to use when emitting source mappings for tokens
|
||||
constantValue?: number; // The constant value of an expression
|
||||
constantValue?: string | number; // The constant value of an expression
|
||||
externalHelpersModuleName?: Identifier; // The local name for an imported helpers module
|
||||
helpers?: EmitHelper[]; // Emit helpers for the node
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace ts {
|
||||
Debug.fail(`Literal kind '${node.kind}' not accounted for.`);
|
||||
}
|
||||
|
||||
function getQuotedEscapedLiteralText(leftQuote: string, text: string, rightQuote: string) {
|
||||
export function getQuotedEscapedLiteralText(leftQuote: string, text: string, rightQuote: string) {
|
||||
return leftQuote + escapeNonAsciiCharacters(escapeString(text)) + rightQuote;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user