diff --git a/computed.txt b/computed.txt new file mode 100644 index 00000000000..a8869ad7d9d --- /dev/null +++ b/computed.txt @@ -0,0 +1,12 @@ +Parse computed expressions and add tests +Disallow computed expressions in class instance properties +Disallow computed expressions in object literal properties, methods, or accessors +Disallow in interfaces + +Emit computed properties for classes and object literals +Discuss down level support for computed properties + +Tests that need to move: +tests\cases\conformance\parser\ecmascript5\IndexSignatures\parserIndexSignature4.ts +tests\cases\conformance\parser\ecmascript5\IndexSignatures\parserIndexSignature5.ts +tests\cases\conformance\parser\ecmascript5\IndexSignatures\parserIndexSignature11.ts \ No newline at end of file diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4dd85715852..0ba246b62e6 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1242,30 +1242,41 @@ module ts { return createIdentifier(token >= SyntaxKind.Identifier); } - function isPropertyName(): boolean { + function isLiteralPropertyName(): boolean { return token >= SyntaxKind.Identifier || token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral; } - function parsePropertyName(): Identifier { + function parsePropertyName(): DeclarationName { if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral) { return parseLiteralNode(/*internName:*/ true); } + if (token === SyntaxKind.OpenBracketToken) { + return parseComputedPropertyName(); + } return parseIdentifierName(); } + function parseComputedPropertyName(): ComputedPropertyName { + var node = createNode(SyntaxKind.ComputedPropertyName); + parseExpected(SyntaxKind.OpenBracketToken); + node.expression = parseAssignmentExpression(/*noIn*/ false); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(node); + } + function parseContextualModifier(t: SyntaxKind): boolean { return token === t && tryParse(() => { nextToken(); - return token === SyntaxKind.OpenBracketToken || isPropertyName(); + return token === SyntaxKind.OpenBracketToken || isLiteralPropertyName(); }); } function parseAnyContextualModifier(): boolean { return isModifier(token) && tryParse(() => { nextToken(); - return token === SyntaxKind.OpenBracketToken || token === SyntaxKind.AsteriskToken || isPropertyName(); + return token === SyntaxKind.OpenBracketToken || isPropertyName(); }); } @@ -1285,9 +1296,11 @@ module ts { case ParsingContext.ClassMembers: return lookAhead(isClassMemberStart); case ParsingContext.EnumMembers: - return isPropertyName(); + // 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(); case ParsingContext.ObjectLiteralMembers: - return token === SyntaxKind.AsteriskToken || isPropertyName(); + return token === SyntaxKind.OpenBracketToken || token === SyntaxKind.AsteriskToken || isLiteralPropertyName(); case ParsingContext.BaseTypeReferences: return isIdentifier() && ((token !== SyntaxKind.ExtendsKeyword && token !== SyntaxKind.ImplementsKeyword) || !lookAhead(() => (nextToken(), isIdentifier()))); case ParsingContext.VariableDeclarations: @@ -1774,6 +1787,42 @@ module ts { return finishNode(node); } + function isIndexSignature(): boolean { + if (token !== SyntaxKind.OpenBracketToken) { + return false; + } + + return lookAhead(() => { + // The only allowed sequence is: + // + // [id: + // + // However, for error recovery, we also check the following cases: + // + // [... + // [id, + // [public id + // [private id + // [protected id + // [] + // + if (nextToken() === SyntaxKind.DotDotDotToken + || token === SyntaxKind.CloseBracketToken + || token === SyntaxKind.PublicKeyword + || token === SyntaxKind.PrivateKeyword + || token === SyntaxKind.ProtectedKeyword) { + + return true; + } + + if (!isIdentifier()) { + return false; + } + + return nextToken() === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken; + }); + } + function parseIndexSignatureMember(fullStart: number, modifiers: ModifiersArray): SignatureDeclaration { var node = createNode(SyntaxKind.IndexSignature, fullStart); setModifiers(node, modifiers); @@ -1817,10 +1866,10 @@ module ts { switch (token) { case SyntaxKind.OpenParenToken: case SyntaxKind.LessThanToken: - case SyntaxKind.OpenBracketToken: + case SyntaxKind.OpenBracketToken: // Both for indexers and computed properties return true; default: - return isPropertyName() && lookAhead(() => nextToken() === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken || token === SyntaxKind.QuestionToken || + return isLiteralPropertyName() && lookAhead(() => nextToken() === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken || token === SyntaxKind.QuestionToken || token === SyntaxKind.ColonToken || canParseSemicolon()); } } @@ -1831,7 +1880,8 @@ module ts { case SyntaxKind.LessThanToken: return parseSignatureMember(SyntaxKind.CallSignature, SyntaxKind.ColonToken); case SyntaxKind.OpenBracketToken: - return parseIndexSignatureMember(scanner.getStartPos(), /*modifiers:*/ undefined); + // Indexer or computed property + return isIndexSignature() ? parseIndexSignatureMember(scanner.getStartPos(), /*modifiers:*/ undefined) : parsePropertyOrMethod(); case SyntaxKind.NewKeyword: if (lookAhead(() => nextToken() === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken)) { return parseSignatureMember(SyntaxKind.ConstructSignature, SyntaxKind.ColonToken); @@ -3368,12 +3418,12 @@ module ts { // Try to get the first property-like token following all modifiers. // This can either be an identifier or the 'get' or 'set' keywords. - if (isPropertyName()) { + if (isLiteralPropertyName()) { idToken = token; nextToken(); } - // Index signatures are class members; we can parse. + // Index signatures and computed properties are class members; we can parse. if (token === SyntaxKind.OpenBracketToken) { return true; } @@ -3442,12 +3492,15 @@ module ts { if (token === SyntaxKind.ConstructorKeyword) { return parseConstructorDeclaration(fullStart, modifiers); } - if (token >= SyntaxKind.Identifier || token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral || token === SyntaxKind.AsteriskToken) { - return parsePropertyMemberDeclaration(fullStart, modifiers); - } - if (token === SyntaxKind.OpenBracketToken) { + if (isIndexSignature()) { return parseIndexSignatureMember(fullStart, modifiers); } + // It is very important that we check this *after* checking indexers because + // the [ token can start an index signature or a computed property name + if (token >= SyntaxKind.Identifier || token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral || + token === SyntaxKind.AsteriskToken || token === SyntaxKind.OpenBracketToken) { + return parsePropertyMemberDeclaration(fullStart, modifiers); + } // 'isClassMemberStart' should have hinted not to attempt parsing. Debug.fail("Should not have attempted to parse class member declaration."); @@ -4438,8 +4491,7 @@ module ts { for (var i = 0, n = node.properties.length; i < n; i++) { var prop = node.properties[i]; - // TODO(jfreeman): continue if we have a computed property - if (prop.kind === SyntaxKind.OmittedExpression) { + if (prop.kind === SyntaxKind.OmittedExpression || p.name.kind === SyntaxKind.ComputedPropertyName) { continue; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1cfcf8036cd..108e00e48b4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -141,6 +141,7 @@ module ts { Missing, // Names QualifiedName, + ComputedPropertyName, // Signature elements TypeParameter, Parameter, @@ -632,7 +633,9 @@ module ts { } export interface EnumMember extends Declaration { - name: Identifier | LiteralExpression; + // This does include ComputedPropertyName, but the parser will give an error + // if it parses a ComputedPropertyName in an EnumMember + name: DeclarationName; initializer?: Expression; } @@ -1255,7 +1258,7 @@ module ts { export interface CommandLineOption { name: string; type: string | Map; // "string", "number", "boolean", or an object literal mapping named values to actual values - shortName?: string; // A short pneumonic for convenience - for instance, 'h' can be used in place of 'help'. + shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help'. description?: DiagnosticMessage; // The message describing what the command line switch does paramName?: DiagnosticMessage; // The name to be used for a non-boolean option's parameter. error?: DiagnosticMessage; // The error given when the argument does not fit a customized 'type'.