From 8282a0b9ca689e362134f5e7f2ba1ead3d176864 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 17 Mar 2015 11:18:48 -0700 Subject: [PATCH] Parser updates for decorators --- src/compiler/parser.ts | 560 +++++++++++++++++++++++++++++++++++------ 1 file changed, 482 insertions(+), 78 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9eae67a29b8..57519f6584e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -64,7 +64,8 @@ module ts { case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).propertyName) || visitNode(cbNode, (node).dotDotDotToken) || visitNode(cbNode, (node).name) || @@ -76,7 +77,8 @@ module ts { case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNodes(cbNodes, (node).typeParameters) || visitNodes(cbNodes, (node).parameters) || visitNode(cbNode, (node).type); @@ -88,7 +90,8 @@ module ts { case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).asteriskToken) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).questionToken) || @@ -116,7 +119,9 @@ module ts { case SyntaxKind.ArrayBindingPattern: return visitNodes(cbNodes, (node).elements); case SyntaxKind.ArrayLiteralExpression: - return visitNodes(cbNodes, (node).elements); + return visitNode(cbNode, (node).openBracketToken) || + visitNodes(cbNodes, (node).elements) || + visitNode(cbNode, (node).closeBracketToken); case SyntaxKind.ObjectLiteralExpression: return visitNodes(cbNodes, (node).properties); case SyntaxKind.PropertyAccessExpression: @@ -125,7 +130,9 @@ module ts { visitNode(cbNode, (node).name); case SyntaxKind.ElementAccessExpression: return visitNode(cbNode, (node).expression) || - visitNode(cbNode, (node).argumentExpression); + visitNode(cbNode, (node).openBracketToken) || + visitNode(cbNode, (node).argumentExpression) || + visitNode(cbNode, (node).closeBracketToken); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return visitNode(cbNode, (node).expression) || @@ -171,7 +178,8 @@ module ts { return visitNodes(cbNodes, (node).statements) || visitNode(cbNode, (node).endOfFileToken); case SyntaxKind.VariableStatement: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).declarationList); case SyntaxKind.VariableDeclarationList: return visitNodes(cbNodes, (node).declarations); @@ -230,39 +238,49 @@ module ts { case SyntaxKind.CatchClause: return visitNode(cbNode, (node).variableDeclaration) || visitNode(cbNode, (node).block); + case SyntaxKind.Decorator: + return visitNode(cbNode, (node).atToken) || + visitNode(cbNode, (node).expression); case SyntaxKind.ClassDeclaration: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNodes(cbNodes, (node).typeParameters) || visitNodes(cbNodes, (node).heritageClauses) || visitNodes(cbNodes, (node).members); case SyntaxKind.InterfaceDeclaration: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNodes(cbNodes, (node).typeParameters) || visitNodes(cbNodes, (node).heritageClauses) || visitNodes(cbNodes, (node).members); case SyntaxKind.TypeAliasDeclaration: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).type); case SyntaxKind.EnumDeclaration: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNodes(cbNodes, (node).members); case SyntaxKind.EnumMember: return visitNode(cbNode, (node).name) || visitNode(cbNode, (node).initializer); case SyntaxKind.ModuleDeclaration: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).body); case SyntaxKind.ImportEqualsDeclaration: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || visitNode(cbNode, (node).moduleReference); case SyntaxKind.ImportDeclaration: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).importClause) || visitNode(cbNode, (node).moduleSpecifier); case SyntaxKind.ImportClause: @@ -274,7 +292,8 @@ module ts { case SyntaxKind.NamedExports: return visitNodes(cbNodes, (node).elements); case SyntaxKind.ExportDeclaration: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).exportClause) || visitNode(cbNode, (node).moduleSpecifier); case SyntaxKind.ImportSpecifier: @@ -282,7 +301,8 @@ module ts { return visitNode(cbNode, (node).propertyName) || visitNode(cbNode, (node).name); case SyntaxKind.ExportAssignment: - return visitNodes(cbNodes, node.modifiers) || + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).type); case SyntaxKind.TemplateExpression: @@ -295,6 +315,9 @@ module ts { return visitNodes(cbNodes, (node).types); case SyntaxKind.ExternalModuleReference: return visitNode(cbNode, (node).expression); + case SyntaxKind.IncompleteDeclaration: + return visitNodes(cbNodes, node.decorators) || + visitNodes(cbNodes, node.modifiers); } } @@ -329,6 +352,12 @@ module ts { Unknown } + interface SignatureResult { + typeParameters?: NodeArray; + parameters?: NodeArray; + type?: TypeNode; + } + function parsingContextErrors(context: ParsingContext): DiagnosticMessage { switch (context) { case ParsingContext.SourceElements: return Diagnostics.Declaration_or_statement_expected; @@ -1130,6 +1159,10 @@ module ts { setContextFlag(val, ParserContextFlags.GeneratorParameter); } + function setDecoratorContext(val: boolean) { + setContextFlag(val, ParserContextFlags.Decorator); + } + function allowInAnd(func: () => T): T { if (contextFlags & ParserContextFlags.DisallowIn) { setDisallowInContext(false); @@ -1178,6 +1211,30 @@ module ts { return func(); } + function doInDecoratorContext(func: () => T): T { + if (contextFlags & ParserContextFlags.Decorator) { + // no need to do anything special if we're already in the [Decorator] context. + return func(); + } + + setDecoratorContext(true); + let result = func(); + setDecoratorContext(false); + return result; + } + + function doOutsideOfDecoratorContext(func: () => T): T { + if (contextFlags & ParserContextFlags.Decorator) { + setDecoratorContext(false); + let result = func(); + setDecoratorContext(true); + return result; + } + + // no need to do anything special if we're not in the [Decorator] context. + return func(); + } + function inYieldContext() { return (contextFlags & ParserContextFlags.Yield) !== 0; } @@ -1194,6 +1251,10 @@ module ts { return (contextFlags & ParserContextFlags.DisallowIn) !== 0; } + function inDecoratorContext() { + return (contextFlags & ParserContextFlags.Decorator) !== 0; + } + function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void { let start = scanner.getTokenPos(); let length = scanner.getTextPos() - start; @@ -1386,8 +1447,12 @@ module ts { return node; } - function finishNode(node: T): T { - node.end = scanner.getStartPos(); + function finishNode(node: T, end?: number): T { + if (!(end >= 0)) { + end = scanner.getStartPos(); + } + + node.end = end; if (contextFlags) { node.parserContextFlags = contextFlags; @@ -1404,6 +1469,13 @@ module ts { return node; } + function createMissingNodeAtPosition(pos: number, kind: SyntaxKind, diagnosticMessage: DiagnosticMessage, arg0?: any): Node { + parseErrorAtPosition(pos, 0, diagnosticMessage, arg0); + let result = createNode(kind, pos); + (result).text = ""; + return finishNode(result, pos); + } + function createMissingNode(kind: SyntaxKind, reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): Node { if (reportAtCurrentPosition) { parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); @@ -2273,7 +2345,7 @@ module ts { } function isStartOfParameter(): boolean { - return token === SyntaxKind.DotDotDotToken || isIdentifierOrPattern() || isModifier(token); + return token === SyntaxKind.DotDotDotToken || isIdentifierOrPattern() || isModifier(token) || token === SyntaxKind.AtToken; } function setModifiers(node: Node, modifiers: ModifiersArray) { @@ -2285,6 +2357,7 @@ module ts { function parseParameter(): ParameterDeclaration { let node = createNode(SyntaxKind.Parameter); + node.decorators = parseDecorators(); setModifiers(node, parseModifiers()); node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); @@ -2473,9 +2546,9 @@ module ts { return token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken || token === SyntaxKind.CloseBracketToken; } - function parseIndexSignatureDeclaration(modifiers: ModifiersArray): IndexSignatureDeclaration { - let fullStart = modifiers ? modifiers.pos : scanner.getStartPos(); + function parseIndexSignatureDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): IndexSignatureDeclaration { let node = createNode(SyntaxKind.IndexSignature, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); node.parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); node.type = parseTypeAnnotation(); @@ -2552,7 +2625,7 @@ module ts { case SyntaxKind.OpenBracketToken: // Indexer or computed property return isIndexSignature() - ? parseIndexSignatureDeclaration(/*modifiers:*/ undefined) + ? parseIndexSignatureDeclaration(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers:*/ undefined) : parsePropertyOrMethodSignature(); case SyntaxKind.NewKeyword: if (lookAhead(isStartOfConstructSignature)) { @@ -2583,9 +2656,11 @@ module ts { } function parseIndexSignatureWithModifiers() { + let fullStart = scanner.getStartPos(); + let decorators = parseDecorators(); let modifiers = parseModifiers(); return isIndexSignature() - ? parseIndexSignatureDeclaration(modifiers) + ? parseIndexSignatureDeclaration(fullStart, decorators, modifiers) : undefined; } @@ -2841,7 +2916,7 @@ module ts { function isStartOfExpressionStatement(): boolean { // As per the grammar, neither '{' nor 'function' can start an expression statement. - return token !== SyntaxKind.OpenBraceToken && token !== SyntaxKind.FunctionKeyword && isStartOfExpression(); + return token !== SyntaxKind.OpenBraceToken && token !== SyntaxKind.FunctionKeyword && token !== SyntaxKind.AtToken && isStartOfExpression(); } function parseExpression(): Expression { @@ -2849,11 +2924,21 @@ module ts { // AssignmentExpression[in] // Expression[in] , AssignmentExpression[in] + // clear the decorator context when parsing Expression, as it should be unambiguous when parsing a decorator + let saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(false); + } + let expr = parseAssignmentExpressionOrHigher(); let operatorToken: Node; while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher()); } + + if (saveDecoratorContext) { + setDecoratorContext(true); + } return expr; } @@ -3500,9 +3585,11 @@ module ts { continue; } - if (parseOptional(SyntaxKind.OpenBracketToken)) { + let openBracketToken = parseOptionalToken(SyntaxKind.OpenBracketToken); + if (openBracketToken) { let indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); indexedAccess.expression = expression; + indexedAccess.openBracketToken = openBracketToken; // It's not uncommon for a user to write: "new Type[]". // Check for that common pattern and report a better error message. @@ -3514,7 +3601,7 @@ module ts { } } - parseExpected(SyntaxKind.CloseBracketToken); + indexedAccess.closeBracketToken = parseExpectedToken(SyntaxKind.CloseBracketToken, /*reportAtCurrentPosition*/false, Diagnostics._0_expected, "]"); expression = finishNode(indexedAccess); continue; } @@ -3536,7 +3623,16 @@ module ts { function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { expression = parseMemberExpressionRest(expression); - + if (inDecoratorContext() && + parsingContext === ParsingContext.ClassMembers && + (token === SyntaxKind.LessThanToken || token === SyntaxKind.OpenParenToken) && + (expression.kind === SyntaxKind.ElementAccessExpression || expression.kind === SyntaxKind.Identifier)) { + // TODO(rbuckton): switch to tryParse and reuse the parsed signature information? + let result = lookAhead(isStartOfMethodSignature); + if (result) { + return expression; + } + } if (token === SyntaxKind.LessThanToken) { // See if this is the start of a generic invocation. If so, consume it and // keep checking for postfix expressions. Otherwise, it's just a '<' that's @@ -3566,6 +3662,28 @@ module ts { } } + function isStartOfMethodSignature() { + // try to parse it as a signature, if successful and we are followed by an open brace or semicolon, this is really a method signature + var parameters: NodeArray; + var type: TypeNode; + if (token === SyntaxKind.LessThanToken && !parseTypeParameters()) { + // failed to parse type parameters here, this is not a method + return false; + } + if (token === SyntaxKind.OpenParenToken) { + if (!parseParameterList(/*yieldAndGeneratorParameterContext*/ false, /*requireCompleteParameterList*/ true)) { + // failed to parse a parameter list here, this is not a method + return false; + } + if (token === SyntaxKind.ColonToken || token === SyntaxKind.SemicolonToken || token === SyntaxKind.OpenBraceToken) { + // found a type annotation, semicolon, or open brace, this is a method signature + type = parseTypeAnnotation(); + return true; + } + } + return false; + } + function parseArgumentList() { parseExpected(SyntaxKind.OpenParenToken); let result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); @@ -3688,19 +3806,19 @@ module ts { function parseArrayLiteralExpression(): ArrayLiteralExpression { let node = createNode(SyntaxKind.ArrayLiteralExpression); - parseExpected(SyntaxKind.OpenBracketToken); + node.openBracketToken = parseExpectedToken(SyntaxKind.OpenBracketToken, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, "["); if (scanner.hasPrecedingLineBreak()) node.flags |= NodeFlags.MultiLine; node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); - parseExpected(SyntaxKind.CloseBracketToken); + node.closeBracketToken = parseExpectedToken(SyntaxKind.CloseBracketToken, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, "]"); return finishNode(node); } - function tryParseAccessorDeclaration(fullStart: number, modifiers: ModifiersArray): AccessorDeclaration { + function tryParseAccessorDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): AccessorDeclaration { if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, modifiers); + return parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, decorators, modifiers); } else if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(SyntaxKind.SetAccessor, fullStart, modifiers); + return parseAccessorDeclaration(SyntaxKind.SetAccessor, fullStart, decorators, modifiers); } return undefined; @@ -3708,9 +3826,10 @@ module ts { function parseObjectLiteralElement(): ObjectLiteralElement { let fullStart = scanner.getStartPos(); + let decorators = parseDecorators(); let modifiers = parseModifiers(); - let accessor = tryParseAccessorDeclaration(fullStart, modifiers); + let accessor = tryParseAccessorDeclaration(fullStart, decorators, modifiers); if (accessor) { return accessor; } @@ -3723,7 +3842,7 @@ module ts { // Disallowing of optional property assignments happens in the grammar checker. let questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(fullStart, modifiers, asteriskToken, propertyName, questionToken); + return parseMethodDeclaration(fullStart, decorators, modifiers, asteriskToken, propertyName, questionToken); } // Parse to check if it is short-hand property assignment or normal property assignment @@ -3760,12 +3879,19 @@ module ts { // function * BindingIdentifier[Yield]opt (FormalParameters[Yield, GeneratorParameter]) { GeneratorBody[Yield] } // FunctionExpression: // function BindingIdentifieropt(FormalParameters) { FunctionBody } + let saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(false); + } let node = createNode(SyntaxKind.FunctionExpression); parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); node.name = node.asteriskToken ? doInYieldContext(parseOptionalIdentifier) : parseOptionalIdentifier(); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ !!node.asteriskToken, /*requireCompleteParameterList:*/ false, node); node.body = parseFunctionBlock(/*allowYield:*/ !!node.asteriskToken, /* ignoreMissingOpenBrace */ false); + if (saveDecoratorContext) { + setDecoratorContext(true); + } return finishNode(node); } @@ -3802,8 +3928,17 @@ module ts { let savedYieldContext = inYieldContext(); setYieldContext(allowYield); + let saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(false); + } + let block = parseBlock(ignoreMissingOpenBrace, /*checkForStrictMode*/ true, diagnosticMessage); + if (saveDecoratorContext) { + setDecoratorContext(true); + } + setYieldContext(savedYieldContext); return block; @@ -4135,9 +4270,9 @@ module ts { case SyntaxKind.VarKeyword: case SyntaxKind.ConstKeyword: // const here should always be parsed as const declaration because of check in 'isStatement' - return parseVariableStatement(scanner.getStartPos(), /*modifiers:*/ undefined); + return parseVariableStatement(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers:*/ undefined); case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(scanner.getStartPos(), /*modifiers:*/ undefined); + return parseFunctionDeclaration(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers:*/ undefined); case SyntaxKind.SemicolonToken: return parseEmptyStatement(); case SyntaxKind.IfKeyword: @@ -4170,7 +4305,7 @@ module ts { case SyntaxKind.LetKeyword: // If let follows identifier on the same line, it is declaration parse it as variable statement if (isLetDeclaration()) { - return parseVariableStatement(scanner.getStartPos(), /*modifiers:*/ undefined); + return parseVariableStatement(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers:*/ undefined); } // Else parse it like identifier - fall through default: @@ -4180,7 +4315,9 @@ module ts { // work properly when incrementally parsing as the parser will produce the // same FunctionDeclaraiton or VariableStatement if it has the same text // regardless of whether it is inside a block or not. - if (isModifier(token)) { + // Even though variable statements and function declarations cannot have decorators, + // we parse them here to provide better error recovery. + if (isModifier(token) || token === SyntaxKind.AtToken) { let result = tryParse(parseVariableStatementOrFunctionDeclarationWithModifiers); if (result) { return result; @@ -4193,6 +4330,7 @@ module ts { function parseVariableStatementOrFunctionDeclarationWithModifiers(): FunctionDeclaration | VariableStatement { let start = scanner.getStartPos(); + let decorators = parseDecorators(); let modifiers = parseModifiers(); switch (token) { case SyntaxKind.ConstKeyword: @@ -4200,18 +4338,18 @@ module ts { if (nextTokenIsEnum) { return undefined; } - return parseVariableStatement(start, modifiers); + return parseVariableStatement(start, decorators, modifiers); case SyntaxKind.LetKeyword: if (!isLetDeclaration()) { return undefined; } - return parseVariableStatement(start, modifiers); + return parseVariableStatement(start, decorators, modifiers); case SyntaxKind.VarKeyword: - return parseVariableStatement(start, modifiers); + return parseVariableStatement(start, decorators, modifiers); case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(start, modifiers); + return parseFunctionDeclaration(start, decorators, modifiers); } return undefined; @@ -4341,16 +4479,18 @@ module ts { return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; } - function parseVariableStatement(fullStart: number, modifiers: ModifiersArray): VariableStatement { + function parseVariableStatement(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): VariableStatement { let node = createNode(SyntaxKind.VariableStatement, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); node.declarationList = parseVariableDeclarationList(/*inForStatementInitializer:*/ false); parseSemicolon(); return finishNode(node); } - function parseFunctionDeclaration(fullStart: number, modifiers: ModifiersArray): FunctionDeclaration { + function parseFunctionDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): FunctionDeclaration { let node = createNode(SyntaxKind.FunctionDeclaration, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); @@ -4360,8 +4500,9 @@ module ts { return finishNode(node); } - function parseConstructorDeclaration(pos: number, modifiers: ModifiersArray): ConstructorDeclaration { + function parseConstructorDeclaration(pos: number, decorators: NodeArray, modifiers: ModifiersArray): ConstructorDeclaration { let node = createNode(SyntaxKind.Constructor, pos); + node.decorators = decorators; setModifiers(node, modifiers); parseExpected(SyntaxKind.ConstructorKeyword); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ false, node); @@ -4369,8 +4510,9 @@ module ts { return finishNode(node); } - function parseMethodDeclaration(fullStart: number, modifiers: ModifiersArray, asteriskToken: Node, name: DeclarationName, questionToken: Node, diagnosticMessage?: DiagnosticMessage): MethodDeclaration { + function parseMethodDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray, asteriskToken: Node, name: DeclarationName, questionToken: Node, diagnosticMessage?: DiagnosticMessage): MethodDeclaration { let method = createNode(SyntaxKind.MethodDeclaration, fullStart); + method.decorators = decorators; setModifiers(method, modifiers); method.asteriskToken = asteriskToken; method.name = name; @@ -4380,7 +4522,7 @@ module ts { return finishNode(method); } - function parsePropertyOrMethodDeclaration(fullStart: number, modifiers: ModifiersArray): ClassElement { + function parsePropertyOrMethodDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ClassElement { let asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); let name = parsePropertyName(); @@ -4388,10 +4530,11 @@ module ts { // report an error in the grammar checker. let questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(fullStart, modifiers, asteriskToken, name, questionToken, Diagnostics.or_expected); + return parseMethodDeclaration(fullStart, decorators, modifiers, asteriskToken, name, questionToken, Diagnostics.or_expected); } else { let property = createNode(SyntaxKind.PropertyDeclaration, fullStart); + property.decorators = decorators; setModifiers(property, modifiers); property.name = name; property.questionToken = questionToken; @@ -4406,8 +4549,9 @@ module ts { return parseInitializer(/*inParameter*/ false); } - function parseAccessorDeclaration(kind: SyntaxKind, fullStart: number, modifiers: ModifiersArray): AccessorDeclaration { + function parseAccessorDeclaration(kind: SyntaxKind, fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): AccessorDeclaration { let node = createNode(kind, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); node.name = parsePropertyName(); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ false, node); @@ -4418,6 +4562,10 @@ module ts { function isClassMemberStart(): boolean { let idToken: SyntaxKind; + if (token === SyntaxKind.AtToken) { + return true; + } + // Eat up all modifiers, but hold on to the last one in case it is actually an identifier. while (isModifier(token)) { idToken = token; @@ -4469,6 +4617,29 @@ module ts { return false; } + function parseDecorators(): NodeArray { + let decorators: NodeArray; + while (true) { + if (token !== SyntaxKind.AtToken) { + break; + } + + if (!decorators) { + decorators = >[]; + decorators.pos = scanner.getStartPos(); + } + + let decorator = createNode(SyntaxKind.Decorator); + decorator.atToken = parseTokenNode(); + decorator.expression = doInDecoratorContext(parseLeftHandSideExpressionOrHigher); + decorators.push(finishNode(decorator)); + } + if (decorators) { + decorators.end = scanner.getStartPos(); + } + return decorators; + } + function parseModifiers(): ModifiersArray { let flags = 0; let modifiers: ModifiersArray; @@ -4494,21 +4665,227 @@ module ts { return modifiers; } + function extractCallExpressionFromDecoratorTail(decorator: Decorator): CallExpression { + // We could have greedily parsed a method signature name as a call expression, so we extract it from the decorator. + let expression = decorator.expression; + if (expression.kind === SyntaxKind.CallExpression) { + decorator.expression = (expression).expression; + decorator.end = decorator.expression.end; + return expression; + } + return undefined; + } + + function extractDeclarationNameFromDecoratorTail(decorator: Decorator): DeclarationName { + let expression = decorator.expression; + let name: DeclarationName; + switch (expression.kind) { + case SyntaxKind.Identifier: + // We could have greedily parsed a property name as an identifier, so + // we extract it from the decorator for a better editor experience: + // @ + // id() {} + // + var end = Math.min(getTokenPosOfNode(decorator.atToken, sourceFile) + 1, decorator.atToken.end); + if (lineBreakBetween(sourceFile, end, getTokenPosOfNode(expression, sourceFile))) { + decorator.expression = createMissingNodeAtPosition(end, SyntaxKind.Identifier, Diagnostics.Expression_expected); + decorator.end = decorator.atToken.end; + return expression; + } + + break; + + case SyntaxKind.ArrayLiteralExpression: + // We could have greedily parsed a computed property name as an array literal expression, so + // we extract it from the decorator for a better editor experience: + // @ + // ["computed"]() {} + // + var end = Math.min(getTokenPosOfNode(decorator.atToken, sourceFile) + 1, decorator.atToken.end); + if (lineBreakBetween(sourceFile, end, getTokenPosOfNode(expression, sourceFile))) { + let arrayExpr = expression; + decorator.expression = createMissingNodeAtPosition(end, SyntaxKind.Identifier, Diagnostics.Expression_expected); + decorator.end = decorator.atToken.end; + + var computedPropertyName = createNode(SyntaxKind.ComputedPropertyName, arrayExpr.pos); + if (arrayExpr.elements.length === 0) { + end = Math.min(getTokenPosOfNode(arrayExpr.openBracketToken, sourceFile) + 1, arrayExpr.openBracketToken.end); + computedPropertyName.expression = createMissingNodeAtPosition(end, SyntaxKind.Identifier, Diagnostics.Expression_expected); + } + else { + let i = arrayExpr.elements.length - 1; + let expr = arrayExpr.elements[i--]; + while (i >= 0) { + var element = arrayExpr.elements[i--]; + var commaExpr = createNode(SyntaxKind.BinaryExpression, element.pos); + commaExpr.operatorToken = finishNode(createNode(SyntaxKind.CommaToken, 0), 0); + commaExpr.left = element; + commaExpr.right = expr; + expr = finishNode(commaExpr, expr.end); + } + computedPropertyName.expression = expr; + } + + return finishNode(computedPropertyName, arrayExpr.end); + } + + break; + + case SyntaxKind.PropertyAccessExpression: + // We could have greedily parsed a property name as a property access, so we extract it from the decorator + // for a better editor experience. + // @expr. + // id() {} + // + let propExpr = expression; + var end = Math.min(getTokenPosOfNode(propExpr.dotToken, sourceFile) + 1, propExpr.dotToken.end); + if (lineBreakBetween(sourceFile, end, getTokenPosOfNode(propExpr.name, sourceFile))) { + name = propExpr.name; + propExpr.name = createMissingNodeAtPosition(end, SyntaxKind.Identifier, Diagnostics.Identifier_expected); + propExpr.end = propExpr.dotToken.end; + decorator.end = propExpr.dotToken.end; + return name; + } + break; + + case SyntaxKind.ElementAccessExpression: + // We could have greedily parsed a computed property name as an element access, so we extract it from the decorator. + // @expr + // ["expr"]() + // + let elementExpr = expression; + decorator.expression = elementExpr.expression; + decorator.end = elementExpr.expression.end; + + var computedPropertyName = createNode(SyntaxKind.ComputedPropertyName, getTokenPosOfNode(elementExpr.argumentExpression, sourceFile)); + computedPropertyName.expression = elementExpr.argumentExpression; + return finishNode(computedPropertyName, elementExpr.end); + } + + return createMissingNodeAtPosition(expression.end, SyntaxKind.Identifier, Diagnostics.Identifier_expected); + } + + function reparseTypeArgumentsAsTypeParameters(typeArguments: NodeArray): NodeArray { + let typeParameters = >[]; + typeParameters.pos = typeArguments.pos; + typeParameters.end = typeArguments.end; + for (let i = 0; i < typeArguments.length; i++) { + let typeArgument = typeArguments[i]; + let typeParameter = createNode(SyntaxKind.TypeParameter, typeArgument.pos); + Debug.assert(typeArgument.kind === SyntaxKind.TypeReference); + let typeReference = typeArgument; + Debug.assert(typeReference.typeName.kind === SyntaxKind.Identifier); + typeParameter.name = typeReference.typeName; + finishNode(typeParameter, typeArgument.end); + typeParameters.push(typeParameter); + } + return typeParameters; + } + + function reparseArgumentsAsParameters(argumentList: NodeArray): NodeArray { + let parameters = >[]; + parameters.pos = argumentList.pos; + parameters.end = argumentList.end; + for (let i = 0; i < argumentList.length; i++) { + let argument = argumentList[i]; + + // TODO: Object literal as object binding + // TODO: Array literal as array binding + let name: Identifier; + let initializer: Expression; + if (argument.kind === SyntaxKind.Identifier) { + name = argument; + } + else if (argument.kind === SyntaxKind.BinaryExpression) { + let binaryExpression = argument; + Debug.assert(binaryExpression.operatorToken.kind === SyntaxKind.EqualsToken); + if (binaryExpression.left.kind === SyntaxKind.Identifier) { + name = binaryExpression.left; + initializer = binaryExpression.right; + } + else { + Debug.fail("Invalid attempt to reclassify expression."); + } + } + else { + Debug.fail("Invalid attempt to reclassify expression."); + } + + let parameter = createNode(SyntaxKind.Parameter, argument.pos); + parameter.name = name; + parameter.initializer = initializer; + finishNode(parameter, argument.end); + parameters.push(parameter); + } + return parameters; + } + + function reparseCallExpressionAsSignature(node: CallExpression, signature: SignatureDeclaration): void { + if (node.typeArguments) { + signature.typeParameters = reparseTypeArgumentsAsTypeParameters(node.typeArguments); + } + signature.parameters = reparseArgumentsAsParameters(node.arguments); + signature.type = parseTypeAnnotation(); + } + + function reparseClassElement(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ClassElement { + // We may have parsed a decorator that greedily included part of the member as its expression. + let lastDecorator = decorators[decorators.length - 1]; + + // TODO(rbuckton): pull out the call expression or declaration name from the consise body of an arrow function + + // If we parsed a call expression, extract the call expression to reparse as a signature + let callExpression = extractCallExpressionFromDecoratorTail(lastDecorator); + + // Extract the declaration name from the decorator + let name = extractDeclarationNameFromDecoratorTail(lastDecorator); + let questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + decorators.end = lastDecorator.end; + + if (callExpression) { + // Reparse as method + let method = createNode(SyntaxKind.MethodDeclaration, fullStart); + method.decorators = decorators; + setModifiers(method, modifiers); + method.name = name; + method.questionToken = questionToken; + reparseCallExpressionAsSignature(callExpression, method); + method.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false); + return finishNode(method); + } + else { + if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { + return parseMethodDeclaration(fullStart, decorators, modifiers, /*asteriskToken*/ undefined, name, questionToken, Diagnostics.or_expected); + } + + // Reparse as property declaration with a missing name. + let property = createNode(SyntaxKind.PropertyDeclaration, fullStart); + property.decorators = decorators; + setModifiers(property, modifiers); + property.name = name; + property.type = parseTypeAnnotation(); + property.initializer = allowInAnd(parseNonParameterInitializer); + parseSemicolon(); + return finishNode(property); + } + } + function parseClassElement(): ClassElement { let fullStart = getNodePos(); + let decorators = parseDecorators(); let modifiers = parseModifiers(); - let accessor = tryParseAccessorDeclaration(fullStart, modifiers); + let accessor = tryParseAccessorDeclaration(fullStart, decorators, modifiers); if (accessor) { return accessor; } if (token === SyntaxKind.ConstructorKeyword) { - return parseConstructorDeclaration(fullStart, modifiers); + return parseConstructorDeclaration(fullStart, decorators, modifiers); } if (isIndexSignature()) { - return parseIndexSignatureDeclaration(modifiers); + return parseIndexSignatureDeclaration(fullStart, decorators, modifiers); } // It is very important that we check this *after* checking indexers because @@ -4519,14 +4896,18 @@ module ts { token === SyntaxKind.AsteriskToken || token === SyntaxKind.OpenBracketToken) { - return parsePropertyOrMethodDeclaration(fullStart, modifiers); + return parsePropertyOrMethodDeclaration(fullStart, decorators, modifiers); + } + + if (decorators) { + return reparseClassElement(fullStart, decorators, modifiers); } // 'isClassMemberStart' should have hinted not to attempt parsing. Debug.fail("Should not have attempted to parse class member declaration."); } - function parseClassDeclaration(fullStart: number, modifiers: ModifiersArray): ClassDeclaration { + function parseClassDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ClassDeclaration { // In ES6 specification, All parts of a ClassDeclaration or a ClassExpression are strict mode code let savedStrictModeContext = inStrictModeContext(); if (languageVersion >= ScriptTarget.ES6) { @@ -4534,6 +4915,7 @@ module ts { } var node = createNode(SyntaxKind.ClassDeclaration, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); parseExpected(SyntaxKind.ClassKeyword); node.name = node.flags & NodeFlags.Default ? parseOptionalIdentifier() : parseIdentifier(); @@ -4597,8 +4979,9 @@ module ts { return parseList(ParsingContext.ClassMembers, /*checkForStrictMode*/ false, parseClassElement); } - function parseInterfaceDeclaration(fullStart: number, modifiers: ModifiersArray): InterfaceDeclaration { + function parseInterfaceDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): InterfaceDeclaration { let node = createNode(SyntaxKind.InterfaceDeclaration, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); parseExpected(SyntaxKind.InterfaceKeyword); node.name = parseIdentifier(); @@ -4608,8 +4991,9 @@ module ts { return finishNode(node); } - function parseTypeAliasDeclaration(fullStart: number, modifiers: ModifiersArray): TypeAliasDeclaration { + function parseTypeAliasDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): TypeAliasDeclaration { let node = createNode(SyntaxKind.TypeAliasDeclaration, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); parseExpected(SyntaxKind.TypeKeyword); node.name = parseIdentifier(); @@ -4630,8 +5014,9 @@ module ts { return finishNode(node); } - function parseEnumDeclaration(fullStart: number, modifiers: ModifiersArray): EnumDeclaration { + function parseEnumDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): EnumDeclaration { let node = createNode(SyntaxKind.EnumDeclaration, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); parseExpected(SyntaxKind.EnumKeyword); node.name = parseIdentifier(); @@ -4657,30 +5042,32 @@ module ts { return finishNode(node); } - function parseInternalModuleTail(fullStart: number, modifiers: ModifiersArray, flags: NodeFlags): ModuleDeclaration { + function parseInternalModuleTail(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray, flags: NodeFlags): ModuleDeclaration { let node = createNode(SyntaxKind.ModuleDeclaration, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); node.flags |= flags; node.name = parseIdentifier(); node.body = parseOptional(SyntaxKind.DotToken) - ? parseInternalModuleTail(getNodePos(), /*modifiers:*/undefined, NodeFlags.Export) + ? parseInternalModuleTail(getNodePos(), /*decorators*/ undefined, /*modifiers:*/undefined, NodeFlags.Export) : parseModuleBlock(); return finishNode(node); } - function parseAmbientExternalModuleDeclaration(fullStart: number, modifiers: ModifiersArray): ModuleDeclaration { + function parseAmbientExternalModuleDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ModuleDeclaration { let node = createNode(SyntaxKind.ModuleDeclaration, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); node.name = parseLiteralNode(/*internName:*/ true); node.body = parseModuleBlock(); return finishNode(node); } - function parseModuleDeclaration(fullStart: number, modifiers: ModifiersArray): ModuleDeclaration { + function parseModuleDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ModuleDeclaration { parseExpected(SyntaxKind.ModuleKeyword); return token === SyntaxKind.StringLiteral - ? parseAmbientExternalModuleDeclaration(fullStart, modifiers) - : parseInternalModuleTail(fullStart, modifiers, modifiers ? modifiers.flags : 0); + ? parseAmbientExternalModuleDeclaration(fullStart, decorators, modifiers) + : parseInternalModuleTail(fullStart, decorators, modifiers, modifiers ? modifiers.flags : 0); } function isExternalModuleReference() { @@ -4698,7 +5085,7 @@ module ts { token === SyntaxKind.FromKeyword; } - function parseImportDeclarationOrImportEqualsDeclaration(fullStart: number, modifiers: ModifiersArray): ImportEqualsDeclaration | ImportDeclaration { + function parseImportDeclarationOrImportEqualsDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ImportEqualsDeclaration | ImportDeclaration { parseExpected(SyntaxKind.ImportKeyword); let afterImportPos = scanner.getStartPos(); @@ -4710,6 +5097,7 @@ module ts { // import x = require("mod"); or // import x = M.x; let importEqualsDeclaration = createNode(SyntaxKind.ImportEqualsDeclaration, fullStart); + importEqualsDeclaration.decorators = decorators; setModifiers(importEqualsDeclaration, modifiers); importEqualsDeclaration.name = identifier; parseExpected(SyntaxKind.EqualsToken); @@ -4721,6 +5109,7 @@ module ts { // Import statement let importDeclaration = createNode(SyntaxKind.ImportDeclaration, fullStart); + importDeclaration.decorators = decorators; setModifiers(importDeclaration, modifiers); // ImportDeclaration: @@ -4855,8 +5244,9 @@ module ts { return finishNode(node); } - function parseExportDeclaration(fullStart: number, modifiers: ModifiersArray): ExportDeclaration { + function parseExportDeclaration(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ExportDeclaration { let node = createNode(SyntaxKind.ExportDeclaration, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); if (parseOptional(SyntaxKind.AsteriskToken)) { parseExpected(SyntaxKind.FromKeyword); @@ -4872,8 +5262,9 @@ module ts { return finishNode(node); } - function parseExportAssignment(fullStart: number, modifiers: ModifiersArray): ExportAssignment { + function parseExportAssignment(fullStart: number, decorators: NodeArray, modifiers: ModifiersArray): ExportAssignment { let node = createNode(SyntaxKind.ExportAssignment, fullStart); + node.decorators = decorators; setModifiers(node, modifiers); if (parseOptional(SyntaxKind.EqualsToken)) { node.isExportEquals = true; @@ -4898,7 +5289,7 @@ module ts { return inStrictModeContext() || lookAhead(nextTokenIsIdentifierOrStartOfDestructuringOnTheSameLine); } - function isDeclarationStart(): boolean { + function isDeclarationStart(followsModifier?: boolean): boolean { switch (token) { case SyntaxKind.VarKeyword: case SyntaxKind.ConstKeyword: @@ -4928,6 +5319,10 @@ module ts { case SyntaxKind.StaticKeyword: // Check for modifier on source element return lookAhead(nextTokenIsDeclarationStart); + case SyntaxKind.AtToken: + // a lookahead here is too costly, and decorators are only valid on a declaration. + // We will assume we are parsing a declaration here and report an error later + return !followsModifier; } } @@ -4954,12 +5349,12 @@ module ts { function nextTokenCanFollowExportKeyword() { nextToken(); return token === SyntaxKind.EqualsToken || token === SyntaxKind.AsteriskToken || - token === SyntaxKind.OpenBraceToken || token === SyntaxKind.DefaultKeyword || isDeclarationStart(); + token === SyntaxKind.OpenBraceToken || token === SyntaxKind.DefaultKeyword || isDeclarationStart(/*followsModifier*/ true); } function nextTokenIsDeclarationStart() { nextToken(); - return isDeclarationStart(); + return isDeclarationStart(/*followsModifier*/ true); } function nextTokenIsAsKeyword() { @@ -4968,14 +5363,15 @@ module ts { function parseDeclaration(): ModuleElement { let fullStart = getNodePos(); + let decorators = parseDecorators(); let modifiers = parseModifiers(); if (token === SyntaxKind.ExportKeyword) { nextToken(); if (token === SyntaxKind.DefaultKeyword || token === SyntaxKind.EqualsToken) { - return parseExportAssignment(fullStart, modifiers); + return parseExportAssignment(fullStart, decorators, modifiers); } if (token === SyntaxKind.AsteriskToken || token === SyntaxKind.OpenBraceToken) { - return parseExportDeclaration(fullStart, modifiers); + return parseExportDeclaration(fullStart, decorators, modifiers); } } @@ -4983,22 +5379,30 @@ module ts { case SyntaxKind.VarKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: - return parseVariableStatement(fullStart, modifiers); + return parseVariableStatement(fullStart, decorators, modifiers); case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(fullStart, modifiers); + return parseFunctionDeclaration(fullStart, decorators, modifiers); case SyntaxKind.ClassKeyword: - return parseClassDeclaration(fullStart, modifiers); + return parseClassDeclaration(fullStart, decorators, modifiers); case SyntaxKind.InterfaceKeyword: - return parseInterfaceDeclaration(fullStart, modifiers); + return parseInterfaceDeclaration(fullStart, decorators, modifiers); case SyntaxKind.TypeKeyword: - return parseTypeAliasDeclaration(fullStart, modifiers); + return parseTypeAliasDeclaration(fullStart, decorators, modifiers); case SyntaxKind.EnumKeyword: - return parseEnumDeclaration(fullStart, modifiers); + return parseEnumDeclaration(fullStart, decorators, modifiers); case SyntaxKind.ModuleKeyword: - return parseModuleDeclaration(fullStart, modifiers); + return parseModuleDeclaration(fullStart, decorators, modifiers); case SyntaxKind.ImportKeyword: - return parseImportDeclarationOrImportEqualsDeclaration(fullStart, modifiers); + return parseImportDeclarationOrImportEqualsDeclaration(fullStart, decorators, modifiers); default: + if (decorators) { + // We reached this point because we encountered an AtToken and assumed a declaration would + // follow. For recovery and error reporting purposes, return an incomplete declaration. + let node = createNode(SyntaxKind.IncompleteDeclaration, fullStart); + node.decorators = decorators; + setModifiers(node, modifiers); + return finishNode(node); + } Debug.fail("Mismatch between isDeclarationStart and parseDeclaration"); } }