diff --git a/src/services/syntax/parser.ts b/src/services/syntax/parser.ts index d3d92291264..537ebd66f06 100644 --- a/src/services/syntax/parser.ts +++ b/src/services/syntax/parser.ts @@ -354,33 +354,6 @@ module TypeScript.Parser { return result; } - function currentNode(): ISyntaxNode { - // If we have any outstanding tokens, then don't reuse a node. - // TODO(cyrusn): This may be too conservative. Perhaps we could reuse hte node and - // attach the skipped tokens in front? For now though, being conservative is nice and - // safe, and likely won't ever affect perf. - if (!skippedTokens) { - var node = source.currentNode(); - - // We can only reuse a node if it was parsed under the same strict mode that we're - // currently in. i.e. if we originally parsed a node in non-strict mode, but then - // the user added 'using strict' at the top of the file, then we can't use that node - // again as the presense of strict mode may cause us to parse the tokens in the file - // differetly. - // - // Note: we *can* reuse tokens when the strict mode changes. That's because tokens - // are unaffected by strict mode. It's just the parser will decide what to do with it - // differently depending on what mode it is in. - if (node && - parserContextFlags(node) === contextFlags) { - - return node; - } - } - - return undefined; - } - function currentToken(): ISyntaxToken { return source.currentToken(); } @@ -466,9 +439,10 @@ module TypeScript.Parser { } } - function consumeNode(node: ISyntaxNode): void { + function consumeNode(node: ISyntaxNode): ISyntaxNode { Debug.assert(skippedTokens === undefined); source.consumeNodeOrToken(node); + return node; } //this method is called very frequently @@ -760,22 +734,12 @@ module TypeScript.Parser { } function isModuleElement(inErrorRecovery: boolean): boolean { - if (SyntaxUtilities.isModuleElement(currentNode())) { - return true; - } - var _modifierCount = modifierCount(); return isInterfaceEnumClassModuleImportExportOrTypeAlias(_modifierCount) || isStatement(_modifierCount, inErrorRecovery); } function tryParseModuleElement(inErrorRecovery: boolean): IModuleElementSyntax { - var node = currentNode(); - if (SyntaxUtilities.isModuleElement(node)) { - consumeNode(node); - return node; - } - var _currentToken = currentToken(); var _modifierCount = modifierCount(); @@ -964,11 +928,6 @@ module TypeScript.Parser { } function isEnumElement(inErrorRecovery: boolean): boolean { - var node = currentNode(); - if (node && node.kind === SyntaxKind.EnumElement) { - return true; - } - return isPropertyName(/*peekToken:*/ 0, inErrorRecovery); } @@ -977,12 +936,6 @@ module TypeScript.Parser { } function tryParseEnumElement(inErrorRecovery: boolean): EnumElementSyntax { - var node = currentNode(); - if (node && node.kind === SyntaxKind.EnumElement) { - consumeNode(node); - return node; - } - if (!isPropertyName(/*peekToken:*/ 0, inErrorRecovery)) { return undefined; } @@ -1182,10 +1135,6 @@ module TypeScript.Parser { } function isClassElement(inErrorRecovery: boolean): boolean { - if (SyntaxUtilities.isClassElement(currentNode())) { - return true; - } - return isAtModifier() || isConstructorDeclaration() || isAccessor(inErrorRecovery) || @@ -1249,12 +1198,6 @@ module TypeScript.Parser { } function tryParseClassElement(inErrorRecovery: boolean): IClassElementSyntax { - var node = currentNode(); - if (SyntaxUtilities.isClassElement(node)) { - consumeNode(node); - return node; - } - // Have to check for indexers before anything else. That way if we see "[foo:" we // parse it out as an indexer and not a member function or variable. var modifiers = parseModifiers(); @@ -1429,10 +1372,6 @@ module TypeScript.Parser { } function isTypeMember(inErrorRecovery: boolean): boolean { - if (SyntaxUtilities.isTypeMember(currentNode())) { - return true; - } - return isCallSignature(/*tokenIndex:*/ 0) || isConstructSignature() || isIndexSignature(/*tokenIndex:*/ 0) || @@ -1468,12 +1407,6 @@ module TypeScript.Parser { } function tryParseTypeMember(inErrorRecovery: boolean): ITypeMemberSyntax { - var node = currentNode(); - if (SyntaxUtilities.isTypeMember(node)) { - consumeNode(node); - return node; - } - if (isCallSignature(/*tokenIndex:*/ 0)) { // A call signature for a type member can both use 'yield' as a parameter name, and // does not have parameter initializers. So we can pass 'false' for both [Yield] @@ -1708,10 +1641,6 @@ module TypeScript.Parser { } function isStatement(modifierCount: number, inErrorRecovery: boolean): boolean { - if (SyntaxUtilities.isStatement(currentNode())) { - return true; - } - if (isDefinitelyNotStatement()) { return false; } @@ -1750,12 +1679,6 @@ module TypeScript.Parser { } function tryParseStatement(inErrorRecovery: boolean): IStatementSyntax { - var node = currentNode(); - if (SyntaxUtilities.isStatement(node)) { - consumeNode(node); - return node; - } - var _currentToken = currentToken(); var currentTokenKind = _currentToken.kind; return tryParseStatementWorker(_currentToken, currentTokenKind, modifierCount(), inErrorRecovery); @@ -2049,33 +1972,20 @@ module TypeScript.Parser { } function isSwitchClause(): boolean { - if (SyntaxUtilities.isSwitchClause(currentNode())) { - return true; - } - var currentTokenKind = currentToken().kind; return currentTokenKind === SyntaxKind.CaseKeyword || currentTokenKind === SyntaxKind.DefaultKeyword; } function tryParseSwitchClause(): ISwitchClauseSyntax { - // Debug.assert(isSwitchClause()); - var node = currentNode(); - if (SyntaxUtilities.isSwitchClause(node)) { - consumeNode(node); - return node; + var _currentToken = currentToken(); + switch (_currentToken.kind) { + case SyntaxKind.CaseKeyword: + return parseCaseSwitchClause(_currentToken); + case SyntaxKind.DefaultKeyword: + return parseDefaultSwitchClause(_currentToken); } - var _currentToken = currentToken(); - var kind = _currentToken.kind; - if (kind === SyntaxKind.CaseKeyword) { - return parseCaseSwitchClause(_currentToken); - } - else if (kind === SyntaxKind.DefaultKeyword) { - return parseDefaultSwitchClause(_currentToken); - } - else { - return undefined; - } + return undefined; } function parseCaseSwitchClause(caseKeyword: ISyntaxToken): CaseSwitchClauseSyntax { @@ -2312,44 +2222,10 @@ module TypeScript.Parser { } function isVariableDeclarator(): boolean { - var node = currentNode(); - if (node && node.kind === SyntaxKind.VariableDeclarator) { - return true; - } - return isIdentifier(currentToken()); } - function canReuseVariableDeclaratorNode(node: ISyntaxNode) { - if (!node || node.kind !== SyntaxKind.VariableDeclarator) { - return false; - } - - // Very subtle incremental parsing bug. Consider the following code: - // - // var v = new List < A, B - // - // This is actually legal code. It's a list of variable declarators "v = new List() - // - // then we have a problem. "v = new Listnode; - return variableDeclarator.equalsValueClause === undefined; - } - function tryParseVariableDeclarator(): VariableDeclaratorSyntax { - var node = currentNode(); - if (canReuseVariableDeclaratorNode(node)) { - consumeNode(node); - return node; - } - if (!isIdentifier(currentToken())) { return undefined; } @@ -4160,10 +4036,6 @@ module TypeScript.Parser { } function isParameter(): boolean { - if (currentNode() && currentNode().kind === SyntaxKind.Parameter) { - return true; - } - return isParameterHelper(currentToken()); } @@ -4181,12 +4053,6 @@ module TypeScript.Parser { } function tryParseParameter(): ParameterSyntax { - var node = currentNode(); - if (node && node.kind === SyntaxKind.Parameter) { - consumeNode(node); - return node; - } - var dotDotDotToken = tryEatToken(SyntaxKind.DotDotDotToken); var modifiers = parseModifiers(); @@ -4654,33 +4520,40 @@ module TypeScript.Parser { return currentToken().kind === SyntaxKind.FinallyKeyword; } - function isExpectedListItem(currentListType: ListParsingState, inErrorRecovery: boolean): any { + function isExpectedListItem(currentListType: ListParsingState, inErrorRecovery: boolean): boolean { + // If we're able to parse out a node for the current list type from the old parse tree, + // then definitely have an expected list item. + var node = currentNode(currentListType); + if (node) { + return true; + } + switch (currentListType) { - case ListParsingState.SourceUnit_ModuleElements: return isModuleElement(inErrorRecovery); - case ListParsingState.ClassDeclaration_ClassElements: return isClassElement(inErrorRecovery); - case ListParsingState.ModuleDeclaration_ModuleElements: return isModuleElement(inErrorRecovery); - case ListParsingState.SwitchStatement_SwitchClauses: return isSwitchClause(); - case ListParsingState.SwitchClause_Statements: return isStatement(modifierCount(), inErrorRecovery); - case ListParsingState.Block_Statements: return isStatement(modifierCount(), inErrorRecovery); + case ListParsingState.SourceUnit_ModuleElements: return isModuleElement(inErrorRecovery); + case ListParsingState.ClassDeclaration_ClassElements: return isClassElement(inErrorRecovery); + case ListParsingState.ModuleDeclaration_ModuleElements: return isModuleElement(inErrorRecovery); + case ListParsingState.SwitchStatement_SwitchClauses: return isSwitchClause(); + case ListParsingState.SwitchClause_Statements: return isStatement(modifierCount(), inErrorRecovery); + case ListParsingState.Block_Statements: return isStatement(modifierCount(), inErrorRecovery); // These two are special. They're just augmentations of "Block_Statements" // used so we can abort out of the try block if we see a 'catch' or 'finally' // keyword. There are no additional list items that they add, so we just // return 'false' here. - case ListParsingState.TryBlock_Statements: return false; - case ListParsingState.CatchBlock_Statements: return false; - case ListParsingState.EnumDeclaration_EnumElements: return isEnumElement(inErrorRecovery); - case ListParsingState.ObjectType_TypeMembers: return isTypeMember(inErrorRecovery); - case ListParsingState.ClassOrInterfaceDeclaration_HeritageClauses: return isHeritageClause(); - case ListParsingState.HeritageClause_TypeNameList: return isHeritageClauseTypeName(); - case ListParsingState.VariableDeclaration_VariableDeclarators: return isVariableDeclarator(); - case ListParsingState.ArgumentList_AssignmentExpressions: return isExpectedArgumentList_AssignmentExpression(); - case ListParsingState.ObjectLiteralExpression_PropertyAssignments: return isPropertyAssignment(inErrorRecovery); + case ListParsingState.TryBlock_Statements: return false; + case ListParsingState.CatchBlock_Statements: return false; + case ListParsingState.EnumDeclaration_EnumElements: return isEnumElement(inErrorRecovery); + case ListParsingState.ObjectType_TypeMembers: return isTypeMember(inErrorRecovery); + case ListParsingState.ClassOrInterfaceDeclaration_HeritageClauses: return isHeritageClause(); + case ListParsingState.HeritageClause_TypeNameList: return isHeritageClauseTypeName(); + case ListParsingState.VariableDeclaration_VariableDeclarators: return isVariableDeclarator(); + case ListParsingState.ArgumentList_AssignmentExpressions: return isExpectedArgumentList_AssignmentExpression(); + case ListParsingState.ObjectLiteralExpression_PropertyAssignments: return isPropertyAssignment(inErrorRecovery); case ListParsingState.ArrayLiteralExpression_AssignmentExpressions: return isAssignmentOrOmittedExpression(); - case ListParsingState.ParameterList_Parameters: return isParameter(); - case ListParsingState.IndexSignature_Parameters: return isParameter(); - case ListParsingState.TypeArgumentList_Types: return isType(); - case ListParsingState.TypeParameterList_TypeParameters: return isTypeParameter(); - case ListParsingState.TupleType_Types: return isType(); + case ListParsingState.ParameterList_Parameters: return isParameter(); + case ListParsingState.IndexSignature_Parameters: return isParameter(); + case ListParsingState.TypeArgumentList_Types: return isType(); + case ListParsingState.TypeParameterList_TypeParameters: return isTypeParameter(); + case ListParsingState.TupleType_Types: return isType(); default: throw Errors.invalidOperation(); } } @@ -4702,6 +4575,11 @@ module TypeScript.Parser { } function tryParseExpectedListItemWorker(currentListType: ListParsingState, inErrorRecovery: boolean): ISyntaxNodeOrToken { + var node = currentNode(currentListType); + if (node) { + return node; + } + switch (currentListType) { case ListParsingState.SourceUnit_ModuleElements: return tryParseModuleElement(inErrorRecovery); case ListParsingState.ClassDeclaration_ClassElements: return tryParseClassElement(inErrorRecovery); @@ -4728,6 +4606,151 @@ module TypeScript.Parser { } } + function currentNode(currentListType: ListParsingState): ISyntaxNode { + // If we have any outstanding tokens, then don't reuse a node. + // TODO(cyrusn): This may be too conservative. Perhaps we could reuse hte node and + // attach the skipped tokens in front? For now though, being conservative is nice and + // safe, and likely won't ever affect perf. + if (skippedTokens) { + return undefined; + } + + var node = source.currentNode(); + if (!node) { + return undefined; + } + + // We can only reuse a node if it was parsed under the same strict mode that we're + // currently in. i.e. if we originally parsed a node in non-strict mode, but then + // the user added 'using strict' at the top of the file, then we can't use that node + // again as the presense of strict mode may cause us to parse the tokens in the file + // differetly. + // + // Note: we *can* reuse tokens when the strict mode changes. That's because tokens + // are unaffected by strict mode. It's just the parser will decide what to do with it + // differently depending on what mode it is in. + // + // This also applies to all our other context flags as well. + if (parserContextFlags(node) !== contextFlags) { + return undefined; + } + + // Ok, we have a node that looks like it could be reused. Now verify that it is valid + // in the currest list parsing context that we're currently at. + if (!canReuseNode(node, currentListType)) { + return undefined; + } + + // It was valid. Let teh source know we're consuming this node, and pass to the list + // parser. + return consumeNode(node); + } + + function canReuseNode(node: ISyntaxNode, listParsingState: ListParsingState): boolean { + switch (listParsingState) { + case ListParsingState.SourceUnit_ModuleElements: + return SyntaxUtilities.isModuleElement(node); + + case ListParsingState.ClassDeclaration_ClassElements: + return SyntaxUtilities.isClassElement(node); + + case ListParsingState.ModuleDeclaration_ModuleElements: + return SyntaxUtilities.isModuleElement(node); + + case ListParsingState.SwitchStatement_SwitchClauses: + return SyntaxUtilities.isSwitchClause(node); + + case ListParsingState.SwitchClause_Statements: + case ListParsingState.Block_Statements: + case ListParsingState.TryBlock_Statements: + case ListParsingState.CatchBlock_Statements: + return SyntaxUtilities.isStatement(node); + + case ListParsingState.EnumDeclaration_EnumElements: + return node.kind === SyntaxKind.EnumElement; + + case ListParsingState.ObjectType_TypeMembers: + return SyntaxUtilities.isTypeMember(node); + + case ListParsingState.VariableDeclaration_VariableDeclarators: + return canReuseVariableDeclaratorNode(node); + + case ListParsingState.ParameterList_Parameters: + return node.kind === SyntaxKind.Parameter; + + case ListParsingState.IndexSignature_Parameters: + return node.kind === SyntaxKind.Parameter; + + // Any other lists we do not care about reusing nodes in. But feel free to add if + // you can do so safely. Danger areas involve nodes that may involve speculative + // parsing. If speculative parsing is involved with the node, then the range the + // parser reached while looking ahead might be in the edited range (see the example + // in canReuseVariableDeclaratorNode for a good case of this). + case ListParsingState.ClassOrInterfaceDeclaration_HeritageClauses: + // This would probably be safe to reuse. There is no speculative parsing with + // heritage clauses. + + case ListParsingState.HeritageClause_TypeNameList: + // This would probably be safe to reuse. There is no speculative parsing with + // type names in a heritage clause. There can be generic names in the type + // name list. But because it is a type context, we never use speculative + // parsing on the type argument list. + + case ListParsingState.TypeParameterList_TypeParameters: + // This would probably be safe to reuse. There is no speculative parsing with + // type parameters. Note that that's because type *parameters* only occur in + // unambiguous *type* contexts. While type *arguments* occur in very ambiguous + // *expression* contexts. + + case ListParsingState.TupleType_Types: + // This would probably be safe to reuse. There is no speculative parsing with + // tuple types. + + // Technically, type argument list types are probably safe to reuse. While + // speculative parsing is involved with them (since type argument lists are only + // produced from speculative parsing a < as a type argument list), we only have + // the types because speculative parsing succeeded. Thus, the lookahead never + // went past the end of the list and rewound. + case ListParsingState.TypeArgumentList_Types: + + // Note: these are almost certainly not safe to ever reuse. Expressions commonly + // need a large amount of lookahead, and we should not reuse them as they may + // have actually intersected the edit. + case ListParsingState.ArgumentList_AssignmentExpressions: + case ListParsingState.ArrayLiteralExpression_AssignmentExpressions: + + // This is not safe to reuse for the same reason as the 'AssignmentExpression' + // cases. i.e. a property assignment may end with an expression, and thus might + // have lookahead far beyond it's old node. + case ListParsingState.ObjectLiteralExpression_PropertyAssignments: + } + + return false; + } + + function canReuseVariableDeclaratorNode(node: ISyntaxNode) { + if (!node || node.kind !== SyntaxKind.VariableDeclarator) { + return false; + } + + // Very subtle incremental parsing bug. Consider the following code: + // + // var v = new List < A, B + // + // This is actually legal code. It's a list of variable declarators "v = new List() + // + // then we have a problem. "v = new Listnode; + return variableDeclarator.equalsValueClause === undefined; + } + function getExpectedListElementType(currentListType: ListParsingState): string { switch (currentListType) { case ListParsingState.SourceUnit_ModuleElements: return getLocalizedText(DiagnosticCode.module_class_interface_enum_import_or_statement, undefined);