Parse computed property names

This commit is contained in:
Jason Freeman 2014-11-12 12:00:46 -08:00
parent 3e1b6b896c
commit dbc48d222f
3 changed files with 86 additions and 19 deletions

12
computed.txt Normal file
View file

@ -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

View file

@ -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 = <ComputedPropertyName>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 = <SignatureDeclaration>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;
}

View file

@ -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<number>; // "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'.