From e1d5bdffd7531f32954ba62af159ba31ce9ca488 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 26 Sep 2016 12:46:39 -0700 Subject: [PATCH] String and numeric literal initializes in ambient const declarations --- src/compiler/checker.ts | 47 ++++++++++++++++++++++++---- src/compiler/declarationEmitter.ts | 4 +++ src/compiler/diagnosticMessages.json | 4 +++ src/compiler/types.ts | 2 ++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 092ef8e4e6b..30a4074fd6b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2015,6 +2015,10 @@ namespace ts { isExternalModuleAugmentation(node.parent.parent); } + function literalTypeToString(type: LiteralType) { + return type.flags & TypeFlags.StringLiteral ? `"${escapeString((type).text)}"` : (type).text; + } + function getSymbolDisplayBuilder(): SymbolDisplayBuilder { function getNameOfSymbol(symbol: Symbol): string { @@ -2190,11 +2194,8 @@ namespace ts { else if (type.flags & TypeFlags.Anonymous) { writeAnonymousType(type, nextFlags); } - else if (type.flags & TypeFlags.StringLiteral) { - writer.writeStringLiteral(`"${escapeString((type).text)}"`); - } - else if (type.flags & TypeFlags.NumberLiteral) { - writer.writeStringLiteral((type).text); + else if (type.flags & TypeFlags.StringOrNumberLiteral) { + writer.writeStringLiteral(literalTypeToString(type)); } else { // Should never get here @@ -19015,6 +19016,19 @@ namespace ts { return undefined; } + function isLiteralConstDeclaration(node: VariableDeclaration): boolean { + if (isConst(node)) { + const type = getTypeOfSymbol(getSymbolOfNode(node)); + return !!(type.flags & TypeFlags.StringOrNumberLiteral && type.flags & TypeFlags.FreshLiteral); + } + return false; + } + + function writeLiteralConstValue(node: VariableDeclaration, writer: SymbolWriter) { + const type = getTypeOfSymbol(getSymbolOfNode(node)); + writer.writeStringLiteral(literalTypeToString(type)); + } + function createResolver(): EmitResolver { // this variable and functions that use it are deliberately moved here from the outer scope // to avoid scope pollution @@ -19059,7 +19073,9 @@ namespace ts { isArgumentsLocalBinding, getExternalModuleFileFromDeclaration, getTypeReferenceDirectivesForEntityName, - getTypeReferenceDirectivesForSymbol + getTypeReferenceDirectivesForSymbol, + isLiteralConstDeclaration, + writeLiteralConstValue }; // defined here to avoid outer scope pollution @@ -20205,10 +20221,29 @@ namespace ts { } } + function isStringOrNumberLiteralExpression(expr: Expression) { + return expr.kind === SyntaxKind.StringLiteral || expr.kind === SyntaxKind.NumericLiteral || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && + (expr).operand.kind === SyntaxKind.NumericLiteral; + } + function checkGrammarVariableDeclaration(node: VariableDeclaration) { if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { if (isInAmbientContext(node)) { if (node.initializer) { + if (isConst(node) && !node.type) { + if (!isStringOrNumberLiteralExpression(node.initializer)) { + return grammarErrorOnNode(node.initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal); + } + } + else { + // Error on equals token which immediate precedes the initializer + const equalsTokenLength = "=".length; + return grammarErrorAtPos(getSourceFileOfNode(node), node.initializer.pos - equalsTokenLength, + equalsTokenLength, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); + } + } + if (node.initializer && !(isConst(node) && isStringOrNumberLiteralExpression(node.initializer))) { // Error on equals token which immediate precedes the initializer const equalsTokenLength = "=".length; return grammarErrorAtPos(getSourceFileOfNode(node), node.initializer.pos - equalsTokenLength, diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index a015ad79b97..dfca14bd0d1 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -1142,6 +1142,10 @@ namespace ts { if ((node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertySignature) && node.parent.kind === SyntaxKind.TypeLiteral) { emitTypeOfVariableDeclarationFromTypeLiteral(node); } + else if (resolver.isLiteralConstDeclaration(node)) { + write(" = "); + resolver.writeLiteralConstValue(node, writer); + } else if (!hasModifier(node, ModifierFlags.Private)) { writeTypeOfDeclaration(node, node.type, getVariableDeclarationTypeVisibilityError); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index be4ee2fa07d..11eb12bb2b4 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -819,6 +819,10 @@ "category": "Error", "code": 1253 }, + "A 'const' initializer in an ambient context must be a string or numeric literal.": { + "category": "Error", + "code": 1254 + }, "'with' statements are not allowed in an async function block.": { "category": "Error", "code": 1300 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8b5b2f1f25f..c69d2aee3f1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2156,6 +2156,8 @@ namespace ts { getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): SourceFile; getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): string[]; getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[]; + isLiteralConstDeclaration(node: VariableDeclaration): boolean; + writeLiteralConstValue(node: VariableDeclaration, writer: SymbolWriter): void; } export const enum SymbolFlags {