/// /// /// /// module ts { var nodeConstructors = new Array Node>(SyntaxKind.Count); export function getNodeConstructor(kind: SyntaxKind): new () => Node { return nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)); } export function createNode(kind: SyntaxKind): Node { return new (getNodeConstructor(kind))(); } // Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes // stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise, // embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns // a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. export function forEachChild(node: Node, cbNode: (node: Node) => T, cbNodes?: (nodes: Node[]) => T): T { function child(node: Node): T { if (node) { return cbNode(node); } } function children(nodes: Node[]) { if (nodes) { if (cbNodes) { return cbNodes(nodes); } for (var i = 0, len = nodes.length; i < len; i++) { var result = cbNode(nodes[i]) if (result) { return result; } } return undefined; } } if (!node) { return; } switch (node.kind) { case SyntaxKind.QualifiedName: return child((node).left) || child((node).right); case SyntaxKind.TypeParameter: return child((node).name) || child((node).constraint) || child((node).expression); case SyntaxKind.Parameter: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: return children(node.modifiers) || child((node).propertyName) || child((node).dotDotDotToken) || child((node).name) || child((node).questionToken) || child((node).type) || child((node).initializer); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: return children(node.modifiers) || children((node).typeParameters) || children((node).parameters) || child((node).type); case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: return children(node.modifiers) || child((node).asteriskToken) || child((node).name) || child((node).questionToken) || children((node).typeParameters) || children((node).parameters) || child((node).type) || child((node).body); case SyntaxKind.TypeReference: return child((node).typeName) || children((node).typeArguments); case SyntaxKind.TypeQuery: return child((node).exprName); case SyntaxKind.TypeLiteral: return children((node).members); case SyntaxKind.ArrayType: return child((node).elementType); case SyntaxKind.TupleType: return children((node).elementTypes); case SyntaxKind.UnionType: return children((node).types); case SyntaxKind.ParenthesizedType: return child((node).type); case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: return children((node).elements); case SyntaxKind.ArrayLiteralExpression: return children((node).elements); case SyntaxKind.ObjectLiteralExpression: return children((node).properties); case SyntaxKind.PropertyAccessExpression: return child((node).expression) || child((node).name); case SyntaxKind.ElementAccessExpression: return child((node).expression) || child((node).argumentExpression); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return child((node).expression) || children((node).typeArguments) || children((node).arguments); case SyntaxKind.TaggedTemplateExpression: return child((node).tag) || child((node).template); case SyntaxKind.TypeAssertionExpression: return child((node).type) || child((node).expression); case SyntaxKind.ParenthesizedExpression: return child((node).expression); case SyntaxKind.DeleteExpression: return child((node).expression); case SyntaxKind.TypeOfExpression: return child((node).expression); case SyntaxKind.VoidExpression: return child((node).expression); case SyntaxKind.PrefixUnaryExpression: return child((node).operand); case SyntaxKind.YieldExpression: return child((node).asteriskToken) || child((node).expression); case SyntaxKind.PostfixUnaryExpression: return child((node).operand); case SyntaxKind.BinaryExpression: return child((node).left) || child((node).right); case SyntaxKind.ConditionalExpression: return child((node).condition) || child((node).whenTrue) || child((node).whenFalse); case SyntaxKind.Block: case SyntaxKind.ModuleBlock: return children((node).statements); case SyntaxKind.SourceFile: return children((node).statements) || child((node).endOfFileToken); case SyntaxKind.VariableStatement: return children(node.modifiers) || child((node).declarationList); case SyntaxKind.VariableDeclarationList: return children((node).declarations); case SyntaxKind.ExpressionStatement: return child((node).expression); case SyntaxKind.IfStatement: return child((node).expression) || child((node).thenStatement) || child((node).elseStatement); case SyntaxKind.DoStatement: return child((node).statement) || child((node).expression); case SyntaxKind.WhileStatement: return child((node).expression) || child((node).statement); case SyntaxKind.ForStatement: return child((node).initializer) || child((node).condition) || child((node).iterator) || child((node).statement); case SyntaxKind.ForInStatement: return child((node).initializer) || child((node).expression) || child((node).statement); case SyntaxKind.ContinueStatement: case SyntaxKind.BreakStatement: return child((node).label); case SyntaxKind.ReturnStatement: return child((node).expression); case SyntaxKind.WithStatement: return child((node).expression) || child((node).statement); case SyntaxKind.SwitchStatement: return child((node).expression) || children((node).clauses); case SyntaxKind.CaseClause: return child((node).expression) || children((node).statements); case SyntaxKind.DefaultClause: return children((node).statements); case SyntaxKind.LabeledStatement: return child((node).label) || child((node).statement); case SyntaxKind.ThrowStatement: return child((node).expression); case SyntaxKind.TryStatement: return child((node).tryBlock) || child((node).catchClause) || child((node).finallyBlock); case SyntaxKind.CatchClause: return child((node).name) || child((node).type) || child((node).block); case SyntaxKind.ClassDeclaration: return children(node.modifiers) || child((node).name) || children((node).typeParameters) || children((node).heritageClauses) || children((node).members); case SyntaxKind.InterfaceDeclaration: return children(node.modifiers) || child((node).name) || children((node).typeParameters) || children((node).heritageClauses) || children((node).members); case SyntaxKind.TypeAliasDeclaration: return children(node.modifiers) || child((node).name) || child((node).type); case SyntaxKind.EnumDeclaration: return children(node.modifiers) || child((node).name) || children((node).members); case SyntaxKind.EnumMember: return child((node).name) || child((node).initializer); case SyntaxKind.ModuleDeclaration: return children(node.modifiers) || child((node).name) || child((node).body); case SyntaxKind.ImportDeclaration: return children(node.modifiers) || child((node).name) || child((node).moduleReference); case SyntaxKind.ExportAssignment: return children(node.modifiers) || child((node).exportName); case SyntaxKind.TemplateExpression: return child((node).head) || children((node).templateSpans); case SyntaxKind.TemplateSpan: return child((node).expression) || child((node).literal); case SyntaxKind.ComputedPropertyName: return child((node).expression); case SyntaxKind.HeritageClause: return children((node).types); case SyntaxKind.ExternalModuleReference: return child((node).expression); } } // TODO (drosen, mhegazy): Move to a more appropriate file. export function createCompilerHost(options: CompilerOptions): CompilerHost { var currentDirectory: string; var existingDirectories: Map = {}; function getCanonicalFileName(fileName: string): string { // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. // otherwise use toLowerCase as a canonical form. return sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); } // returned by CScript sys environment var unsupportedFileEncodingErrorCode = -2147024809; function getSourceFile(filename: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { try { var text = sys.readFile(filename, options.charset); } catch (e) { if (onError) { onError(e.number === unsupportedFileEncodingErrorCode ? createCompilerDiagnostic(Diagnostics.Unsupported_file_encoding).messageText : e.message); } text = ""; } return text !== undefined ? createSourceFile(filename, text, languageVersion) : undefined; } function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { function directoryExists(directoryPath: string): boolean { if (hasProperty(existingDirectories, directoryPath)) { return true; } if (sys.directoryExists(directoryPath)) { existingDirectories[directoryPath] = true; return true; } return false; } function ensureDirectoriesExist(directoryPath: string) { if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { var parentDirectory = getDirectoryPath(directoryPath); ensureDirectoriesExist(parentDirectory); sys.createDirectory(directoryPath); } } try { ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); sys.writeFile(fileName, data, writeByteOrderMark); } catch (e) { if (onError) { onError(e.message); } } } return { getSourceFile, getDefaultLibFilename: options => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), options.target === ScriptTarget.ES6 ? "lib.es6.d.ts" : "lib.d.ts"), writeFile, getCurrentDirectory: () => currentDirectory || (currentDirectory = sys.getCurrentDirectory()), useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => sys.newLine }; } const enum ParsingContext { SourceElements, // Elements in source file ModuleElements, // Elements in module declaration BlockStatements, // Statements in block SwitchClauses, // Clauses in switch statement SwitchClauseStatements, // Statements in switch clause TypeMembers, // Members in interface or type literal ClassMembers, // Members in class declaration EnumMembers, // Members in enum declaration TypeReferences, // Type references in extends or implements clause VariableDeclarations, // Variable declarations in variable statement ObjectBindingElements, // Binding elements in object binding list ArrayBindingElements, // Binding elements in array binding list ArgumentExpressions, // Expressions in argument list ObjectLiteralMembers, // Members in object literal ArrayLiteralMembers, // Members in array literal Parameters, // Parameters in parameter list TypeParameters, // Type parameters in type parameter list TypeArguments, // Type arguments in type argument list TupleElementTypes, // Element types in tuple element type list HeritageClauses, // Heritage clauses for a class or interface declaration. Count // Number of parsing contexts } const enum Tristate { False, True, Unknown } function parsingContextErrors(context: ParsingContext): DiagnosticMessage { switch (context) { case ParsingContext.SourceElements: return Diagnostics.Declaration_or_statement_expected; case ParsingContext.ModuleElements: return Diagnostics.Declaration_or_statement_expected; case ParsingContext.BlockStatements: return Diagnostics.Statement_expected; case ParsingContext.SwitchClauses: return Diagnostics.case_or_default_expected; case ParsingContext.SwitchClauseStatements: return Diagnostics.Statement_expected; case ParsingContext.TypeMembers: return Diagnostics.Property_or_signature_expected; case ParsingContext.ClassMembers: return Diagnostics.Unexpected_token_A_constructor_method_accessor_or_property_was_expected; case ParsingContext.EnumMembers: return Diagnostics.Enum_member_expected; case ParsingContext.TypeReferences: return Diagnostics.Type_reference_expected; case ParsingContext.VariableDeclarations: return Diagnostics.Variable_declaration_expected; case ParsingContext.ObjectBindingElements: return Diagnostics.Property_destructuring_pattern_expected; case ParsingContext.ArrayBindingElements: return Diagnostics.Array_element_destructuring_pattern_expected; case ParsingContext.ArgumentExpressions: return Diagnostics.Argument_expression_expected; case ParsingContext.ObjectLiteralMembers: return Diagnostics.Property_assignment_expected; case ParsingContext.ArrayLiteralMembers: return Diagnostics.Expression_or_comma_expected; case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected; case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected; case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected; case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected; case ParsingContext.HeritageClauses: return Diagnostics.Unexpected_token_expected; } }; export function modifierToFlag(token: SyntaxKind): NodeFlags { switch (token) { case SyntaxKind.StaticKeyword: return NodeFlags.Static; case SyntaxKind.PublicKeyword: return NodeFlags.Public; case SyntaxKind.ProtectedKeyword: return NodeFlags.Protected; case SyntaxKind.PrivateKeyword: return NodeFlags.Private; case SyntaxKind.ExportKeyword: return NodeFlags.Export; case SyntaxKind.DeclareKeyword: return NodeFlags.Ambient; case SyntaxKind.ConstKeyword: return NodeFlags.Const; } return 0; } function fixupParentReferences(sourceFile: SourceFile) { // normally parent references are set during binding. // however here SourceFile data is used only for syntactic features so running the whole binding process is an overhead. // walk over the nodes and set parent references var parent: Node = sourceFile; function walk(n: Node): void { // walk down setting parents that differ from the parent we think it should be. This // allows us to quickly bail out of setting parents for subtrees during incremental // parsing if (n.parent !== parent) { n.parent = parent; var saveParent = parent; parent = n; forEachChild(n, walk); parent = saveParent; } } forEachChild(sourceFile, walk); } export function isEvalOrArgumentsIdentifier(node: Node): boolean { return node.kind === SyntaxKind.Identifier && ((node).text === "eval" || (node).text === "arguments"); } /// Should be called only on prologue directives (isPrologueDirective(node) should be true) function isUseStrictPrologueDirective(sourceFile: SourceFile, node: Node): boolean { Debug.assert(isPrologueDirective(node)); var nodeText = getSourceTextOfNodeFromSourceFile(sourceFile,(node).expression); // Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the // string to contain unicode escapes (as per ES5). return nodeText === '"use strict"' || nodeText === "'use strict'"; } interface IncrementalElement extends TextRange { parent?: Node; intersectsChange: boolean length?: number; _children: Node[]; } interface IncrementalNode extends Node, IncrementalElement { } interface IncrementalNodeArray extends NodeArray, IncrementalElement { length: number } // Allows finding nodes in the source file at a certain position in an efficient manner. // The implementation takes advantage of the calling pattern it knows the parser will // make in order to optimize finding nodes as quickly as possible. interface SyntaxCursor { currentNode(position: number): IncrementalNode; } const enum InvalidPosition { Value = -1 } function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { var currentArray: NodeArray = sourceFile.statements; var currentArrayIndex = 0; Debug.assert(currentArrayIndex < currentArray.length); var current = currentArray[currentArrayIndex]; var lastQueriedPosition = InvalidPosition.Value; return { currentNode(position: number) { // Only compute the current node if the position is different than the last time // we were asked. The parser commonly asks for the node at the same position // twice. Once to know if can read an appropriate list element at a certain point, // and then to actually read and consume the node. if (position !== lastQueriedPosition) { // Much of the time the parser will need the very next node in the array that // we just returned a node from.So just simply check for that case and move // forward in the array instead of searching for the node again. if (current && current.end === position && currentArrayIndex < currentArray.length) { currentArrayIndex++; current = currentArray[currentArrayIndex]; } // If we don't have a node, or the node we have isn't in the right position, // then try to find a viable node at the position requested. if (!current || current.pos !== position) { findHighestListElementThatStartsAtPosition(position); } } // Cache this query so that we don't do any extra work if the parser calls back // into us. Note: this is very common as the parser will make pairs of calls like // 'isListElement -> parseListElement'. If we were unable to find a node when // called with 'isListElement', we don't want to redo the work when parseListElement // is called immediately after. lastQueriedPosition = position; // Either we don'd have a node, or we have a node at the position being asked for. Debug.assert(!current || current.pos === position); return current; } }; // Finds the highest element in the tree we can find that starts at the provided position. // The element must be a direct child of some node list in the tree. This way after we // return it, we can easily return its next sibling in the list. function findHighestListElementThatStartsAtPosition(position: number) { // Clear out any cached state about the last node we found. currentArray = undefined; currentArrayIndex = InvalidPosition.Value; current = undefined; // Recurse into the source file to find the highest node at this position. forEachChild(sourceFile, visitNode, visitArray); function visitNode(node: Node) { if (position >= node.pos && position < node.end) { // Position was within this node. Keep searching deeper to find the node. forEachChild(node, visitNode, visitArray); // don't procede any futher in the search. return true; } // position wasn't in this node, have to keep searching. return false; } function visitArray(array: NodeArray) { if (position >= array.pos && position < array.end) { // position was in this array. Search through this array to see if we find a // viable element. for (var i = 0, n = array.length; i < n; i++) { var child = array[i]; if (child) { if (child.pos === position) { // Found the right node. We're done. currentArray = array; currentArrayIndex = i; current = child; return true; } else { if (child.pos < position && position < child.end) { // Position in somewhere within this child. Search in it and // stop searching in this array. forEachChild(child, visitNode, visitArray); return true; } } } } } // position wasn't in this array, have to keep searching. return false; } } } export function createSourceFile(filename: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false): SourceFile { var parsingContext: ParsingContext; var identifiers: Map; var identifierCount = 0; var nodeCount = 0; var lineStarts: number[]; var syntacticDiagnostics: Diagnostic[]; var scanner: Scanner; var token: SyntaxKind; var syntaxCursor: SyntaxCursor; // Flags that dictate what parsing context we're in. For example: // Whether or not we are in strict parsing mode. All that changes in strict parsing mode is // that some tokens that would be considered identifiers may be considered keywords. // // When adding more parser context flags, consider which is the more common case that the // flag will be in. This should be hte 'false' state for that flag. The reason for this is // that we don't store data in our nodes unless the value is in the *non-default* state. So, // for example, more often than code 'allows-in' (or doesn't 'disallow-in'). We opt for // 'disallow-in' set to 'false'. Otherwise, if we had 'allowsIn' set to 'true', then almost // all nodes would need extra state on them to store this info. // // Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6 // grammar specification. // // An important thing about these context concepts. By default they are effectively inherited // while parsing through every grammar production. i.e. if you don't change them, then when // you parse a sub-production, it will have the same context values as hte parent production. // This is great most of the time. After all, consider all the 'expression' grammar productions // and how nearly all of them pass along the 'in' and 'yield' context values: // // EqualityExpression[In, Yield] : // RelationalExpression[?In, ?Yield] // EqualityExpression[?In, ?Yield] == RelationalExpression[?In, ?Yield] // EqualityExpression[?In, ?Yield] != RelationalExpression[?In, ?Yield] // EqualityExpression[?In, ?Yield] === RelationalExpression[?In, ?Yield] // EqualityExpression[?In, ?Yield] !== RelationalExpression[?In, ?Yield] // // Where you have to be careful is then understanding what the points are in the grammar // where the values are *not* passed along. For example: // // SingleNameBinding[Yield,GeneratorParameter] // [+GeneratorParameter]BindingIdentifier[Yield] Initializer[In]opt // [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt // // Here this is saying that if the GeneratorParameter context flag is set, that we should // explicitly set the 'yield' context flag to false before calling into the BindingIdentifier // and we should explicitly unset the 'yield' context flag before calling into the Initializer. // production. Conversely, if the GeneratorParameter context flag is not set, then we // should leave the 'yield' context flag alone. // // Getting this all correct is tricky and requires careful reading of the grammar to // understand when these values should be changed versus when they should be inherited. // // Note: it should not be necessary to save/restore these flags during speculative/lookahead // parsing. These context flags are naturally stored and restored through normal recursive // descent parsing and unwinding. var contextFlags: ParserContextFlags; // Whether or not we've had a parse error since creating the last AST node. If we have // encountered an error, it will be stored on the next AST node we create. Parse errors // can be broken down into three categories: // // 1) An error that occurred during scanning. For example, an unterminated literal, or a // character that was completely not understood. // // 2) A token was expected, but was not present. This type of error is commonly produced // by the 'parseExpected' function. // // 3) A token was present that no parsing function was able to consume. This type of error // only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser // decides to skip the token. // // In all of these cases, we want to mark the next node as having had an error before it. // With this mark, we can know in incremental settings if this node can be reused, or if // we have to reparse it. If we don't keep this information around, we may just reuse the // node. in that event we would then not produce the same errors as we did before, causing // significant confusion problems. // // Note: it is necessary that this value be saved/restored during speculative/lookahead // parsing. During lookahead parsing, we will often create a node. That node will have // this value attached, and then this value will be set back to 'false'. If we decide to // rewind, we must get back to the same value we had prior to the lookahead. // // Note: any errors at the end of the file that do not precede a regular node, should get // attached to the EOF token. var parseErrorBeforeNextFinishedNode: boolean; var sourceFile: SourceFile; return parseSourceFile(sourceText, setParentNodes); function parseSourceFile(text: string, setParentNodes: boolean): SourceFile { // Set our initial state before parsing. sourceText = text; parsingContext = 0; identifiers = {}; lineStarts = undefined; syntacticDiagnostics = undefined; contextFlags = 0; parseErrorBeforeNextFinishedNode = false; sourceFile = createNode(SyntaxKind.SourceFile, 0); sourceFile.referenceDiagnostics = []; sourceFile.parseDiagnostics = []; sourceFile.grammarDiagnostics = []; sourceFile.semanticDiagnostics = []; // Create and prime the scanner before parsing the source elements. scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError); token = nextToken(); sourceFile.flags = fileExtensionIs(filename, ".d.ts") ? NodeFlags.DeclarationFile : 0; sourceFile.end = sourceText.length; sourceFile.filename = normalizePath(filename); sourceFile.text = sourceText; sourceFile.getLineAndCharacterFromPosition = getLineAndCharacterFromSourcePosition; sourceFile.getPositionFromLineAndCharacter = getPositionFromSourceLineAndCharacter; sourceFile.getLineStarts = getLineStarts; sourceFile.getSyntacticDiagnostics = getSyntacticDiagnostics; sourceFile.update = update; processReferenceComments(sourceFile); sourceFile.statements = parseList(ParsingContext.SourceElements, /*checkForStrictMode*/ true, parseSourceElement); Debug.assert(token === SyntaxKind.EndOfFileToken); sourceFile.endOfFileToken = parseTokenNode(); setExternalModuleIndicator(sourceFile); sourceFile.nodeCount = nodeCount; sourceFile.identifierCount = identifierCount; sourceFile.languageVersion = languageVersion; sourceFile.identifiers = identifiers; if (setParentNodes) { fixupParentReferences(sourceFile); } return sourceFile; } function update(newText: string, textChangeRange: TextChangeRange) { if (textChangeRangeIsUnchanged(textChangeRange)) { // if the text didn't change, then we can just return our current source file as-is. return sourceFile; } if (sourceFile.statements.length === 0) { // If we don't have any statements in the current source file, then there's no real // way to incrementally parse. So just do a full parse instead. return parseSourceFile(newText, /*setNodeParents*/ true); } syntaxCursor = createSyntaxCursor(sourceFile); // Make the actual change larger so that we know to reparse anything whose lookahead // might have intersected the change. var changeRange = extendToAffectedRange(textChangeRange); // The is the amount the nodes after the edit range need to be adjusted. It can be // positive (if the edit added characters), negative (if the edit deleted characters) // or zero (if this was a pure overwrite with nothing added/removed). var delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length; // If we added or removed characters during the edit, then we need to go and adjust all // the nodes after the edit. Those nodes may move forward down (if we inserted chars) // or they may move backward (if we deleted chars). // // Doing this helps us out in two ways. First, it means that any nodes/tokens we want // to reuse are already at the appropriate position in the new text. That way when we // reuse them, we don't have to figure out if they need to be adjusted. Second, it makes // it very easy to determine if we can reuse a node. If the node's position is at where // we are in the text, then we can reuse it. Otherwise we can't. If hte node's position // is ahead of us, then we'll need to rescan tokens. If the node's position is behind // us, then we'll need to skip it or crumble it as appropriate // // We will also adjust the positions of nodes that intersect the change range as well. // By doing this, we ensure that all the positions in the old tree are consistent, not // just the positions of nodes entirely before/after the change range. By being // consistent, we can then easily map from positions to nodes in the old tree easily. // // Also, mark any syntax elements that intersect the changed span. We know, up front, // that we cannot reuse these elements. updateTokenPositionsAndMarkElements(sourceFile, changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta); // Now that we've set up our internal incremental state just proceed and parse the // source file in the normal fashion. When possible the parser will retrieve and // reuse nodes from the old tree. // // Note: passing in 'true' for setNodeParents is very important. When incrementally // parsing, we will be reusing nodes from the old tree, and placing it into new // parents. If we don't set the parents now, we'll end up with an observably // inconsistent tree. Setting the parents on the new tree should be very fast. We // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. var result = parseSourceFile(newText, /*setNodeParents*/ true); // Clear out the syntax cursor so it doesn't keep anything alive longer than it should. syntaxCursor = undefined; return result; } function updateTokenPositionsAndMarkElements(node: IncrementalNode, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number): void { visitNode(node); function visitNode(child: IncrementalNode) { if (child.pos > changeRangeOldEnd) { // Node is entirely past the change range. We need to move both its pos and // end, forward or backward appropriately. moveElementEntirelyPastChangeRange(child, delta); return; } // Check if the element intersects the change range. If it does, then it is not // reusable. Also, we'll need to recurse to see what constituent portions we may // be able to use. var fullEnd = child.end; if (fullEnd >= changeStart) { child.intersectsChange = true; // Adjust the pos or end (or both) of the intersecting element accordingly. adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); forEachChild(child, visitNode, visitArray); return; } // Otherwise, the node is entirely before the change range. No need to do anything with it. } function visitArray(array: IncrementalNodeArray) { if (array.pos > changeRangeOldEnd) { // Array is entirely after the change range. We need to move it, and move any of // its children. moveElementEntirelyPastChangeRange(array, delta); } else { // Check if the element intersects the change range. If it does, then it is not // reusable. Also, we'll need to recurse to see what constituent portions we may // be able to use. var fullEnd = array.end; if (fullEnd >= changeStart) { array.intersectsChange = true; // Adjust the pos or end (or both) of the intersecting array accordingly. adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); for (var i = 0, n = array.length; i < n; i++) { visitNode(array[i]); } } // else { // Otherwise, the array is entirely before the change range. No need to do anything with it. // } } } } function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range"); Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range"); // We have an element that intersects the change range in some way. It may have its // start, or its end (or both) in the changed range. We want to adjust any part // that intersects such that the final tree is in a consistent state. i.e. all // chlidren have spans within the span of their parent, and all siblings are ordered // properly. // We may need to update both the 'pos' and the 'end' of the element. // If the 'pos' is before the start of the change, then we don't need to touch it. // If it isn't, then the 'pos' must be inside the change. How we update it will // depend if delta is positive or negative. If delta is positive then we have // something like: // // -------------------AAA----------------- // -------------------BBBCCCCCCC----------------- // // In this case, we consider any node that started in the change range to still be // starting at the same position. // // however, if the delta is negative, then we instead have something like this: // // -------------------XXXYYYYYYY----------------- // -------------------ZZZ----------------- // // In this case, any element that started in the 'X' range will keep its position. // However any element htat started after that will have their pos adjusted to be // at the end of the new range. i.e. any node that started in the 'Y' range will // be adjusted to have their start at the end of the 'Z' range. // // The element will keep its position if possible. Or Move backward to the new-end // if it's in the 'Y' range. element.pos = Math.min(element.pos, changeRangeNewEnd); // If the 'end' is after the change range, then we always adjust it by the delta // amount. However, if the end is in the change range, then how we adjust it // will depend on if delta is positive or negative. If delta is positive then we // have something like: // // -------------------AAA----------------- // -------------------BBBCCCCCCC----------------- // // In this case, we consider any node that ended inside the change range to keep its // end position. // // however, if the delta is negative, then we instead have something like this: // // -------------------XXXYYYYYYY----------------- // -------------------ZZZ----------------- // // In this case, any element that ended in the 'X' range will keep its position. // However any element htat ended after that will have their pos adjusted to be // at the end of the new range. i.e. any node that ended in the 'Y' range will // be adjusted to have their end at the end of the 'Z' range. if (element.end >= changeRangeOldEnd) { // Element ends after the change range. Always adjust the end pos. element.end += delta; } else { // Element ends in the change range. The element will keep its position if // possible. Or Move backward to the new-end if it's in the 'Y' range. element.end = Math.min(element.end, changeRangeNewEnd); } Debug.assert(element.pos <= element.end); if (element.parent) { Debug.assert(element.pos >= element.parent.pos); Debug.assert(element.end <= element.parent.end); } } function moveElementEntirelyPastChangeRange(element: IncrementalElement, delta: number) { if (element.length) { visitArray(element); } else { visitNode(element); } function visitNode(node: IncrementalNode) { // Ditch any existing LS children we may have created. This way we can avoid // moving them forward. node._children = undefined; node.pos += delta; node.end += delta; forEachChild(node, visitNode, visitArray); } function visitArray(array: IncrementalNodeArray) { array.pos += delta; array.end += delta; for (var i = 0, n = array.length; i < n; i++) { visitNode(array[i]); } } } function extendToAffectedRange(changeRange: TextChangeRange): TextChangeRange { // Consider the following code: // void foo() { /; } // // If the text changes with an insertion of / just before the semicolon then we end up with: // void foo() { //; } // // If we were to just use the changeRange a is, then we would not rescan the { token // (as it does not intersect the actual original change range). Because an edit may // change the token touching it, we actually need to look back *at least* one token so // that the prior token sees that change. var maxLookahead = 1; var start = changeRange.span.start; // the first iteration aligns us with the change start. subsequent iteration move us to // the left by maxLookahead tokens. We only need to do this as long as we're not at the // start of the tree. for (var i = 0; start > 0 && i <= maxLookahead; i++) { var nearestNode = findNearestNodeStartingBeforeOrAtPosition(start); var position = nearestNode.pos; start = Math.max(0, position - 1); } var finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); var finalLength = changeRange.newLength + (changeRange.span.start - start); return createTextChangeRange(finalSpan, finalLength); } function findNearestNodeStartingBeforeOrAtPosition(position: number): Node { var bestResult: Node = sourceFile; var lastNodeEntirelyBeforePosition: Node; forEachChild(sourceFile, visit); if (lastNodeEntirelyBeforePosition) { var lastChildOfLastEntireNodeBeforePosition = getLastChild(lastNodeEntirelyBeforePosition); if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { bestResult = lastChildOfLastEntireNodeBeforePosition; } } return bestResult; function getLastChild(node: Node): Node { while (true) { var lastChild = getLastChildWorker(node); if (lastChild) { node = lastChild; } else { return node; } } } function getLastChildWorker(node: Node): Node { var last:Node = undefined; forEachChild(node, child => { if (nodeIsPresent(child)) { last = child; } }); return last; } function visit(child: Node) { if (nodeIsMissing(child)) { // Missing nodes are effectively invisible to us. We never even consider them // When trying to find the nearest node before us. return; } // If the child intersects this position, then this node is currently the nearest // node that starts before the position. if (child.pos <= position) { if (child.pos >= bestResult.pos) { // This node starts before the position, and is closer to the position than // the previous best node we found. It is now the new best node. bestResult = child; } // Now, the node may overlap the position, or it may end entirely before the // position. If it overlaps with the position, then either it, or one of its // children must be the nearest node before the position. So we can just // recurse into this child to see if we can find something better. if (position < child.end) { // The nearest node is either this child, or one of the children inside // of it. We've already marked this child as the best so far. Recurse // in case one of the children is better. forEachChild(child, visit); // Once we look at the children of this node, then there's no need to // continue any further. return true; } else { Debug.assert(child.end <= position); // The child ends entirely before this position. Say you have the following // (where $ is the position) // // ? $ : <...> <...> // // We would want to find the nearest preceding node in "complex expr 2". // To support that, we keep track of this node, and once we're done searching // for a best node, we recurse down this node to see if we can find a good // result in it. // // This approach allows us to quickly skip over nodes that are entirely // before the position, while still allowing us to find any nodes in the // last one that might be what we want. lastNodeEntirelyBeforePosition = child; } } else { Debug.assert(child.pos > position); // We're now at a node that is entirely past the position we're searching for. // This node (and all following nodes) could never contribute to the result, // so just skip them by returning 'true' here. return true; } } } function setContextFlag(val: Boolean, flag: ParserContextFlags) { if (val) { contextFlags |= flag; } else { contextFlags &= ~flag; } } function setStrictModeContext(val: boolean) { setContextFlag(val, ParserContextFlags.StrictMode); } function setDisallowInContext(val: boolean) { setContextFlag(val, ParserContextFlags.DisallowIn); } function setYieldContext(val: boolean) { setContextFlag(val, ParserContextFlags.Yield); } function setGeneratorParameterContext(val: boolean) { setContextFlag(val, ParserContextFlags.GeneratorParameter); } function allowInAnd(func: () => T): T { if (contextFlags & ParserContextFlags.DisallowIn) { setDisallowInContext(false); var result = func(); setDisallowInContext(true); return result; } // no need to do anything special if 'in' is already allowed. return func(); } function disallowInAnd(func: () => T): T { if (contextFlags & ParserContextFlags.DisallowIn) { // no need to do anything special if 'in' is already disallowed. return func(); } setDisallowInContext(true); var result = func(); setDisallowInContext(false); return result; } function doInYieldContext(func: () => T): T { if (contextFlags & ParserContextFlags.Yield) { // no need to do anything special if we're already in the [Yield] context. return func(); } setYieldContext(true); var result = func(); setYieldContext(false); return result; } function doOutsideOfYieldContext(func: () => T): T { if (contextFlags & ParserContextFlags.Yield) { setYieldContext(false); var result = func(); setYieldContext(true); return result; } // no need to do anything special if we're not in the [Yield] context. return func(); } function inYieldContext() { return (contextFlags & ParserContextFlags.Yield) !== 0; } function inStrictModeContext() { return (contextFlags & ParserContextFlags.StrictMode) !== 0; } function inGeneratorParameterContext() { return (contextFlags & ParserContextFlags.GeneratorParameter) !== 0; } function inDisallowInContext() { return (contextFlags & ParserContextFlags.DisallowIn) !== 0; } function getLineStarts(): number[] { return lineStarts || (lineStarts = computeLineStarts(sourceText)); } function getLineAndCharacterFromSourcePosition(position: number) { return getLineAndCharacterOfPosition(getLineStarts(), position); } function getPositionFromSourceLineAndCharacter(line: number, character: number): number { return getPositionFromLineAndCharacter(getLineStarts(), line, character); } function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void { var start = scanner.getTokenPos(); var length = scanner.getTextPos() - start; parseErrorAtPosition(start, length, message, arg0); } function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void { // Don't report another error if it would just be at the same position as the last error. var lastError = lastOrUndefined(sourceFile.parseDiagnostics); if (!lastError || start !== lastError.start) { sourceFile.parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0)); } // Mark that we've encountered an error. We'll set an appropriate bit on the next // node we finish so that it can't be reused incrementally. parseErrorBeforeNextFinishedNode = true; } function scanError(message: DiagnosticMessage) { var pos = scanner.getTextPos(); parseErrorAtPosition(pos, 0, message); } function getNodePos(): number { return scanner.getStartPos(); } function getNodeEnd(): number { return scanner.getStartPos(); } function nextToken(): SyntaxKind { return token = scanner.scan(); } function getTokenPos(pos: number): number { return skipTrivia(sourceText, pos); } function reScanGreaterToken(): SyntaxKind { return token = scanner.reScanGreaterToken(); } function reScanSlashToken(): SyntaxKind { return token = scanner.reScanSlashToken(); } function reScanTemplateToken(): SyntaxKind { return token = scanner.reScanTemplateToken(); } function speculationHelper(callback: () => T, isLookAhead: boolean): T { // Keep track of the state we'll need to rollback to if lookahead fails (or if the // caller asked us to always reset our state). var saveToken = token; var saveParseDiagnosticsLength = sourceFile.parseDiagnostics.length; var saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; // Note: it is not actually necessary to save/restore the context flags here. That's // because the saving/restorating of these flags happens naturally through the recursive // descent nature of our parser. However, we still store this here just so we can // assert that that invariant holds. var saveContextFlags = contextFlags; // If we're only looking ahead, then tell the scanner to only lookahead as well. // Otherwise, if we're actually speculatively parsing, then tell the scanner to do the // same. var result = isLookAhead ? scanner.lookAhead(callback) : scanner.tryScan(callback); Debug.assert(saveContextFlags === contextFlags); // If our callback returned something 'falsy' or we're just looking ahead, // then unconditionally restore us to where we were. if (!result || isLookAhead) { token = saveToken; sourceFile.parseDiagnostics.length = saveParseDiagnosticsLength; parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; } return result; } // Invokes the provided callback then unconditionally restores the parser to the state it // was in immediately prior to invoking the callback. The result of invoking the callback // is returned from this function. function lookAhead(callback: () => T): T { return speculationHelper(callback, /*isLookAhead:*/ true); } // Invokes the provided callback. If the callback returns something falsy, then it restores // the parser to the state it was in immediately prior to invoking the callback. If the // callback returns something truthy, then the parser state is not rolled back. The result // of invoking the callback is returned from this function. function tryParse(callback: () => T): T { return speculationHelper(callback, /*isLookAhead:*/ false); } function isIdentifier(): boolean { if (token === SyntaxKind.Identifier) { return true; } // If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is // considered a keyword and is not an identifier. if (token === SyntaxKind.YieldKeyword && inYieldContext()) { return false; } return inStrictModeContext() ? token > SyntaxKind.LastFutureReservedWord : token > SyntaxKind.LastReservedWord; } function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage): boolean { if (token === kind) { nextToken(); return true; } // Report specific message if provided with one. Otherwise, report generic fallback message. if (diagnosticMessage) { parseErrorAtCurrentToken(diagnosticMessage); } else { parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); } return false; } function parseOptional(t: SyntaxKind): boolean { if (token === t) { nextToken(); return true; } return false; } function parseOptionalToken(t: SyntaxKind): Node { if (token === t) { var node = createNode(t); nextToken(); return finishNode(node); } return undefined; } function canParseSemicolon() { // If there's a real semicolon, then we can always parse it out. if (token === SyntaxKind.SemicolonToken) { return true; } // We can parse out an optional semicolon in ASI cases in the following cases. return token === SyntaxKind.CloseBraceToken || token === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak(); } function parseSemicolon(): boolean { if (canParseSemicolon()) { if (token === SyntaxKind.SemicolonToken) { // consume the semicolon if it was explicitly provided. nextToken(); } return true; } else { return parseExpected(SyntaxKind.SemicolonToken); } } function createNode(kind: SyntaxKind, pos?: number): Node { nodeCount++; var node = new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))(); if (!(pos >= 0)) { pos = scanner.getStartPos(); } node.pos = pos; node.end = pos; return node; } function finishNode(node: T): T { node.end = scanner.getStartPos(); if (contextFlags) { node.parserContextFlags = contextFlags; } // Keep track on the node if we encountered an error while parsing it. If we did, then // we cannot reuse the node incrementally. Once we've marked this node, clear out the // flag so that we don't mark any subsequent nodes. if (parseErrorBeforeNextFinishedNode) { parseErrorBeforeNextFinishedNode = false; node.parserContextFlags |= ParserContextFlags.ThisNodeHasError; } return node; } function createMissingNode(kind: SyntaxKind, reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): Node { if (reportAtCurrentPosition) { parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); } else { parseErrorAtCurrentToken(diagnosticMessage, arg0); } var result = createNode(kind, scanner.getStartPos()); (result).text = ""; return finishNode(result); } function internIdentifier(text: string): string { text = escapeIdentifier(text); return hasProperty(identifiers, text) ? identifiers[text] : (identifiers[text] = text); } // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues // with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for // each identifier in order to reduce memory consumption. function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage): Identifier { identifierCount++; if (isIdentifier) { var node = createNode(SyntaxKind.Identifier); node.text = internIdentifier(scanner.getTokenValue()); nextToken(); return finishNode(node); } return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition:*/ false, diagnosticMessage || Diagnostics.Identifier_expected); } function parseIdentifier(diagnosticMessage?: DiagnosticMessage): Identifier { return createIdentifier(isIdentifier(), diagnosticMessage); } function parseIdentifierName(): Identifier { return createIdentifier(isIdentifierOrKeyword()); } function isLiteralPropertyName(): boolean { return isIdentifierOrKeyword() || token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral; } 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 { // PropertyName[Yield,GeneratorParameter] : // LiteralPropertyName // [+GeneratorParameter] ComputedPropertyName // [~GeneratorParameter] ComputedPropertyName[?Yield] // // ComputedPropertyName[Yield] : // [ AssignmentExpression[In, ?Yield] ] // var node = createNode(SyntaxKind.ComputedPropertyName); parseExpected(SyntaxKind.OpenBracketToken); // We parse any expression (including a comma expression). But the grammar // says that only an assignment expression is allowed, so the grammar checker // will error if it sees a comma expression. var yieldContext = inYieldContext(); if (inGeneratorParameterContext()) { setYieldContext(false); } node.expression = allowInAnd(parseExpression); if (inGeneratorParameterContext()) { setYieldContext(yieldContext); } parseExpected(SyntaxKind.CloseBracketToken); return finishNode(node); } function parseContextualModifier(t: SyntaxKind): boolean { return token === t && tryParse(nextTokenCanFollowModifier); } function nextTokenCanFollowModifier() { nextToken(); return canFollowModifier(); } function parseAnyContextualModifier(): boolean { return isModifier(token) && tryParse(nextTokenCanFollowContextualModifier); } function nextTokenCanFollowContextualModifier() { if (token === SyntaxKind.ConstKeyword) { // 'const' is only a modifier if followed by 'enum'. return nextToken() === SyntaxKind.EnumKeyword; } nextToken(); return canFollowModifier(); } function canFollowModifier(): boolean { return token === SyntaxKind.OpenBracketToken || token === SyntaxKind.AsteriskToken || isLiteralPropertyName(); } // True if positioned at the start of a list element function isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean { var node = currentNode(parsingContext); if (node) { return true; } switch (parsingContext) { case ParsingContext.SourceElements: case ParsingContext.ModuleElements: return isSourceElement(inErrorRecovery); case ParsingContext.BlockStatements: case ParsingContext.SwitchClauseStatements: return isStartOfStatement(inErrorRecovery); case ParsingContext.SwitchClauses: return token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword; case ParsingContext.TypeMembers: return isStartOfTypeMember(); case ParsingContext.ClassMembers: return lookAhead(isClassMemberStart); case ParsingContext.EnumMembers: // 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.OpenBracketToken || token === SyntaxKind.AsteriskToken || isLiteralPropertyName(); case ParsingContext.ObjectBindingElements: return isLiteralPropertyName(); case ParsingContext.TypeReferences: // We want to make sure that the "extends" in "extends foo" or the "implements" in // "implements foo" is not considered a type name. return isIdentifier() && !isNotHeritageClauseTypeName(); case ParsingContext.VariableDeclarations: return isIdentifierOrPattern(); case ParsingContext.ArrayBindingElements: return token === SyntaxKind.CommaToken || isIdentifierOrPattern(); case ParsingContext.TypeParameters: return isIdentifier(); case ParsingContext.ArgumentExpressions: return token === SyntaxKind.CommaToken || isStartOfExpression(); case ParsingContext.ArrayLiteralMembers: return token === SyntaxKind.CommaToken || isStartOfExpression(); case ParsingContext.Parameters: return isStartOfParameter(); case ParsingContext.TypeArguments: case ParsingContext.TupleElementTypes: return token === SyntaxKind.CommaToken || isStartOfType(); case ParsingContext.HeritageClauses: return isHeritageClause(); } Debug.fail("Non-exhaustive case in 'isListElement'."); } function nextTokenIsIdentifier() { nextToken(); return isIdentifier(); } function isNotHeritageClauseTypeName(): boolean { if (token === SyntaxKind.ImplementsKeyword || token === SyntaxKind.ExtendsKeyword) { return lookAhead(nextTokenIsIdentifier); } return false; } // True if positioned at a list terminator function isListTerminator(kind: ParsingContext): boolean { if (token === SyntaxKind.EndOfFileToken) { // Being at the end of the file ends all lists. return true; } switch (kind) { case ParsingContext.ModuleElements: case ParsingContext.BlockStatements: case ParsingContext.SwitchClauses: case ParsingContext.TypeMembers: case ParsingContext.ClassMembers: case ParsingContext.EnumMembers: case ParsingContext.ObjectLiteralMembers: case ParsingContext.ObjectBindingElements: return token === SyntaxKind.CloseBraceToken; case ParsingContext.SwitchClauseStatements: return token === SyntaxKind.CloseBraceToken || token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword; case ParsingContext.TypeReferences: return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.ExtendsKeyword || token === SyntaxKind.ImplementsKeyword; case ParsingContext.VariableDeclarations: return isVariableDeclaratorListTerminator(); case ParsingContext.TypeParameters: // Tokens other than '>' are here for better error recovery return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.OpenBraceToken || token === SyntaxKind.ExtendsKeyword || token === SyntaxKind.ImplementsKeyword; case ParsingContext.ArgumentExpressions: // Tokens other than ')' are here for better error recovery return token === SyntaxKind.CloseParenToken || token === SyntaxKind.SemicolonToken; case ParsingContext.ArrayLiteralMembers: case ParsingContext.TupleElementTypes: case ParsingContext.ArrayBindingElements: return token === SyntaxKind.CloseBracketToken; case ParsingContext.Parameters: // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery return token === SyntaxKind.CloseParenToken || token === SyntaxKind.CloseBracketToken /*|| token === SyntaxKind.OpenBraceToken*/; case ParsingContext.TypeArguments: // Tokens other than '>' are here for better error recovery return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.OpenParenToken; case ParsingContext.HeritageClauses: return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.CloseBraceToken; } } function isVariableDeclaratorListTerminator(): boolean { // If we can consume a semicolon (either explicitly, or with ASI), then consider us done // with parsing the list of variable declarators. if (canParseSemicolon()) { return true; } // in the case where we're parsing the variable declarator of a 'for-in' statement, we // are done if we see an 'in' keyword in front of us. if (token === SyntaxKind.InKeyword) { return true; } // ERROR RECOVERY TWEAK: // For better error recovery, if we see an '=>' then we just stop immediately. We've got an // arrow function here and it's going to be very unlikely that we'll resynchronize and get // another variable declaration. if (token === SyntaxKind.EqualsGreaterThanToken) { return true; } // Keep trying to parse out variable declarators. return false; } // True if positioned at element or terminator of the current list or any enclosing list function isInSomeParsingContext(): boolean { for (var kind = 0; kind < ParsingContext.Count; kind++) { if (parsingContext & (1 << kind)) { if (isListElement(kind, /* inErrorRecovery */ true) || isListTerminator(kind)) { return true; } } } return false; } // Parses a list of elements function parseList(kind: ParsingContext, checkForStrictMode: boolean, parseElement: () => T): NodeArray { var saveParsingContext = parsingContext; parsingContext |= 1 << kind; var result = >[]; result.pos = getNodePos(); var savedStrictModeContext = inStrictModeContext(); while (!isListTerminator(kind)) { if (isListElement(kind, /* inErrorRecovery */ false)) { var element = parseListElement(kind, parseElement); result.push(element); // test elements only if we are not already in strict mode if (checkForStrictMode && !inStrictModeContext()) { if (isPrologueDirective(element)) { if (isUseStrictPrologueDirective(sourceFile, element)) { setStrictModeContext(true); checkForStrictMode = false; } } else { checkForStrictMode = false; } } continue; } if (abortParsingListOrMoveToNextToken(kind)) { break; } } setStrictModeContext(savedStrictModeContext); result.end = getNodeEnd(); parsingContext = saveParsingContext; return result; } function parseListElement(kind: ParsingContext, parseElement: () => T): T { var node = currentNode(kind); if (node) { return consumeNode(node); } return parseElement(); } function currentNode(parsingContext: ParsingContext): Node { // If there is an outstanding parse error that we've encountered, but not attached to // some node, then we cannot get a node from the old source tree. This is because we // want to mark the next node we encounter as being unusable. // // Note: This may be too conservative. Perhaps we could reuse hte node and set the bit // on it (or its leftmost child) as having the error. For now though, being conservative // is nice and likely won't ever affect perf. if (parseErrorBeforeNextFinishedNode) { return undefined; } if (!syntaxCursor) { // if we don't have a cursor, we could never return a node from the old tree. return undefined; } var node = syntaxCursor.currentNode(scanner.getStartPos()); // Can't reuse a missing node. if (nodeIsMissing(node)) { return undefined; } // Can't reuse a node that intersected the change range. if (node.intersectsChange) { return undefined; } // Can't reuse a node that contains a parse error. This is necessary so that we // produce the same set of errors again. if (containsParseError(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. var nodeContextFlags = node.parserContextFlags & ParserContextFlags.ParserGeneratedFlags; if (nodeContextFlags !== 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, parsingContext)) { return undefined; } return node; } function consumeNode(node: Node) { // Move the scanner so it is after the node we just consumed. scanner.setTextPos(node.end); nextToken(); return node; } function canReuseNode(node: Node, parsingContext: ParsingContext): boolean { switch (parsingContext) { case ParsingContext.ModuleElements: return isReusableModuleElement(node); case ParsingContext.ClassMembers: return isReusableClassMember(node); case ParsingContext.SwitchClauses: return isReusableSwitchClause(node); case ParsingContext.BlockStatements: case ParsingContext.SwitchClauseStatements: return isReusableStatement(node); case ParsingContext.EnumMembers: return isReusableEnumMember(node); case ParsingContext.TypeMembers: return isReusableTypeMember(node); case ParsingContext.VariableDeclarations: return isReusableVariableDeclaration(node); case ParsingContext.Parameters: return isReusableParameter(node); // 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 ParsingContext.HeritageClauses: // This would probably be safe to reuse. There is no speculative parsing with // heritage clauses. case ParsingContext.TypeReferences: // 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 ParsingContext.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 ParsingContext.TupleElementTypes: // 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 ParsingContext.TypeArguments: // 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 ParsingContext.ArgumentExpressions: // 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 ParsingContext.ObjectLiteralMembers: } return false; } function isReusableModuleElement(node: Node) { if (node) { switch (node.kind) { case SyntaxKind.ImportDeclaration: case SyntaxKind.ExportAssignment: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.ModuleDeclaration: case SyntaxKind.EnumDeclaration: // Keep in sync with isStatement: case SyntaxKind.FunctionDeclaration: case SyntaxKind.VariableStatement: case SyntaxKind.Block: case SyntaxKind.IfStatement: case SyntaxKind.ExpressionStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.ReturnStatement: case SyntaxKind.SwitchStatement: case SyntaxKind.BreakStatement: case SyntaxKind.ContinueStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForStatement: case SyntaxKind.WhileStatement: case SyntaxKind.WithStatement: case SyntaxKind.EmptyStatement: case SyntaxKind.TryStatement: case SyntaxKind.LabeledStatement: case SyntaxKind.DoStatement: case SyntaxKind.DebuggerStatement: return true; } } return false; } function isReusableClassMember(node: Node) { if (node) { switch (node.kind) { case SyntaxKind.Constructor: case SyntaxKind.IndexSignature: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.PropertyDeclaration: return true; } } return false; } function isReusableSwitchClause(node: Node) { if (node) { switch (node.kind) { case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: return true; } } return false; } function isReusableStatement(node: Node) { if (node) { switch (node.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.VariableStatement: case SyntaxKind.Block: case SyntaxKind.IfStatement: case SyntaxKind.ExpressionStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.ReturnStatement: case SyntaxKind.SwitchStatement: case SyntaxKind.BreakStatement: case SyntaxKind.ContinueStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForStatement: case SyntaxKind.WhileStatement: case SyntaxKind.WithStatement: case SyntaxKind.EmptyStatement: case SyntaxKind.TryStatement: case SyntaxKind.LabeledStatement: case SyntaxKind.DoStatement: case SyntaxKind.DebuggerStatement: return true; } } return false; } function isReusableEnumMember(node: Node) { return node.kind === SyntaxKind.EnumMember; } function isReusableTypeMember(node: Node) { if (node) { switch (node.kind) { case SyntaxKind.ConstructSignature: case SyntaxKind.MethodSignature: case SyntaxKind.IndexSignature: case SyntaxKind.PropertySignature: case SyntaxKind.CallSignature: return true; } } return false; } function isReusableVariableDeclaration(node: Node) { if (node.kind !== SyntaxKind.VariableDeclaration) { 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.initializer === undefined; } function isReusableParameter(node: Node) { // TODO: this most likely needs the same initializer check that // isReusableVariableDeclaration has. return node.kind === SyntaxKind.Parameter; } // Returns true if we should abort parsing. function abortParsingListOrMoveToNextToken(kind: ParsingContext) { parseErrorAtCurrentToken(parsingContextErrors(kind)); if (isInSomeParsingContext()) { return true; } nextToken(); return false; } // Parses a comma-delimited list of elements function parseDelimitedList(kind: ParsingContext, parseElement: () => T): NodeArray { var saveParsingContext = parsingContext; parsingContext |= 1 << kind; var result = >[]; result.pos = getNodePos(); var commaStart = -1; // Meaning the previous token was not a comma while (true) { if (isListElement(kind, /* inErrorRecovery */ false)) { result.push(parseListElement(kind, parseElement)); commaStart = scanner.getTokenPos(); if (parseOptional(SyntaxKind.CommaToken)) { continue; } commaStart = -1; // Back to the state where the last token was not a comma if (isListTerminator(kind)) { break; } parseExpected(SyntaxKind.CommaToken); continue; } if (isListTerminator(kind)) { break; } if (abortParsingListOrMoveToNextToken(kind)) { break; } } // Recording the trailing comma is deliberately done after the previous // loop, and not just if we see a list terminator. This is because the list // may have ended incorrectly, but it is still important to know if there // was a trailing comma. // Check if the last token was a comma. if (commaStart >= 0) { // Always preserve a trailing comma by marking it on the NodeArray result.hasTrailingComma = true; } result.end = getNodeEnd(); parsingContext = saveParsingContext; return result; } function createMissingList(): NodeArray { var pos = getNodePos(); var result = >[]; result.pos = pos; result.end = pos; return result; } function parseBracketedList(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray { if (parseExpected(open)) { var result = parseDelimitedList(kind, parseElement); parseExpected(close); return result; } return createMissingList(); } // The allowReservedWords parameter controls whether reserved words are permitted after the first dot function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { var entity: EntityName = parseIdentifier(diagnosticMessage); while (parseOptional(SyntaxKind.DotToken)) { var node = createNode(SyntaxKind.QualifiedName, entity.pos); node.left = entity; node.right = parseRightSideOfDot(allowReservedWords); entity = finishNode(node); } return entity; } function parseRightSideOfDot(allowIdentifierNames: boolean): Identifier { // Technically a keyword is valid here as all keywords are identifier names. // However, often we'll encounter this in error situations when the keyword // is actually starting another valid construct. // // So, we check for the following specific case: // // name. // keyword identifierNameOrKeyword // // Note: the newlines are important here. For example, if that above code // were rewritten into: // // name.keyword // identifierNameOrKeyword // // Then we would consider it valid. That's because ASI would take effect and // the code would be implicitly: "name.keyword; identifierNameOrKeyword". // In the first case though, ASI will not take effect because there is not a // line terminator after the keyword. if (scanner.hasPrecedingLineBreak() && scanner.isReservedWord()) { var matchesPattern = lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); if (matchesPattern) { // Report that we need an identifier. However, report it right after the dot, // and not on the next token. This is because the next token might actually // be an identifier and the error woudl be quite confusing. return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentToken:*/ true, Diagnostics.Identifier_expected); } } return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); } function parseTokenNode(): T { var node = createNode(token); nextToken(); return finishNode(node); } function parseTemplateExpression(): TemplateExpression { var template = createNode(SyntaxKind.TemplateExpression); template.head = parseLiteralNode(); Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); var templateSpans = >[]; templateSpans.pos = getNodePos(); do { templateSpans.push(parseTemplateSpan()); } while (templateSpans[templateSpans.length - 1].literal.kind === SyntaxKind.TemplateMiddle) templateSpans.end = getNodeEnd(); template.templateSpans = templateSpans; return finishNode(template); } function parseTemplateSpan(): TemplateSpan { var span = createNode(SyntaxKind.TemplateSpan); span.expression = allowInAnd(parseExpression); var literal: LiteralExpression; if (token === SyntaxKind.CloseBraceToken) { reScanTemplateToken() literal = parseLiteralNode(); } else { literal = createMissingNode( SyntaxKind.TemplateTail, /*reportAtCurrentPosition:*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)); } span.literal = literal; return finishNode(span); } function parseLiteralNode(internName?: boolean): LiteralExpression { var node = createNode(token); var text = scanner.getTokenValue(); node.text = internName ? internIdentifier(text) : text; if (scanner.isUnterminated()) { node.isUnterminated = true; } var tokenPos = scanner.getTokenPos(); nextToken(); finishNode(node); // Octal literals are not allowed in strict mode or ES5 // Note that theoretically the following condition would hold true literals like 009, // which is not octal.But because of how the scanner separates the tokens, we would // never get a token like this. Instead, we would get 00 and 9 as two separate tokens. // We also do not need to check for negatives because any prefix operator would be part of a // parent unary expression. if (node.kind === SyntaxKind.NumericLiteral && sourceText.charCodeAt(tokenPos) === CharacterCodes._0 && isOctalDigit(sourceText.charCodeAt(tokenPos + 1))) { node.flags |= NodeFlags.OctalLiteral; } return node; } // TYPES function parseTypeReference(): TypeReferenceNode { var node = createNode(SyntaxKind.TypeReference); node.typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected); if (!scanner.hasPrecedingLineBreak() && token === SyntaxKind.LessThanToken) { node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } return finishNode(node); } function parseTypeQuery(): TypeQueryNode { var node = createNode(SyntaxKind.TypeQuery); parseExpected(SyntaxKind.TypeOfKeyword); node.exprName = parseEntityName(/*allowReservedWords*/ true); return finishNode(node); } function parseTypeParameter(): TypeParameterDeclaration { var node = createNode(SyntaxKind.TypeParameter); node.name = parseIdentifier(); if (parseOptional(SyntaxKind.ExtendsKeyword)) { // It's not uncommon for people to write improper constraints to a generic. If the // user writes a constraint that is an expression and not an actual type, then parse // it out as an expression (so we can recover well), but report that a type is needed // instead. if (isStartOfType() || !isStartOfExpression()) { node.constraint = parseType(); } else { // It was not a type, and it looked like an expression. Parse out an expression // here so we recover well. Note: it is important that we call parseUnaryExpression // and not parseExpression here. If the user has: // // // // We do *not* want to consume the > as we're consuming the expression for "". node.expression = parseUnaryExpressionOrHigher(); } } return finishNode(node); } function parseTypeParameters(): NodeArray { if (token === SyntaxKind.LessThanToken) { return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } } function parseParameterType(): TypeNode { if (parseOptional(SyntaxKind.ColonToken)) { return token === SyntaxKind.StringLiteral ? parseLiteralNode(/*internName:*/ true) : parseType(); } return undefined; } function isStartOfParameter(): boolean { return token === SyntaxKind.DotDotDotToken || isIdentifierOrPattern() || isModifier(token); } function setModifiers(node: Node, modifiers: ModifiersArray) { if (modifiers) { node.flags |= modifiers.flags; node.modifiers = modifiers; } } function parseParameter(): ParameterDeclaration { var node = createNode(SyntaxKind.Parameter); setModifiers(node, parseModifiers()); node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); // SingleNameBinding[Yield,GeneratorParameter] : See 13.2.3 // [+GeneratorParameter]BindingIdentifier[Yield]Initializer[In]opt // [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt node.name = inGeneratorParameterContext() ? doInYieldContext(parseIdentifierOrPattern) : parseIdentifierOrPattern(); if (getFullWidth(node.name) === 0 && node.flags === 0 && isModifier(token)) { // in cases like // 'use strict' // function foo(static) // isParameter('static') === true, because of isModifier('static') // however 'static' is not a legal identifier in a strict mode. // so result of this function will be ParameterDeclaration (flags = 0, name = missing, type = undefined, initializer = undefined) // and current token will not change => parsing of the enclosing parameter list will last till the end of time (or OOM) // to avoid this we'll advance cursor to the next token. nextToken(); } node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); node.type = parseParameterType(); node.initializer = inGeneratorParameterContext() ? doOutsideOfYieldContext(parseParameterInitializer) : parseParameterInitializer(); // Do not check for initializers in an ambient context for parameters. This is not // a grammar error because the grammar allows arbitrary call signatures in // an ambient context. // It is actually not necessary for this to be an error at all. The reason is that // function/constructor implementations are syntactically disallowed in ambient // contexts. In addition, parameter initializers are semantically disallowed in // overload signatures. So parameter initializers are transitively disallowed in // ambient contexts. return finishNode(node); } function parseParameterInitializer() { return parseInitializer(/*inParameter*/ true); } function fillSignature( returnToken: SyntaxKind, yieldAndGeneratorParameterContext: boolean, requireCompleteParameterList: boolean, signature: SignatureDeclaration): void { var returnTokenRequired = returnToken === SyntaxKind.EqualsGreaterThanToken; signature.typeParameters = parseTypeParameters(); signature.parameters = parseParameterList(yieldAndGeneratorParameterContext, requireCompleteParameterList); if (returnTokenRequired) { parseExpected(returnToken); signature.type = parseType(); } else if (parseOptional(returnToken)) { signature.type = parseType(); } } // Note: after careful analysis of the grammar, it does not appear to be possible to // have 'Yield' And 'GeneratorParameter' not in sync. i.e. any production calling // this FormalParameters production either always sets both to true, or always sets // both to false. As such we only have a single parameter to represent both. function parseParameterList(yieldAndGeneratorParameterContext: boolean, requireCompleteParameterList: boolean) { // FormalParameters[Yield,GeneratorParameter] : // ... // // FormalParameter[Yield,GeneratorParameter] : // BindingElement[?Yield, ?GeneratorParameter] // // BindingElement[Yield, GeneratorParameter ] : See 13.2.3 // SingleNameBinding[?Yield, ?GeneratorParameter] // [+GeneratorParameter]BindingPattern[?Yield, GeneratorParameter]Initializer[In]opt // [~GeneratorParameter]BindingPattern[?Yield]Initializer[In, ?Yield]opt // // SingleNameBinding[Yield, GeneratorParameter] : See 13.2.3 // [+GeneratorParameter]BindingIdentifier[Yield]Initializer[In]opt // [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt if (parseExpected(SyntaxKind.OpenParenToken)) { var savedYieldContext = inYieldContext(); var savedGeneratorParameterContext = inGeneratorParameterContext(); setYieldContext(yieldAndGeneratorParameterContext); setGeneratorParameterContext(yieldAndGeneratorParameterContext); var result = parseDelimitedList(ParsingContext.Parameters, parseParameter); setYieldContext(savedYieldContext); setGeneratorParameterContext(savedGeneratorParameterContext); if (!parseExpected(SyntaxKind.CloseParenToken) && requireCompleteParameterList) { // Caller insisted that we had to end with a ) We didn't. So just return // undefined here. return undefined; } return result; } // We didn't even have an open paren. If the caller requires a complete parameter list, // we definitely can't provide that. However, if they're ok with an incomplete one, // then just return an empty set of parameters. return requireCompleteParameterList ? undefined : createMissingList(); } function parseTypeMemberSemicolon() { // Try to parse out an explicit or implicit (ASI) semicolon for a type member. If we // don't have one, then an appropriate error will be reported. if (parseSemicolon()) { return; } // If we don't have a semicolon, then the user may have written a comma instead // accidently (pretty easy to do since commas are so prevalent as list separators). So // just consume the comma and keep going. Note: we'll have already reported the error // about the missing semicolon above. parseOptional(SyntaxKind.CommaToken); } function parseSignatureMember(kind: SyntaxKind): SignatureDeclaration { var node = createNode(kind); if (kind === SyntaxKind.ConstructSignature) { parseExpected(SyntaxKind.NewKeyword); } fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ false, node); parseTypeMemberSemicolon(); return finishNode(node); } function isIndexSignature(): boolean { if (token !== SyntaxKind.OpenBracketToken) { return false; } return lookAhead(isUnambiguouslyIndexSignature); } function isUnambiguouslyIndexSignature() { // The only allowed sequence is: // // [id: // // However, for error recovery, we also check the following cases: // // [... // [id, // [id?, // [id?: // [id?] // [public id // [private id // [protected id // [] // nextToken(); if (token === SyntaxKind.DotDotDotToken || token === SyntaxKind.CloseBracketToken) { return true; } if (isModifier(token)) { nextToken(); if (isIdentifier()) { return true; } } else if (!isIdentifier()) { return false; } else { // Skip the identifier nextToken(); } // A colon signifies a well formed indexer // A comma should be a badly formed indexer because comma expressions are not allowed // in computed properties. if (token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken) { return true; } // Question mark could be an indexer with an optional property, // or it could be a conditional expression in a computed property. if (token !== SyntaxKind.QuestionToken) { return false; } // If any of the following tokens are after the question mark, it cannot // be a conditional expression, so treat it as an indexer. nextToken(); return token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken || token === SyntaxKind.CloseBracketToken; } function parseIndexSignatureDeclaration(modifiers: ModifiersArray): IndexSignatureDeclaration { var fullStart = modifiers ? modifiers.pos : scanner.getStartPos(); var node = createNode(SyntaxKind.IndexSignature, fullStart); setModifiers(node, modifiers); node.parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); node.type = parseTypeAnnotation(); parseTypeMemberSemicolon(); return finishNode(node) } function parsePropertyOrMethodSignature(): Declaration { var fullStart = scanner.getStartPos(); var name = parsePropertyName(); var questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { var method = createNode(SyntaxKind.MethodSignature, fullStart); method.name = name; method.questionToken = questionToken; // Method signatues don't exist in expression contexts. So they have neither // [Yield] nor [GeneratorParameter] fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ false, method); parseTypeMemberSemicolon(); return finishNode(method); } else { var property = createNode(SyntaxKind.PropertySignature, fullStart); property.name = name; property.questionToken = questionToken; property.type = parseTypeAnnotation(); parseTypeMemberSemicolon(); return finishNode(property); } } function isStartOfTypeMember(): boolean { switch (token) { case SyntaxKind.OpenParenToken: case SyntaxKind.LessThanToken: case SyntaxKind.OpenBracketToken: // Both for indexers and computed properties return true; default: if (isModifier(token)) { var result = lookAhead(isStartOfIndexSignatureDeclaration); if (result) { return result; } } return isLiteralPropertyName() && lookAhead(isTypeMemberWithLiteralPropertyName); } } function isStartOfIndexSignatureDeclaration() { while (isModifier(token)) { nextToken(); } return isIndexSignature(); } function isTypeMemberWithLiteralPropertyName() { nextToken(); return token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken || token === SyntaxKind.QuestionToken || token === SyntaxKind.ColonToken || canParseSemicolon(); } function parseTypeMember(): Declaration { switch (token) { case SyntaxKind.OpenParenToken: case SyntaxKind.LessThanToken: return parseSignatureMember(SyntaxKind.CallSignature); case SyntaxKind.OpenBracketToken: // Indexer or computed property return isIndexSignature() ? parseIndexSignatureDeclaration(/*modifiers:*/ undefined) : parsePropertyOrMethodSignature(); case SyntaxKind.NewKeyword: if (lookAhead(isStartOfConstructSignature)) { return parseSignatureMember(SyntaxKind.ConstructSignature); } // fall through. case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: return parsePropertyOrMethodSignature(); default: // Index declaration as allowed as a type member. But as per the grammar, // they also allow modifiers. So we have to check for an index declaration // that might be following modifiers. This ensures that things work properly // when incrementally parsing as the parser will produce the Index declaration // if it has the same text regardless of whether it is inside a class or an // object type. if (isModifier(token)) { var result = tryParse(parseIndexSignatureWithModifiers); if (result) { return result; } } if (isIdentifierOrKeyword()) { return parsePropertyOrMethodSignature(); } } } function parseIndexSignatureWithModifiers() { var modifiers = parseModifiers(); return isIndexSignature() ? parseIndexSignatureDeclaration(modifiers) : undefined; } function isStartOfConstructSignature() { nextToken(); return token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken; } function parseTypeLiteral(): TypeLiteralNode { var node = createNode(SyntaxKind.TypeLiteral); node.members = parseObjectTypeMembers(); return finishNode(node); } function parseObjectTypeMembers(): NodeArray { var members: NodeArray; if (parseExpected(SyntaxKind.OpenBraceToken)) { members = parseList(ParsingContext.TypeMembers, /*checkForStrictMode*/ false, parseTypeMember); parseExpected(SyntaxKind.CloseBraceToken); } else { members = createMissingList(); } return members; } function parseTupleType(): TupleTypeNode { var node = createNode(SyntaxKind.TupleType); node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); return finishNode(node); } function parseParenthesizedType(): ParenthesizedTypeNode { var node = createNode(SyntaxKind.ParenthesizedType); parseExpected(SyntaxKind.OpenParenToken); node.type = parseType(); parseExpected(SyntaxKind.CloseParenToken); return finishNode(node); } function parseFunctionOrConstructorType(kind: SyntaxKind): FunctionOrConstructorTypeNode { var node = createNode(kind); if (kind === SyntaxKind.ConstructorType) { parseExpected(SyntaxKind.NewKeyword); } fillSignature(SyntaxKind.EqualsGreaterThanToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ false, node); return finishNode(node); } function parseKeywordAndNoDot(): TypeNode { var node = parseTokenNode(); return token === SyntaxKind.DotToken ? undefined : node; } function parseNonArrayType(): TypeNode { switch (token) { case SyntaxKind.AnyKeyword: case SyntaxKind.StringKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.BooleanKeyword: // If these are followed by a dot, then parse these out as a dotted type reference instead. var node = tryParse(parseKeywordAndNoDot); return node || parseTypeReference(); case SyntaxKind.VoidKeyword: return parseTokenNode(); case SyntaxKind.TypeOfKeyword: return parseTypeQuery(); case SyntaxKind.OpenBraceToken: return parseTypeLiteral(); case SyntaxKind.OpenBracketToken: return parseTupleType(); case SyntaxKind.OpenParenToken: return parseParenthesizedType(); default: return parseTypeReference(); } } function isStartOfType(): boolean { switch (token) { case SyntaxKind.AnyKeyword: case SyntaxKind.StringKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.VoidKeyword: case SyntaxKind.TypeOfKeyword: case SyntaxKind.OpenBraceToken: case SyntaxKind.OpenBracketToken: case SyntaxKind.LessThanToken: case SyntaxKind.NewKeyword: return true; case SyntaxKind.OpenParenToken: // Only consider '(' the start of a type if followed by ')', '...', an identifier, a modifier, // or something that starts a type. We don't want to consider things like '(1)' a type. return lookAhead(isStartOfParenthesizedOrFunctionType); default: return isIdentifier(); } } function isStartOfParenthesizedOrFunctionType() { nextToken(); return token === SyntaxKind.CloseParenToken || isStartOfParameter() || isStartOfType(); } function parseArrayTypeOrHigher(): TypeNode { var type = parseNonArrayType(); while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) { parseExpected(SyntaxKind.CloseBracketToken); var node = createNode(SyntaxKind.ArrayType, type.pos); node.elementType = type; type = finishNode(node); } return type; } function parseUnionTypeOrHigher(): TypeNode { var type = parseArrayTypeOrHigher(); if (token === SyntaxKind.BarToken) { var types = >[type]; types.pos = type.pos; while (parseOptional(SyntaxKind.BarToken)) { types.push(parseArrayTypeOrHigher()); } types.end = getNodeEnd(); var node = createNode(SyntaxKind.UnionType, type.pos); node.types = types; type = finishNode(node); } return type; } function isStartOfFunctionType(): boolean { if (token === SyntaxKind.LessThanToken) { return true; } return token === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType); } function isUnambiguouslyStartOfFunctionType() { nextToken(); if (token === SyntaxKind.CloseParenToken || token === SyntaxKind.DotDotDotToken) { // ( ) // ( ... return true; } if (isIdentifier() || isModifier(token)) { nextToken(); if (token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken || token === SyntaxKind.QuestionToken || token === SyntaxKind.EqualsToken || isIdentifier() || isModifier(token)) { // ( id : // ( id , // ( id ? // ( id = // ( modifier id return true; } if (token === SyntaxKind.CloseParenToken) { nextToken(); if (token === SyntaxKind.EqualsGreaterThanToken) { // ( id ) => return true; } } } return false; } function parseType(): TypeNode { // The rules about 'yield' only apply to actual code/expression contexts. They don't // apply to 'type' contexts. So we disable these parameters here before moving on. var savedYieldContext = inYieldContext(); var savedGeneratorParameterContext = inGeneratorParameterContext(); setYieldContext(false); setGeneratorParameterContext(false); var result = parseTypeWorker(); setYieldContext(savedYieldContext); setGeneratorParameterContext(savedGeneratorParameterContext); return result; } function parseTypeWorker(): TypeNode { if (isStartOfFunctionType()) { return parseFunctionOrConstructorType(SyntaxKind.FunctionType); } if (token === SyntaxKind.NewKeyword) { return parseFunctionOrConstructorType(SyntaxKind.ConstructorType); } return parseUnionTypeOrHigher(); } function parseTypeAnnotation(): TypeNode { return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined; } // EXPRESSIONS function isStartOfExpression(): boolean { switch (token) { case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateHead: case SyntaxKind.OpenParenToken: case SyntaxKind.OpenBracketToken: case SyntaxKind.OpenBraceToken: case SyntaxKind.FunctionKeyword: case SyntaxKind.NewKeyword: case SyntaxKind.SlashToken: case SyntaxKind.SlashEqualsToken: case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: case SyntaxKind.ExclamationToken: case SyntaxKind.DeleteKeyword: case SyntaxKind.TypeOfKeyword: case SyntaxKind.VoidKeyword: case SyntaxKind.PlusPlusToken: case SyntaxKind.MinusMinusToken: case SyntaxKind.LessThanToken: case SyntaxKind.Identifier: case SyntaxKind.YieldKeyword: // Yield always starts an expression. Either it is an identifier (in which case // it is definitely an expression). Or it's a keyword (either because we're in // a generator, or in strict mode (or both)) and it started a yield expression. return true; default: // Error tolerance. If we see the start of some binary operator, we consider // that the start of an expression. That way we'll parse out a missing identifier, // give a good message about an identifier being missing, and then consume the // rest of the binary expression. if (isBinaryOperator()) { return true; } return isIdentifier(); } } function isStartOfExpressionStatement(): boolean { // As per the grammar, neither '{' nor 'function' can start an expression statement. return token !== SyntaxKind.OpenBraceToken && token !== SyntaxKind.FunctionKeyword && isStartOfExpression(); } function parseExpression(): Expression { // Expression[in]: // AssignmentExpression[in] // Expression[in] , AssignmentExpression[in] var expr = parseAssignmentExpressionOrHigher(); while (parseOptional(SyntaxKind.CommaToken)) { expr = makeBinaryExpression(expr, SyntaxKind.CommaToken, parseAssignmentExpressionOrHigher()); } return expr; } function parseInitializer(inParameter: boolean): Expression { if (token !== SyntaxKind.EqualsToken) { // It's not uncommon during typing for the user to miss writing the '=' token. Check if // there is no newline after the last token and if we're on an expression. If so, parse // this as an equals-value clause with a missing equals. // NOTE: There are two places where we allow equals-value clauses. The first is in a // variable declarator. The second is with a parameter. For variable declarators // it's more likely that a { would be a allowed (as an object literal). While this // is also allowed for parameters, the risk is that we consume the { as an object // literal when it really will be for the block following the parameter. if (scanner.hasPrecedingLineBreak() || (inParameter && token === SyntaxKind.OpenBraceToken) || !isStartOfExpression()) { // preceding line break, open brace in a parameter (likely a function body) or current token is not an expression - // do not try to parse initializer return undefined; } } // Initializer[In, Yield] : // = AssignmentExpression[?In, ?Yield] parseExpected(SyntaxKind.EqualsToken); return parseAssignmentExpressionOrHigher(); } function parseAssignmentExpressionOrHigher(): Expression { // AssignmentExpression[in,yield]: // 1) ConditionalExpression[?in,?yield] // 2) LeftHandSideExpression = AssignmentExpression[?in,?yield] // 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield] // 4) ArrowFunctionExpression[?in,?yield] // 5) [+Yield] YieldExpression[?In] // // Note: for ease of implementation we treat productions '2' and '3' as the same thing. // (i.e. they're both BinaryExpressions with an assignment operator in it). // First, do the simple check if we have a YieldExpression (production '5'). if (isYieldExpression()) { return parseYieldExpression(); } // Then, check if we have an arrow function (production '4') that starts with a parenthesized // parameter list. If we do, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is // not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done // with AssignmentExpression if we see one. var arrowExpression = tryParseParenthesizedArrowFunctionExpression(); if (arrowExpression) { return arrowExpression; } // Now try to see if we're in production '1', '2' or '3'. A conditional expression can // start with a LogicalOrExpression, while the assignment productions can only start with // LeftHandSideExpressions. // // So, first, we try to just parse out a BinaryExpression. If we get something that is a // LeftHandSide or higher, then we can try to parse out the assignment expression part. // Otherwise, we try to parse out the conditional expression bit. We want to allow any // binary expression here, so we pass in the 'lowest' precedence here so that it matches // and consumes anything. var expr = parseBinaryExpressionOrHigher(/*precedence:*/ 0); // To avoid a look-ahead, we did not handle the case of an arrow function with a single un-parenthesized // parameter ('x => ...') above. We handle it here by checking if the parsed expression was a single // identifier and the current token is an arrow. if (expr.kind === SyntaxKind.Identifier && token === SyntaxKind.EqualsGreaterThanToken) { return parseSimpleArrowFunctionExpression(expr); } // Now see if we might be in cases '2' or '3'. // If the expression was a LHS expression, and we have an assignment operator, then // we're in '2' or '3'. Consume the assignment and return. // // Note: we call reScanGreaterToken so that we get an appropriately merged token // for cases like > > = becoming >>= if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) { var operator = token; nextToken(); return makeBinaryExpression(expr, operator, parseAssignmentExpressionOrHigher()); } // It wasn't an assignment or a lambda. This is a conditional expression: return parseConditionalExpressionRest(expr); } function isYieldExpression(): boolean { if (token === SyntaxKind.YieldKeyword) { // If we have a 'yield' keyword, and htis is a context where yield expressions are // allowed, then definitely parse out a yield expression. if (inYieldContext()) { return true; } if (inStrictModeContext()) { // If we're in strict mode, then 'yield' is a keyword, could only ever start // a yield expression. return true; } // We're in a context where 'yield expr' is not allowed. However, if we can // definitely tell that the user was trying to parse a 'yield expr' and not // just a normal expr that start with a 'yield' identifier, then parse out // a 'yield expr'. We can then report an error later that they are only // allowed in generator expressions. // // for example, if we see 'yield(foo)', then we'll have to treat that as an // invocation expression of something called 'yield'. However, if we have // 'yield foo' then that is not legal as a normal expression, so we can // definitely recognize this as a yield expression. // // for now we just check if the next token is an identifier. More heuristics // can be added here later as necessary. We just need to make sure that we // don't accidently consume something legal. return lookAhead(nextTokenIsIdentifierOnSameLine); } return false; } function nextTokenIsIdentifierOnSameLine() { nextToken(); return !scanner.hasPrecedingLineBreak() && isIdentifier() } function parseYieldExpression(): YieldExpression { var node = createNode(SyntaxKind.YieldExpression); // YieldExpression[In] : // yield // yield [no LineTerminator here] [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] // yield [no LineTerminator here] * [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] nextToken(); if (!scanner.hasPrecedingLineBreak() && (token === SyntaxKind.AsteriskToken || isStartOfExpression())) { node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); node.expression = parseAssignmentExpressionOrHigher(); return finishNode(node); } else { // if the next token is not on the same line as yield. or we don't have an '*' or // the start of an expressin, then this is just a simple "yield" expression. return finishNode(node); } } function parseSimpleArrowFunctionExpression(identifier: Identifier): Expression { Debug.assert(token === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>"); var node = createNode(SyntaxKind.ArrowFunction, identifier.pos); var parameter = createNode(SyntaxKind.Parameter, identifier.pos); parameter.name = identifier; finishNode(parameter); node.parameters = >[parameter]; node.parameters.pos = parameter.pos; node.parameters.end = parameter.end; parseExpected(SyntaxKind.EqualsGreaterThanToken); node.body = parseArrowFunctionExpressionBody(); return finishNode(node); } function tryParseParenthesizedArrowFunctionExpression(): Expression { var triState = isParenthesizedArrowFunctionExpression(); if (triState === Tristate.False) { // It's definitely not a parenthesized arrow function expression. return undefined; } // If we definitely have an arrow function, then we can just parse one, not requiring a // following => or { token. Otherwise, we *might* have an arrow function. Try to parse // it out, but don't allow any ambiguity, and return 'undefined' if this could be an // expression instead. var arrowFunction = triState === Tristate.True ? parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity:*/ true) : tryParse(parsePossibleParenthesizedArrowFunctionExpressionHead); if (!arrowFunction) { // Didn't appear to actually be a parenthesized arrow function. Just bail out. return undefined; } // If we have an arrow, then try to parse the body. Even if not, try to parse if we // have an opening brace, just in case we're in an error state. if (parseExpected(SyntaxKind.EqualsGreaterThanToken) || token === SyntaxKind.OpenBraceToken) { arrowFunction.body = parseArrowFunctionExpressionBody(); } else { // If not, we're probably better off bailing out and returning a bogus function expression. arrowFunction.body = parseIdentifier(); } return finishNode(arrowFunction); } // True -> We definitely expect a parenthesized arrow function here. // False -> There *cannot* be a parenthesized arrow function here. // Unknown -> There *might* be a parenthesized arrow function here. // Speculatively look ahead to be sure, and rollback if not. function isParenthesizedArrowFunctionExpression(): Tristate { if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { return lookAhead(isParenthesizedArrowFunctionExpressionWorker); } if (token === SyntaxKind.EqualsGreaterThanToken) { // ERROR RECOVERY TWEAK: // If we see a standalone => try to parse it as an arrow function expression as that's // likely what the user intended to write. return Tristate.True; } // Definitely not a parenthesized arrow function. return Tristate.False; } function isParenthesizedArrowFunctionExpressionWorker() { var first = token; var second = nextToken(); if (first === SyntaxKind.OpenParenToken) { if (second === SyntaxKind.CloseParenToken) { // Simple cases: "() =>", "(): ", and "() {". // This is an arrow function with no parameters. // The last one is not actually an arrow function, // but this is probably what the user intended. var third = nextToken(); switch (third) { case SyntaxKind.EqualsGreaterThanToken: case SyntaxKind.ColonToken: case SyntaxKind.OpenBraceToken: return Tristate.True; default: return Tristate.False; } } // Simple case: "(..." // This is an arrow function with a rest parameter. if (second === SyntaxKind.DotDotDotToken) { return Tristate.True; } // If we had "(" followed by something that's not an identifier, // then this definitely doesn't look like a lambda. // Note: we could be a little more lenient and allow // "(public" or "(private". These would not ever actually be allowed, // but we could provide a good error message instead of bailing out. if (!isIdentifier()) { return Tristate.False; } // If we have something like "(a:", then we must have a // type-annotated parameter in an arrow function expression. if (nextToken() === SyntaxKind.ColonToken) { return Tristate.True; } // This *could* be a parenthesized arrow function. // Return Unknown to let the caller know. return Tristate.Unknown; } else { Debug.assert(first === SyntaxKind.LessThanToken); // If we have "<" not followed by an identifier, // then this definitely is not an arrow function. if (!isIdentifier()) { return Tristate.False; } // This *could* be a parenthesized arrow function. return Tristate.Unknown; } } function parsePossibleParenthesizedArrowFunctionExpressionHead() { return parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity:*/ false); } function parseParenthesizedArrowFunctionExpressionHead(allowAmbiguity: boolean): FunctionExpression { var node = createNode(SyntaxKind.ArrowFunction); // Arrow functions are never generators. // // If we're speculatively parsing a signature for a parenthesized arrow function, then // we have to have a complete parameter list. Otherwise we might see something like // a => (b => c) // And think that "(b =>" was actually a parenthesized arrow function with a missing // close paren. fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ !allowAmbiguity, node); // If we couldn't get parameters, we definitely could not parse out an arrow function. if (!node.parameters) { return undefined; } // Parsing a signature isn't enough. // Parenthesized arrow signatures often look like other valid expressions. // For instance: // - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value. // - "(x,y)" is a comma expression parsed as a signature with two parameters. // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. // // So we need just a bit of lookahead to ensure that it can only be a signature. if (!allowAmbiguity && token !== SyntaxKind.EqualsGreaterThanToken && token !== SyntaxKind.OpenBraceToken) { // Returning undefined here will cause our caller to rewind to where we started from. return undefined; } return node; } function parseArrowFunctionExpressionBody(): Block | Expression { if (token === SyntaxKind.OpenBraceToken) { return parseFunctionBlock(/*allowYield:*/ false, /* ignoreMissingOpenBrace */ false); } if (isStartOfStatement(/*inErrorRecovery:*/ true) && !isStartOfExpressionStatement() && token !== SyntaxKind.FunctionKeyword) { // Check if we got a plain statement (i.e. no expression-statements, no functions expressions/declarations) // // Here we try to recover from a potential error situation in the case where the // user meant to supply a block. For example, if the user wrote: // // a => // var v = 0; // } // // they may be missing an open brace. Check to see if that's the case so we can // try to recover better. If we don't do this, then the next close curly we see may end // up preemptively closing the containing construct. // // Note: even when 'ignoreMissingOpenBrace' is passed as true, parseBody will still error. return parseFunctionBlock(/*allowYield:*/ false, /* ignoreMissingOpenBrace */ true); } return parseAssignmentExpressionOrHigher(); } function parseConditionalExpressionRest(leftOperand: Expression): Expression { // Note: we are passed in an expression which was produced from parseBinaryExpressionOrHigher. if (!parseOptional(SyntaxKind.QuestionToken)) { return leftOperand; } // Note: we explicitly 'allowIn' in the whenTrue part of the condition expression, and // we do not that for the 'whenFalse' part. var node = createNode(SyntaxKind.ConditionalExpression, leftOperand.pos); node.condition = leftOperand; node.whenTrue = allowInAnd(parseAssignmentExpressionOrHigher); parseExpected(SyntaxKind.ColonToken); node.whenFalse = parseAssignmentExpressionOrHigher(); return finishNode(node); } function parseBinaryExpressionOrHigher(precedence: number): Expression { var leftOperand = parseUnaryExpressionOrHigher(); return parseBinaryExpressionRest(precedence, leftOperand); } function parseBinaryExpressionRest(precedence: number, leftOperand: Expression): Expression { while (true) { // We either have a binary operator here, or we're finished. We call // reScanGreaterToken so that we merge token sequences like > and = into >= reScanGreaterToken(); var newPrecedence = getBinaryOperatorPrecedence(); // Check the precedence to see if we should "take" this operator if (newPrecedence <= precedence) { break; } if (token === SyntaxKind.InKeyword && inDisallowInContext()) { break; } var operator = token; nextToken(); leftOperand = makeBinaryExpression(leftOperand, operator, parseBinaryExpressionOrHigher(newPrecedence)); } return leftOperand; } function isBinaryOperator() { if (inDisallowInContext() && token === SyntaxKind.InKeyword) { return false; } return getBinaryOperatorPrecedence() > 0; } function getBinaryOperatorPrecedence(): number { switch (token) { case SyntaxKind.BarBarToken: return 1; case SyntaxKind.AmpersandAmpersandToken: return 2; case SyntaxKind.BarToken: return 3; case SyntaxKind.CaretToken: return 4; case SyntaxKind.AmpersandToken: return 5; case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: return 6; case SyntaxKind.LessThanToken: case SyntaxKind.GreaterThanToken: case SyntaxKind.LessThanEqualsToken: case SyntaxKind.GreaterThanEqualsToken: case SyntaxKind.InstanceOfKeyword: case SyntaxKind.InKeyword: return 7; case SyntaxKind.LessThanLessThanToken: case SyntaxKind.GreaterThanGreaterThanToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return 8; case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: return 9; case SyntaxKind.AsteriskToken: case SyntaxKind.SlashToken: case SyntaxKind.PercentToken: return 10; } // -1 is lower than all other precedences. Returning it will cause binary expression // parsing to stop. return -1; } function makeBinaryExpression(left: Expression, operator: SyntaxKind, right: Expression): BinaryExpression { var node = createNode(SyntaxKind.BinaryExpression, left.pos); node.left = left; node.operator = operator; node.right = right; return finishNode(node); } function parsePrefixUnaryExpression() { var node = createNode(SyntaxKind.PrefixUnaryExpression); node.operator = token; nextToken(); node.operand = parseUnaryExpressionOrHigher(); return finishNode(node); } function parseDeleteExpression() { var node = createNode(SyntaxKind.DeleteExpression); nextToken(); node.expression = parseUnaryExpressionOrHigher(); return finishNode(node); } function parseTypeOfExpression() { var node = createNode(SyntaxKind.TypeOfExpression); nextToken(); node.expression = parseUnaryExpressionOrHigher(); return finishNode(node); } function parseVoidExpression() { var node = createNode(SyntaxKind.VoidExpression); nextToken(); node.expression = parseUnaryExpressionOrHigher(); return finishNode(node); } function parseUnaryExpressionOrHigher(): UnaryExpression { switch (token) { case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: case SyntaxKind.ExclamationToken: case SyntaxKind.PlusPlusToken: case SyntaxKind.MinusMinusToken: return parsePrefixUnaryExpression(); case SyntaxKind.DeleteKeyword: return parseDeleteExpression(); case SyntaxKind.TypeOfKeyword: return parseTypeOfExpression(); case SyntaxKind.VoidKeyword: return parseVoidExpression(); case SyntaxKind.LessThanToken: return parseTypeAssertion(); default: return parsePostfixExpressionOrHigher(); } } function parsePostfixExpressionOrHigher(): PostfixExpression { var expression = parseLeftHandSideExpressionOrHigher(); Debug.assert(isLeftHandSideExpression(expression)); if ((token === SyntaxKind.PlusPlusToken || token === SyntaxKind.MinusMinusToken) && !scanner.hasPrecedingLineBreak()) { var node = createNode(SyntaxKind.PostfixUnaryExpression, expression.pos); node.operand = expression; node.operator = token; nextToken(); return finishNode(node); } return expression; } function parseLeftHandSideExpressionOrHigher(): LeftHandSideExpression { // Original Ecma: // LeftHandSideExpression: See 11.2 // NewExpression // CallExpression // // Our simplification: // // LeftHandSideExpression: See 11.2 // MemberExpression // CallExpression // // See comment in parseMemberExpressionOrHigher on how we replaced NewExpression with // MemberExpression to make our lives easier. // // to best understand the below code, it's important to see how CallExpression expands // out into its own productions: // // CallExpression: // MemberExpression Arguments // CallExpression Arguments // CallExpression[Expression] // CallExpression.IdentifierName // super ( ArgumentListopt ) // super.IdentifierName // // Because of the recursion in these calls, we need to bottom out first. There are two // bottom out states we can run into. Either we see 'super' which must start either of // the last two CallExpression productions. Or we have a MemberExpression which either // completes the LeftHandSideExpression, or starts the beginning of the first four // CallExpression productions. var expression = token === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher(); // Now, we *may* be complete. However, we might have consumed the start of a // CallExpression. As such, we need to consume the rest of it here to be complete. return parseCallExpressionRest(expression); } function parseMemberExpressionOrHigher(): MemberExpression { // Note: to make our lives simpler, we decompose the the NewExpression productions and // place ObjectCreationExpression and FunctionExpression into PrimaryExpression. // like so: // // PrimaryExpression : See 11.1 // this // Identifier // Literal // ArrayLiteral // ObjectLiteral // (Expression) // FunctionExpression // new MemberExpression Arguments? // // MemberExpression : See 11.2 // PrimaryExpression // MemberExpression[Expression] // MemberExpression.IdentifierName // // CallExpression : See 11.2 // MemberExpression // CallExpression Arguments // CallExpression[Expression] // CallExpression.IdentifierName // // Technically this is ambiguous. i.e. CallExpression defines: // // CallExpression: // CallExpression Arguments // // If you see: "new Foo()" // // Then that could be treated as a single ObjectCreationExpression, or it could be // treated as the invocation of "new Foo". We disambiguate that in code (to match // the original grammar) by making sure that if we see an ObjectCreationExpression // we always consume arguments if they are there. So we treat "new Foo()" as an // object creation only, and not at all as an invocation) Another way to think // about this is that for every "new" that we see, we will consume an argument list if // it is there as part of the *associated* object creation node. Any additional // argument lists we see, will become invocation expressions. // // Because there are no other places in the grammar now that refer to FunctionExpression // or ObjectCreationExpression, it is safe to push down into the PrimaryExpression // production. // // Because CallExpression and MemberExpression are left recursive, we need to bottom out // of the recursion immediately. So we parse out a primary expression to start with. var expression = parsePrimaryExpression(); return parseMemberExpressionRest(expression); } function parseSuperExpression(): MemberExpression { var expression = parseTokenNode(); if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.DotToken) { return expression; } // If we have seen "super" it must be followed by '(' or '.'. // If it wasn't then just try to parse out a '.' and report an error. var node = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); node.expression = expression; parseExpected(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access); node.name = parseRightSideOfDot(/*allowIdentifierNames:*/ true); return finishNode(node); } function parseTypeAssertion(): TypeAssertion { var node = createNode(SyntaxKind.TypeAssertionExpression); parseExpected(SyntaxKind.LessThanToken); node.type = parseType(); parseExpected(SyntaxKind.GreaterThanToken); node.expression = parseUnaryExpressionOrHigher(); return finishNode(node); } function parseMemberExpressionRest(expression: LeftHandSideExpression): MemberExpression { while (true) { var dotOrBracketStart = scanner.getTokenPos(); if (parseOptional(SyntaxKind.DotToken)) { var propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); propertyAccess.expression = expression; propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames:*/ true); expression = finishNode(propertyAccess); continue; } if (parseOptional(SyntaxKind.OpenBracketToken)) { var indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); indexedAccess.expression = expression; // It's not uncommon for a user to write: "new Type[]". // Check for that common pattern and report a better error message. if (token !== SyntaxKind.CloseBracketToken) { indexedAccess.argumentExpression = allowInAnd(parseExpression); if (indexedAccess.argumentExpression.kind === SyntaxKind.StringLiteral || indexedAccess.argumentExpression.kind === SyntaxKind.NumericLiteral) { var literal = indexedAccess.argumentExpression; literal.text = internIdentifier(literal.text); } } parseExpected(SyntaxKind.CloseBracketToken); expression = finishNode(indexedAccess); continue; } if (token === SyntaxKind.NoSubstitutionTemplateLiteral || token === SyntaxKind.TemplateHead) { var tagExpression = createNode(SyntaxKind.TaggedTemplateExpression, expression.pos); tagExpression.tag = expression; tagExpression.template = token === SyntaxKind.NoSubstitutionTemplateLiteral ? parseLiteralNode() : parseTemplateExpression(); expression = finishNode(tagExpression); continue; } return expression; } } function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { expression = parseMemberExpressionRest(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 // part of an arithmetic expression. Break out so we consume it higher in the // stack. var typeArguments = tryParse(parseTypeArgumentsInExpression); if (!typeArguments) { return expression; } var callExpr = createNode(SyntaxKind.CallExpression, expression.pos); callExpr.expression = expression; callExpr.typeArguments = typeArguments; callExpr.arguments = parseArgumentList(); expression = finishNode(callExpr); continue; } else if (token === SyntaxKind.OpenParenToken) { var callExpr = createNode(SyntaxKind.CallExpression, expression.pos); callExpr.expression = expression; callExpr.arguments = parseArgumentList(); expression = finishNode(callExpr); continue; } return expression; } } function parseArgumentList() { parseExpected(SyntaxKind.OpenParenToken); var result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); parseExpected(SyntaxKind.CloseParenToken); return result; } function parseTypeArgumentsInExpression() { if (!parseOptional(SyntaxKind.LessThanToken)) { return undefined; } var typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); if (!parseExpected(SyntaxKind.GreaterThanToken)) { // If it doesn't have the closing > then it's definitely not an type argument list. return undefined; } // If we have a '<', then only parse this as a arugment list if the type arguments // are complete and we have an open paren. if we don't, rewind and return nothing. return typeArguments && canFollowTypeArgumentsInExpression() ? typeArguments : undefined; } function canFollowTypeArgumentsInExpression(): boolean { switch (token) { case SyntaxKind.OpenParenToken: // foo( // this case are the only case where this token can legally follow a type argument // list. So we definitely want to treat this as a type arg list. case SyntaxKind.DotToken: // foo. case SyntaxKind.CloseParenToken: // foo) case SyntaxKind.CloseBracketToken: // foo] case SyntaxKind.ColonToken: // foo: case SyntaxKind.SemicolonToken: // foo; case SyntaxKind.CommaToken: // foo, case SyntaxKind.QuestionToken: // foo? case SyntaxKind.EqualsEqualsToken: // foo == case SyntaxKind.EqualsEqualsEqualsToken: // foo === case SyntaxKind.ExclamationEqualsToken: // foo != case SyntaxKind.ExclamationEqualsEqualsToken: // foo !== case SyntaxKind.AmpersandAmpersandToken: // foo && case SyntaxKind.BarBarToken: // foo || case SyntaxKind.CaretToken: // foo ^ case SyntaxKind.AmpersandToken: // foo & case SyntaxKind.BarToken: // foo | case SyntaxKind.CloseBraceToken: // foo } case SyntaxKind.EndOfFileToken: // foo // these cases can't legally follow a type arg list. However, they're not legal // expressions either. The user is probably in the middle of a generic type. So // treat it as such. return true; default: // Anything else treat as an expression. return false; } } function parsePrimaryExpression(): PrimaryExpression { switch (token) { case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: return parseTokenNode(); case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: return parseLiteralNode(); case SyntaxKind.OpenParenToken: return parseParenthesizedExpression(); case SyntaxKind.OpenBracketToken: return parseArrayLiteralExpression(); case SyntaxKind.OpenBraceToken: return parseObjectLiteralExpression(); case SyntaxKind.FunctionKeyword: return parseFunctionExpression(); case SyntaxKind.NewKeyword: return parseNewExpression(); case SyntaxKind.SlashToken: case SyntaxKind.SlashEqualsToken: if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) { return parseLiteralNode(); } break; case SyntaxKind.TemplateHead: return parseTemplateExpression(); } return parseIdentifier(Diagnostics.Expression_expected); } function parseParenthesizedExpression(): ParenthesizedExpression { var node = createNode(SyntaxKind.ParenthesizedExpression); parseExpected(SyntaxKind.OpenParenToken); node.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); return finishNode(node); } function parseAssignmentExpressionOrOmittedExpression(): Expression { return token === SyntaxKind.CommaToken ? createNode(SyntaxKind.OmittedExpression) : parseAssignmentExpressionOrHigher(); } function parseArrayLiteralElement(): Expression { return parseAssignmentExpressionOrOmittedExpression(); } function parseArgumentExpression(): Expression { return allowInAnd(parseAssignmentExpressionOrOmittedExpression); } function parseArrayLiteralExpression(): ArrayLiteralExpression { var node = createNode(SyntaxKind.ArrayLiteralExpression); parseExpected(SyntaxKind.OpenBracketToken); if (scanner.hasPrecedingLineBreak()) node.flags |= NodeFlags.MultiLine; node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArrayLiteralElement); parseExpected(SyntaxKind.CloseBracketToken); return finishNode(node); } function tryParseAccessorDeclaration(fullStart: number, modifiers: ModifiersArray): AccessorDeclaration { if (parseContextualModifier(SyntaxKind.GetKeyword)) { return parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, modifiers); } else if (parseContextualModifier(SyntaxKind.SetKeyword)) { return parseAccessorDeclaration(SyntaxKind.SetAccessor, fullStart, modifiers); } return undefined; } function parseObjectLiteralElement(): ObjectLiteralElement { var fullStart = scanner.getStartPos(); var modifiers = parseModifiers(); var accessor = tryParseAccessorDeclaration(fullStart, modifiers); if (accessor) { return accessor; } var asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); var tokenIsIdentifier = isIdentifier(); var nameToken = token; var propertyName = parsePropertyName(); // Disallowing of optional property assignments happens in the grammar checker. var questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { return parseMethodDeclaration(fullStart, modifiers, asteriskToken, propertyName, questionToken); } // Parse to check if it is short-hand property assignment or normal property assignment if ((token === SyntaxKind.CommaToken || token === SyntaxKind.CloseBraceToken) && tokenIsIdentifier) { var shorthandDeclaration = createNode(SyntaxKind.ShorthandPropertyAssignment, fullStart); shorthandDeclaration.name = propertyName; shorthandDeclaration.questionToken = questionToken; return finishNode(shorthandDeclaration); } else { var propertyAssignment = createNode(SyntaxKind.PropertyAssignment, fullStart); propertyAssignment.name = propertyName; propertyAssignment.questionToken = questionToken; parseExpected(SyntaxKind.ColonToken); propertyAssignment.initializer = allowInAnd(parseAssignmentExpressionOrHigher); return finishNode(propertyAssignment); } } function parseObjectLiteralExpression(): ObjectLiteralExpression { var node = createNode(SyntaxKind.ObjectLiteralExpression); parseExpected(SyntaxKind.OpenBraceToken); if (scanner.hasPrecedingLineBreak()) { node.flags |= NodeFlags.MultiLine; } node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement); parseExpected(SyntaxKind.CloseBraceToken); return finishNode(node); } function parseFunctionExpression(): FunctionExpression { // GeneratorExpression : // function * BindingIdentifier[Yield]opt (FormalParameters[Yield, GeneratorParameter]) { GeneratorBody[Yield] } // FunctionExpression: // function BindingIdentifieropt(FormalParameters) { FunctionBody } var 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); return finishNode(node); } function parseOptionalIdentifier() { return isIdentifier() ? parseIdentifier() : undefined; } function parseNewExpression(): NewExpression { var node = createNode(SyntaxKind.NewExpression); parseExpected(SyntaxKind.NewKeyword); node.expression = parseMemberExpressionOrHigher(); node.typeArguments = tryParse(parseTypeArgumentsInExpression); if (node.typeArguments || token === SyntaxKind.OpenParenToken) { node.arguments = parseArgumentList(); } return finishNode(node); } // STATEMENTS function parseBlock(ignoreMissingOpenBrace: boolean, checkForStrictMode: boolean, diagnosticMessage?: DiagnosticMessage): Block { var node = createNode(SyntaxKind.Block); if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) { node.statements = parseList(ParsingContext.BlockStatements, checkForStrictMode, parseStatement); parseExpected(SyntaxKind.CloseBraceToken); } else { node.statements = createMissingList(); } return finishNode(node); } function parseFunctionBlock(allowYield: boolean, ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { var savedYieldContext = inYieldContext(); setYieldContext(allowYield); var block = parseBlock(ignoreMissingOpenBrace, /*checkForStrictMode*/ true, diagnosticMessage); setYieldContext(savedYieldContext); return block; } function parseEmptyStatement(): Statement { var node = createNode(SyntaxKind.EmptyStatement); parseExpected(SyntaxKind.SemicolonToken); return finishNode(node); } function parseIfStatement(): IfStatement { var node = createNode(SyntaxKind.IfStatement); parseExpected(SyntaxKind.IfKeyword); parseExpected(SyntaxKind.OpenParenToken); node.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); node.thenStatement = parseStatement(); node.elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined; return finishNode(node); } function parseDoStatement(): DoStatement { var node = createNode(SyntaxKind.DoStatement); parseExpected(SyntaxKind.DoKeyword); node.statement = parseStatement(); parseExpected(SyntaxKind.WhileKeyword); parseExpected(SyntaxKind.OpenParenToken); node.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); // From: https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html // 157 min --- All allen at wirfs-brock.com CONF --- "do{;}while(false)false" prohibited in // spec but allowed in consensus reality. Approved -- this is the de-facto standard whereby // do;while(0)x will have a semicolon inserted before x. parseOptional(SyntaxKind.SemicolonToken); return finishNode(node); } function parseWhileStatement(): WhileStatement { var node = createNode(SyntaxKind.WhileStatement); parseExpected(SyntaxKind.WhileKeyword); parseExpected(SyntaxKind.OpenParenToken); node.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); node.statement = parseStatement(); return finishNode(node); } function parseForOrForInStatement(): Statement { var pos = getNodePos(); parseExpected(SyntaxKind.ForKeyword); parseExpected(SyntaxKind.OpenParenToken); var initializer: VariableDeclarationList | Expression = undefined; if (token !== SyntaxKind.SemicolonToken) { if (token === SyntaxKind.VarKeyword || token === SyntaxKind.LetKeyword || token === SyntaxKind.ConstKeyword) { initializer = parseVariableDeclarationList(/*disallowIn:*/ true); } else { initializer = disallowInAnd(parseExpression); } } var forOrForInStatement: IterationStatement; if (parseOptional(SyntaxKind.InKeyword)) { var forInStatement = createNode(SyntaxKind.ForInStatement, pos); forInStatement.initializer = initializer; forInStatement.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); forOrForInStatement = forInStatement; } else { var forStatement = createNode(SyntaxKind.ForStatement, pos); forStatement.initializer = initializer; parseExpected(SyntaxKind.SemicolonToken); if (token !== SyntaxKind.SemicolonToken && token !== SyntaxKind.CloseParenToken) { forStatement.condition = allowInAnd(parseExpression); } parseExpected(SyntaxKind.SemicolonToken); if (token !== SyntaxKind.CloseParenToken) { forStatement.iterator = allowInAnd(parseExpression); } parseExpected(SyntaxKind.CloseParenToken); forOrForInStatement = forStatement; } forOrForInStatement.statement = parseStatement(); return finishNode(forOrForInStatement); } function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement { var node = createNode(kind); parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); if (!canParseSemicolon()) { node.label = parseIdentifier(); } parseSemicolon(); return finishNode(node); } function parseReturnStatement(): ReturnStatement { var node = createNode(SyntaxKind.ReturnStatement); parseExpected(SyntaxKind.ReturnKeyword); if (!canParseSemicolon()) { node.expression = allowInAnd(parseExpression); } parseSemicolon(); return finishNode(node); } function parseWithStatement(): WithStatement { var node = createNode(SyntaxKind.WithStatement); parseExpected(SyntaxKind.WithKeyword); parseExpected(SyntaxKind.OpenParenToken); node.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); node.statement = parseStatement(); return finishNode(node); } function parseCaseClause(): CaseClause { var node = createNode(SyntaxKind.CaseClause); parseExpected(SyntaxKind.CaseKeyword); node.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.ColonToken); node.statements = parseList(ParsingContext.SwitchClauseStatements, /*checkForStrictMode*/ false, parseStatement); return finishNode(node); } function parseDefaultClause(): DefaultClause { var node = createNode(SyntaxKind.DefaultClause); parseExpected(SyntaxKind.DefaultKeyword); parseExpected(SyntaxKind.ColonToken); node.statements = parseList(ParsingContext.SwitchClauseStatements, /*checkForStrictMode*/ false, parseStatement); return finishNode(node); } function parseCaseOrDefaultClause(): CaseOrDefaultClause { return token === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause(); } function parseSwitchStatement(): SwitchStatement { var node = createNode(SyntaxKind.SwitchStatement); parseExpected(SyntaxKind.SwitchKeyword); parseExpected(SyntaxKind.OpenParenToken); node.expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); parseExpected(SyntaxKind.OpenBraceToken); node.clauses = parseList(ParsingContext.SwitchClauses, /*checkForStrictMode*/ false, parseCaseOrDefaultClause); parseExpected(SyntaxKind.CloseBraceToken); return finishNode(node); } function parseThrowStatement(): ThrowStatement { // ThrowStatement[Yield] : // throw [no LineTerminator here]Expression[In, ?Yield]; // Because of automatic semicolon insertion, we need to report error if this // throw could be terminated with a semicolon. Note: we can't call 'parseExpression' // directly as that might consume an expression on the following line. // We just return 'undefined' in that case. The actual error will be reported in the // grammar walker. var node = createNode(SyntaxKind.ThrowStatement); parseExpected(SyntaxKind.ThrowKeyword); node.expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression); parseSemicolon(); return finishNode(node); } // TODO: Review for error recovery function parseTryStatement(): TryStatement { var node = createNode(SyntaxKind.TryStatement); parseExpected(SyntaxKind.TryKeyword); node.tryBlock = parseBlock(/*ignoreMissingOpenBrace:*/ false, /*checkForStrictMode*/ false); node.catchClause = token === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined; // If we don't have a catch clause, then we must have a finally clause. Try to parse // one out no matter what. if (!node.catchClause || token === SyntaxKind.FinallyKeyword) { parseExpected(SyntaxKind.FinallyKeyword); node.finallyBlock = parseBlock(/*ignoreMissingOpenBrace:*/ false, /*checkForStrictMode*/ false); } return finishNode(node); } function parseCatchClause(): CatchClause { var result = createNode(SyntaxKind.CatchClause); parseExpected(SyntaxKind.CatchKeyword); parseExpected(SyntaxKind.OpenParenToken); result.name = parseIdentifier(); result.type = parseTypeAnnotation(); parseExpected(SyntaxKind.CloseParenToken); result.block = parseBlock(/*ignoreMissingOpenBrace:*/ false, /*checkForStrictMode:*/ false); return finishNode(result); } function parseDebuggerStatement(): Statement { var node = createNode(SyntaxKind.DebuggerStatement); parseExpected(SyntaxKind.DebuggerKeyword); parseSemicolon(); return finishNode(node); } function parseExpressionOrLabeledStatement(): ExpressionStatement | LabeledStatement { // Avoiding having to do the lookahead for a labeled statement by just trying to parse // out an expression, seeing if it is identifier and then seeing if it is followed by // a colon. var fullStart = scanner.getStartPos(); var expression = allowInAnd(parseExpression); if (expression.kind === SyntaxKind.Identifier && parseOptional(SyntaxKind.ColonToken)) { var labeledStatement = createNode(SyntaxKind.LabeledStatement, fullStart); labeledStatement.label = expression; labeledStatement.statement = parseStatement(); return finishNode(labeledStatement); } else { var expressionStatement = createNode(SyntaxKind.ExpressionStatement, fullStart); expressionStatement.expression = expression; parseSemicolon(); return finishNode(expressionStatement); } } function isStartOfStatement(inErrorRecovery: boolean): boolean { // Functions and variable statements are allowed as a statement. But as per the grammar, // they also allow modifiers. So we have to check for those statements that might be // following modifiers.This ensures that things 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)) { var result = lookAhead(parseVariableStatementOrFunctionDeclarationWithModifiers); if (result) { return true; } } switch (token) { case SyntaxKind.SemicolonToken: // If we're in error recovery, then we don't want to treat ';' as an empty statement. // The problem is that ';' can show up in far too many contexts, and if we see one // and assume it's a statement, then we may bail out inappropriately from whatever // we're parsing. For example, if we have a semicolon in the middle of a class, then // we really don't want to assume the class is over and we're on a statement in the // outer module. We just want to consume and move on. return !inErrorRecovery; case SyntaxKind.OpenBraceToken: case SyntaxKind.VarKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.FunctionKeyword: case SyntaxKind.IfKeyword: case SyntaxKind.DoKeyword: case SyntaxKind.WhileKeyword: case SyntaxKind.ForKeyword: case SyntaxKind.ContinueKeyword: case SyntaxKind.BreakKeyword: case SyntaxKind.ReturnKeyword: case SyntaxKind.WithKeyword: case SyntaxKind.SwitchKeyword: case SyntaxKind.ThrowKeyword: case SyntaxKind.TryKeyword: case SyntaxKind.DebuggerKeyword: // 'catch' and 'finally' do not actually indicate that the code is part of a statement, // however, we say they are here so that we may gracefully parse them and error later. case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: return true; case SyntaxKind.ConstKeyword: // const keyword can precede enum keyword when defining constant enums // 'const enum' do not start statement. // In ES 6 'enum' is a future reserved keyword, so it should not be used as identifier var isConstEnum = lookAhead(nextTokenIsEnumKeyword); return !isConstEnum; case SyntaxKind.InterfaceKeyword: case SyntaxKind.ClassKeyword: case SyntaxKind.ModuleKeyword: case SyntaxKind.EnumKeyword: case SyntaxKind.TypeKeyword: // When followed by an identifier, these do not start a statement but might // instead be following declarations if (isDeclarationStart()) { return false; } case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.StaticKeyword: // When followed by an identifier or keyword, these do not start a statement but // might instead be following type members if (lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine)) { return false; } default: return isStartOfExpression(); } } function nextTokenIsEnumKeyword() { nextToken(); return token === SyntaxKind.EnumKeyword } function nextTokenIsIdentifierOrKeywordOnSameLine() { nextToken(); return isIdentifierOrKeyword() && !scanner.hasPrecedingLineBreak(); } function parseStatement(): Statement { switch (token) { case SyntaxKind.OpenBraceToken: return parseBlock(/*ignoreMissingOpenBrace:*/ false, /*checkForStrictMode:*/ false); 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); case SyntaxKind.FunctionKeyword: return parseFunctionDeclaration(scanner.getStartPos(), /*modifiers:*/ undefined); case SyntaxKind.SemicolonToken: return parseEmptyStatement(); case SyntaxKind.IfKeyword: return parseIfStatement(); case SyntaxKind.DoKeyword: return parseDoStatement(); case SyntaxKind.WhileKeyword: return parseWhileStatement(); case SyntaxKind.ForKeyword: return parseForOrForInStatement(); case SyntaxKind.ContinueKeyword: return parseBreakOrContinueStatement(SyntaxKind.ContinueStatement); case SyntaxKind.BreakKeyword: return parseBreakOrContinueStatement(SyntaxKind.BreakStatement); case SyntaxKind.ReturnKeyword: return parseReturnStatement(); case SyntaxKind.WithKeyword: return parseWithStatement(); case SyntaxKind.SwitchKeyword: return parseSwitchStatement(); case SyntaxKind.ThrowKeyword: return parseThrowStatement(); case SyntaxKind.TryKeyword: // Include the next two for error recovery. case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: return parseTryStatement(); case SyntaxKind.DebuggerKeyword: return parseDebuggerStatement(); 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); } // Else parse it like identifier - fall through default: // Functions and variable statements are allowed as a statement. But as per // the grammar, they also allow modifiers. So we have to check for those // statements that might be following modifiers. This ensures that things // 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)) { var result = tryParse(parseVariableStatementOrFunctionDeclarationWithModifiers); if (result) { return result; } } return parseExpressionOrLabeledStatement(); } } function parseVariableStatementOrFunctionDeclarationWithModifiers(): FunctionDeclaration | VariableStatement { var start = scanner.getStartPos(); var modifiers = parseModifiers(); switch (token) { case SyntaxKind.ConstKeyword: var nextTokenIsEnum = lookAhead(nextTokenIsEnumKeyword) if (nextTokenIsEnum) { return undefined; } return parseVariableStatement(start, modifiers); case SyntaxKind.LetKeyword: if (!isLetDeclaration()) { return undefined; } return parseVariableStatement(start, modifiers); case SyntaxKind.VarKeyword: return parseVariableStatement(start, modifiers); case SyntaxKind.FunctionKeyword: return parseFunctionDeclaration(start, modifiers); } return undefined; } function parseFunctionBlockOrSemicolon(isGenerator: boolean, diagnosticMessage?: DiagnosticMessage): Block { if (token !== SyntaxKind.OpenBraceToken && canParseSemicolon()) { parseSemicolon(); return; } return parseFunctionBlock(isGenerator, /*ignoreMissingOpenBrace:*/ false, diagnosticMessage); } // DECLARATIONS function parseArrayBindingElement(): BindingElement { if (token === SyntaxKind.CommaToken) { return createNode(SyntaxKind.OmittedExpression); } var node = createNode(SyntaxKind.BindingElement); node.name = parseIdentifierOrPattern(); node.initializer = parseInitializer(/*inParameter*/ false); return finishNode(node); } function parseObjectBindingElement(): BindingElement { var node = createNode(SyntaxKind.BindingElement); // TODO(andersh): Handle computed properties var id = parsePropertyName(); if (id.kind === SyntaxKind.Identifier && token !== SyntaxKind.ColonToken) { node.name = id; } else { parseExpected(SyntaxKind.ColonToken); node.propertyName = id; node.name = parseIdentifierOrPattern(); } node.initializer = parseInitializer(/*inParameter*/ false); return finishNode(node); } function parseObjectBindingPattern(): BindingPattern { var node = createNode(SyntaxKind.ObjectBindingPattern); parseExpected(SyntaxKind.OpenBraceToken); node.elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); parseExpected(SyntaxKind.CloseBraceToken); return finishNode(node); } function parseArrayBindingPattern(): BindingPattern { var node = createNode(SyntaxKind.ArrayBindingPattern); parseExpected(SyntaxKind.OpenBracketToken); node.elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); parseExpected(SyntaxKind.CloseBracketToken); return finishNode(node); } function isIdentifierOrPattern() { return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.OpenBracketToken || isIdentifier(); } function parseIdentifierOrPattern(): Identifier | BindingPattern { if (token === SyntaxKind.OpenBracketToken) { return parseArrayBindingPattern(); } if (token === SyntaxKind.OpenBraceToken) { return parseObjectBindingPattern(); } return parseIdentifier(); } function parseVariableDeclaration(): VariableDeclaration { var node = createNode(SyntaxKind.VariableDeclaration); node.name = parseIdentifierOrPattern(); node.type = parseTypeAnnotation(); node.initializer = parseInitializer(/*inParameter*/ false); return finishNode(node); } function parseVariableDeclarationList(disallowIn: boolean): VariableDeclarationList { var node = createNode(SyntaxKind.VariableDeclarationList); switch (token) { case SyntaxKind.VarKeyword: break; case SyntaxKind.LetKeyword: node.flags |= NodeFlags.Let; break; case SyntaxKind.ConstKeyword: node.flags |= NodeFlags.Const; break; default: Debug.fail(); } nextToken(); var savedDisallowIn = inDisallowInContext(); setDisallowInContext(disallowIn); node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations, parseVariableDeclaration); setDisallowInContext(savedDisallowIn); return finishNode(node); } function parseVariableStatement(fullStart: number, modifiers: ModifiersArray): VariableStatement { var node = createNode(SyntaxKind.VariableStatement, fullStart); setModifiers(node, modifiers); node.declarationList = parseVariableDeclarationList(/*disallowIn:*/ false); parseSemicolon(); return finishNode(node); } function parseFunctionDeclaration(fullStart: number, modifiers: ModifiersArray): FunctionDeclaration { var node = createNode(SyntaxKind.FunctionDeclaration, fullStart); setModifiers(node, modifiers); parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); node.name = parseIdentifier(); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ !!node.asteriskToken, /*requireCompleteParameterList:*/ false, node); node.body = parseFunctionBlockOrSemicolon(!!node.asteriskToken, Diagnostics.or_expected); return finishNode(node); } function parseConstructorDeclaration(pos: number, modifiers: ModifiersArray): ConstructorDeclaration { var node = createNode(SyntaxKind.Constructor, pos); setModifiers(node, modifiers); parseExpected(SyntaxKind.ConstructorKeyword); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ false, node); node.body = parseFunctionBlockOrSemicolon(/*isGenerator:*/ false, Diagnostics.or_expected); return finishNode(node); } function parseMethodDeclaration(fullStart: number, modifiers: ModifiersArray, asteriskToken: Node, name: DeclarationName, questionToken: Node, diagnosticMessage?: DiagnosticMessage): MethodDeclaration { var method = createNode(SyntaxKind.MethodDeclaration, fullStart); setModifiers(method, modifiers); method.asteriskToken = asteriskToken; method.name = name; method.questionToken = questionToken; fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ !!asteriskToken, /*requireCompleteParameterList:*/ false, method); method.body = parseFunctionBlockOrSemicolon(!!asteriskToken, diagnosticMessage); return finishNode(method); } function parsePropertyOrMethodDeclaration(fullStart: number, modifiers: ModifiersArray): ClassElement { var asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); var name = parsePropertyName(); // Note: this is not legal as per the grammar. But we allow it in the parser and // report an error in the grammar checker. var questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { return parseMethodDeclaration(fullStart, modifiers, asteriskToken, name, questionToken, Diagnostics.or_expected); } else { var property = createNode(SyntaxKind.PropertyDeclaration, fullStart); setModifiers(property, modifiers); property.name = name; property.questionToken = questionToken; property.type = parseTypeAnnotation(); property.initializer = allowInAnd(parseNonParameterInitializer); parseSemicolon(); return finishNode(property); } } function parseNonParameterInitializer() { return parseInitializer(/*inParameter*/ false); } function parseAccessorDeclaration(kind: SyntaxKind, fullStart: number, modifiers: ModifiersArray): AccessorDeclaration { var node = createNode(kind, fullStart); setModifiers(node, modifiers); node.name = parsePropertyName(); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ false, /*requireCompleteParameterList:*/ false, node); node.body = parseFunctionBlockOrSemicolon(/*isGenerator:*/ false); return finishNode(node); } function isClassMemberStart(): boolean { var idToken: SyntaxKind; // Eat up all modifiers, but hold on to the last one in case it is actually an identifier. while (isModifier(token)) { idToken = token; nextToken(); } if (token === SyntaxKind.AsteriskToken) { return true; } // Try to get the first property-like token following all modifiers. // This can either be an identifier or the 'get' or 'set' keywords. if (isLiteralPropertyName()) { idToken = token; nextToken(); } // Index signatures and computed properties are class members; we can parse. if (token === SyntaxKind.OpenBracketToken) { return true; } // If we were able to get any potential identifier... if (idToken !== undefined) { // If we have a non-keyword identifier, or if we have an accessor, then it's safe to parse. if (!isKeyword(idToken) || idToken === SyntaxKind.SetKeyword || idToken === SyntaxKind.GetKeyword) { return true; } // If it *is* a keyword, but not an accessor, check a little farther along // to see if it should actually be parsed as a class member. switch (token) { case SyntaxKind.OpenParenToken: // Method declaration case SyntaxKind.LessThanToken: // Generic Method declaration case SyntaxKind.ColonToken: // Type Annotation for declaration case SyntaxKind.EqualsToken: // Initializer for declaration case SyntaxKind.QuestionToken: // Not valid, but permitted so that it gets caught later on. return true; default: // Covers // - Semicolons (declaration termination) // - Closing braces (end-of-class, must be declaration) // - End-of-files (not valid, but permitted so that it gets caught later on) // - Line-breaks (enabling *automatic semicolon insertion*) return canParseSemicolon(); } } return false; } function parseModifiers(): ModifiersArray { var flags = 0; var modifiers: ModifiersArray; while (true) { var modifierStart = scanner.getStartPos(); var modifierKind = token; if (!parseAnyContextualModifier()) { break; } if (!modifiers) { modifiers = []; modifiers.pos = modifierStart; } flags |= modifierToFlag(modifierKind); modifiers.push(finishNode(createNode(modifierKind, modifierStart))); } if (modifiers) { modifiers.flags = flags; modifiers.end = scanner.getStartPos(); } return modifiers; } function parseClassElement(): ClassElement { var fullStart = getNodePos(); var modifiers = parseModifiers(); var accessor = tryParseAccessorDeclaration(fullStart, modifiers); if (accessor) { return accessor; } if (token === SyntaxKind.ConstructorKeyword) { return parseConstructorDeclaration(fullStart, modifiers); } if (isIndexSignature()) { return parseIndexSignatureDeclaration(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 (isIdentifierOrKeyword() || token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral || token === SyntaxKind.AsteriskToken || token === SyntaxKind.OpenBracketToken) { return parsePropertyOrMethodDeclaration(fullStart, 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 { var node = createNode(SyntaxKind.ClassDeclaration, fullStart); setModifiers(node, modifiers); parseExpected(SyntaxKind.ClassKeyword); node.name = parseIdentifier(); node.typeParameters = parseTypeParameters(); node.heritageClauses = parseHeritageClauses(/*isClassHeritageClause:*/ true); if (parseExpected(SyntaxKind.OpenBraceToken)) { // ClassTail[Yield,GeneratorParameter] : See 14.5 // [~GeneratorParameter]ClassHeritage[?Yield]opt { ClassBody[?Yield]opt } // [+GeneratorParameter] ClassHeritageopt { ClassBodyopt } node.members = inGeneratorParameterContext() ? doOutsideOfYieldContext(parseClassMembers) : parseClassMembers(); parseExpected(SyntaxKind.CloseBraceToken); } else { node.members = createMissingList(); } return finishNode(node); } function parseHeritageClauses(isClassHeritageClause: boolean): NodeArray { // ClassTail[Yield,GeneratorParameter] : See 14.5 // [~GeneratorParameter]ClassHeritage[?Yield]opt { ClassBody[?Yield]opt } // [+GeneratorParameter] ClassHeritageopt { ClassBodyopt } if (isHeritageClause()) { return isClassHeritageClause && inGeneratorParameterContext() ? doOutsideOfYieldContext(parseHeritageClausesWorker) : parseHeritageClausesWorker(); } return undefined; } function parseHeritageClausesWorker() { return parseList(ParsingContext.HeritageClauses, /*checkForStrictMode:*/ false, parseHeritageClause); } function parseHeritageClause() { if (token === SyntaxKind.ExtendsKeyword || token === SyntaxKind.ImplementsKeyword) { var node = createNode(SyntaxKind.HeritageClause); node.token = token; nextToken(); node.types = parseDelimitedList(ParsingContext.TypeReferences, parseTypeReference); return finishNode(node); } return undefined; } function isHeritageClause(): boolean { return token === SyntaxKind.ExtendsKeyword || token === SyntaxKind.ImplementsKeyword; } function parseClassMembers() { return parseList(ParsingContext.ClassMembers, /*checkForStrictMode*/ false, parseClassElement); } function parseInterfaceDeclaration(fullStart: number, modifiers: ModifiersArray): InterfaceDeclaration { var node = createNode(SyntaxKind.InterfaceDeclaration, fullStart); setModifiers(node, modifiers); parseExpected(SyntaxKind.InterfaceKeyword); node.name = parseIdentifier(); node.typeParameters = parseTypeParameters(); node.heritageClauses = parseHeritageClauses(/*isClassHeritageClause:*/ false); node.members = parseObjectTypeMembers(); return finishNode(node); } function parseTypeAliasDeclaration(fullStart: number, modifiers: ModifiersArray): TypeAliasDeclaration { var node = createNode(SyntaxKind.TypeAliasDeclaration, fullStart); setModifiers(node, modifiers); parseExpected(SyntaxKind.TypeKeyword); node.name = parseIdentifier(); parseExpected(SyntaxKind.EqualsToken); node.type = parseType(); parseSemicolon(); return finishNode(node); } // In an ambient declaration, the grammar only allows integer literals as initializers. // In a non-ambient declaration, the grammar allows uninitialized members only in a // ConstantEnumMemberSection, which starts at the beginning of an enum declaration // or any time an integer literal initializer is encountered. function parseEnumMember(): EnumMember { var node = createNode(SyntaxKind.EnumMember, scanner.getStartPos()); node.name = parsePropertyName(); node.initializer = allowInAnd(parseNonParameterInitializer); return finishNode(node); } function parseEnumDeclaration(fullStart: number, modifiers: ModifiersArray): EnumDeclaration { var node = createNode(SyntaxKind.EnumDeclaration, fullStart); setModifiers(node, modifiers); parseExpected(SyntaxKind.EnumKeyword); node.name = parseIdentifier(); if (parseExpected(SyntaxKind.OpenBraceToken)) { node.members = parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember); parseExpected(SyntaxKind.CloseBraceToken); } else { node.members = createMissingList(); } return finishNode(node); } function parseModuleBlock(): ModuleBlock { var node = createNode(SyntaxKind.ModuleBlock, scanner.getStartPos()); if (parseExpected(SyntaxKind.OpenBraceToken)) { node.statements = parseList(ParsingContext.ModuleElements, /*checkForStrictMode*/false, parseModuleElement); parseExpected(SyntaxKind.CloseBraceToken); } else { node.statements = createMissingList(); } return finishNode(node); } function parseInternalModuleTail(fullStart: number, modifiers: ModifiersArray, flags: NodeFlags): ModuleDeclaration { var node = createNode(SyntaxKind.ModuleDeclaration, fullStart); setModifiers(node, modifiers); node.flags |= flags; node.name = parseIdentifier(); node.body = parseOptional(SyntaxKind.DotToken) ? parseInternalModuleTail(getNodePos(), /*modifiers:*/undefined, NodeFlags.Export) : parseModuleBlock(); return finishNode(node); } function parseAmbientExternalModuleDeclaration(fullStart: number, modifiers: ModifiersArray): ModuleDeclaration { var node = createNode(SyntaxKind.ModuleDeclaration, fullStart); setModifiers(node, modifiers); node.name = parseLiteralNode(/*internName:*/ true); node.body = parseModuleBlock(); return finishNode(node); } function parseModuleDeclaration(fullStart: number, modifiers: ModifiersArray): ModuleDeclaration { parseExpected(SyntaxKind.ModuleKeyword); return token === SyntaxKind.StringLiteral ? parseAmbientExternalModuleDeclaration(fullStart, modifiers) : parseInternalModuleTail(fullStart, modifiers, modifiers ? modifiers.flags : 0); } function isExternalModuleReference() { return token === SyntaxKind.RequireKeyword && lookAhead(nextTokenIsOpenParen); } function nextTokenIsOpenParen() { return nextToken() === SyntaxKind.OpenParenToken; } function parseImportDeclaration(fullStart: number, modifiers: ModifiersArray): ImportDeclaration { var node = createNode(SyntaxKind.ImportDeclaration, fullStart); setModifiers(node, modifiers); parseExpected(SyntaxKind.ImportKeyword); node.name = parseIdentifier(); parseExpected(SyntaxKind.EqualsToken); node.moduleReference = parseModuleReference(); parseSemicolon(); return finishNode(node); } function parseModuleReference() { return isExternalModuleReference() ? parseExternalModuleReference() : parseEntityName(/*allowReservedWords*/ false); } function parseExternalModuleReference() { var node = createNode(SyntaxKind.ExternalModuleReference); parseExpected(SyntaxKind.RequireKeyword); parseExpected(SyntaxKind.OpenParenToken); // We allow arbitrary expressions here, even though the grammar only allows string // literals. We check to ensure that it is only a string literal later in the grammar // walker. node.expression = parseExpression(); // Ensure the string being required is in our 'identifier' table. This will ensure // that features like 'find refs' will look inside this file when search for its name. if (node.expression.kind === SyntaxKind.StringLiteral) { internIdentifier((node.expression).text); } parseExpected(SyntaxKind.CloseParenToken); return finishNode(node); } function parseExportAssignmentTail(fullStart: number, modifiers: ModifiersArray): ExportAssignment { var node = createNode(SyntaxKind.ExportAssignment, fullStart); setModifiers(node, modifiers); node.exportName = parseIdentifier(); parseSemicolon(); return finishNode(node); } function isLetDeclaration() { // It is let declaration if in strict mode or next token is identifier on same line. // otherwise it needs to be treated like identifier return inStrictModeContext() || lookAhead(nextTokenIsIdentifierOnSameLine); } function isDeclarationStart(): boolean { switch (token) { case SyntaxKind.VarKeyword: case SyntaxKind.ConstKeyword: case SyntaxKind.FunctionKeyword: return true; case SyntaxKind.LetKeyword: return isLetDeclaration(); case SyntaxKind.ClassKeyword: case SyntaxKind.InterfaceKeyword: case SyntaxKind.EnumKeyword: case SyntaxKind.ImportKeyword: case SyntaxKind.TypeKeyword: // Not true keywords so ensure an identifier follows return lookAhead(nextTokenIsIdentifierOrKeyword); case SyntaxKind.ModuleKeyword: // Not a true keyword so ensure an identifier or string literal follows return lookAhead(nextTokenIsIdentifierOrKeywordOrStringLiteral); case SyntaxKind.ExportKeyword: // Check for export assignment or modifier on source element return lookAhead(nextTokenIsEqualsTokenOrDeclarationStart); case SyntaxKind.DeclareKeyword: case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.StaticKeyword: // Check for modifier on source element return lookAhead(nextTokenIsDeclarationStart); } } function isIdentifierOrKeyword() { return token >= SyntaxKind.Identifier; } function nextTokenIsIdentifierOrKeyword() { nextToken(); return isIdentifierOrKeyword(); } function nextTokenIsIdentifierOrKeywordOrStringLiteral() { nextToken(); return isIdentifierOrKeyword() || token === SyntaxKind.StringLiteral; } function nextTokenIsEqualsTokenOrDeclarationStart() { nextToken(); return token === SyntaxKind.EqualsToken || isDeclarationStart(); } function nextTokenIsDeclarationStart() { nextToken(); return isDeclarationStart(); } function parseDeclaration(): ModuleElement { var fullStart = getNodePos(); var modifiers = parseModifiers(); if (token === SyntaxKind.ExportKeyword) { nextToken(); if (parseOptional(SyntaxKind.EqualsToken)) { return parseExportAssignmentTail(fullStart, modifiers); } } switch (token) { case SyntaxKind.VarKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: return parseVariableStatement(fullStart, modifiers); case SyntaxKind.FunctionKeyword: return parseFunctionDeclaration(fullStart, modifiers); case SyntaxKind.ClassKeyword: return parseClassDeclaration(fullStart, modifiers); case SyntaxKind.InterfaceKeyword: return parseInterfaceDeclaration(fullStart, modifiers); case SyntaxKind.TypeKeyword: return parseTypeAliasDeclaration(fullStart, modifiers); case SyntaxKind.EnumKeyword: return parseEnumDeclaration(fullStart, modifiers); case SyntaxKind.ModuleKeyword: return parseModuleDeclaration(fullStart, modifiers); case SyntaxKind.ImportKeyword: return parseImportDeclaration(fullStart, modifiers); default: Debug.fail("Mismatch between isDeclarationStart and parseDeclaration"); } } function isSourceElement(inErrorRecovery: boolean): boolean { return isDeclarationStart() || isStartOfStatement(inErrorRecovery); } function parseSourceElement() { return parseSourceElementOrModuleElement(); } function parseModuleElement() { return parseSourceElementOrModuleElement(); } function parseSourceElementOrModuleElement(): ModuleElement { return isDeclarationStart() ? parseDeclaration() : parseStatement(); } function processReferenceComments(sourceFile: SourceFile): void { var triviaScanner = createScanner(languageVersion, /*skipTrivia*/false, sourceText); var referencedFiles: FileReference[] = []; var amdDependencies: string[] = []; var amdModuleName: string; // Keep scanning all the leading trivia in the file until we get to something that // isn't trivia. Any single line comment will be analyzed to see if it is a // reference comment. while (true) { var kind = triviaScanner.scan(); if (kind === SyntaxKind.WhitespaceTrivia || kind === SyntaxKind.NewLineTrivia || kind === SyntaxKind.MultiLineCommentTrivia) { continue; } if (kind !== SyntaxKind.SingleLineCommentTrivia) { break; } var range = { pos: triviaScanner.getTokenPos(), end: triviaScanner.getTextPos() }; var comment = sourceText.substring(range.pos, range.end); var referencePathMatchResult = getFileReferenceFromReferencePath(comment, range); if (referencePathMatchResult) { var fileReference = referencePathMatchResult.fileReference; sourceFile.hasNoDefaultLib = referencePathMatchResult.isNoDefaultLib; var diagnosticMessage = referencePathMatchResult.diagnosticMessage; if (fileReference) { referencedFiles.push(fileReference); } if (diagnosticMessage) { sourceFile.referenceDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, diagnosticMessage)); } } else { var amdModuleNameRegEx = /^\/\/\/\s* node.flags & NodeFlags.Export || node.kind === SyntaxKind.ImportDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference || node.kind === SyntaxKind.ExportAssignment ? node : undefined); } function getSyntacticDiagnostics() { if (syntacticDiagnostics === undefined) { // Don't bother doing any grammar checks if there are already parser errors. // Otherwise we may end up with too many cascading errors. syntacticDiagnostics = sourceFile.referenceDiagnostics.concat(sourceFile.parseDiagnostics); } Debug.assert(syntacticDiagnostics !== undefined); return syntacticDiagnostics; } } export function isLeftHandSideExpression(expr: Expression): boolean { if (expr) { switch (expr.kind) { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.NewExpression: case SyntaxKind.CallExpression: case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.Identifier: case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateExpression: case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.ThisKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.SuperKeyword: return true; } } return false; } export function isAssignmentOperator(token: SyntaxKind): boolean { return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment; } export function createProgram(rootNames: string[], options: CompilerOptions, host: CompilerHost): Program { var program: Program; var files: SourceFile[] = []; var filesByName: Map = {}; var errors: Diagnostic[] = []; var seenNoDefaultLib = options.noLib; var commonSourceDirectory: string; forEach(rootNames, name => processRootFile(name, false)); if (!seenNoDefaultLib) { processRootFile(host.getDefaultLibFilename(options), true); } verifyCompilerOptions(); errors.sort(compareDiagnostics); program = { getSourceFile: getSourceFile, getSourceFiles: () => files, getCompilerOptions: () => options, getCompilerHost: () => host, getDiagnostics: getDiagnostics, getGlobalDiagnostics: getGlobalDiagnostics, getTypeChecker: fullTypeCheckMode => createTypeChecker(program, fullTypeCheckMode), getCommonSourceDirectory: () => commonSourceDirectory, }; return program; function getSourceFile(filename: string) { filename = host.getCanonicalFileName(filename); return hasProperty(filesByName, filename) ? filesByName[filename] : undefined; } function getDiagnostics(sourceFile?: SourceFile): Diagnostic[] { return sourceFile ? filter(errors, e => e.file === sourceFile) : errors; } function getGlobalDiagnostics(): Diagnostic[] { return filter(errors, e => !e.file); } function hasExtension(filename: string): boolean { return getBaseFilename(filename).indexOf(".") >= 0; } function processRootFile(filename: string, isDefaultLib: boolean) { processSourceFile(normalizePath(filename), isDefaultLib); } function processSourceFile(filename: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) { if (refEnd !== undefined && refPos !== undefined) { var start = refPos; var length = refEnd - refPos; } var diagnostic: DiagnosticMessage; if (hasExtension(filename)) { if (!options.allowNonTsExtensions && !fileExtensionIs(filename, ".ts")) { diagnostic = Diagnostics.File_0_must_have_extension_ts_or_d_ts; } else if (!findSourceFile(filename, isDefaultLib, refFile, refPos, refEnd)) { diagnostic = Diagnostics.File_0_not_found; } else if (refFile && host.getCanonicalFileName(filename) === host.getCanonicalFileName(refFile.filename)) { diagnostic = Diagnostics.A_file_cannot_have_a_reference_to_itself; } } else { if (!(findSourceFile(filename + ".ts", isDefaultLib, refFile, refPos, refEnd) || findSourceFile(filename + ".d.ts", isDefaultLib, refFile, refPos, refEnd))) { diagnostic = Diagnostics.File_0_not_found; filename += ".ts"; } } if (diagnostic) { if (refFile) { errors.push(createFileDiagnostic(refFile, start, length, diagnostic, filename)); } else { errors.push(createCompilerDiagnostic(diagnostic, filename)); } } } // Get source file from normalized filename function findSourceFile(filename: string, isDefaultLib: boolean, refFile?: SourceFile, refStart?: number, refLength?: number): SourceFile { var canonicalName = host.getCanonicalFileName(filename); if (hasProperty(filesByName, canonicalName)) { // We've already looked for this file, use cached result return getSourceFileFromCache(filename, canonicalName, /*useAbsolutePath*/ false); } else { var normalizedAbsolutePath = getNormalizedAbsolutePath(filename, host.getCurrentDirectory()); var canonicalAbsolutePath = host.getCanonicalFileName(normalizedAbsolutePath); if (hasProperty(filesByName, canonicalAbsolutePath)) { return getSourceFileFromCache(normalizedAbsolutePath, canonicalAbsolutePath, /*useAbsolutePath*/ true); } // We haven't looked for this file, do so now and cache result var file = filesByName[canonicalName] = host.getSourceFile(filename, options.target, hostErrorMessage => { if (refFile) { errors.push(createFileDiagnostic(refFile, refStart, refLength, Diagnostics.Cannot_read_file_0_Colon_1, filename, hostErrorMessage)); } else { errors.push(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, filename, hostErrorMessage)); } }); if (file) { seenNoDefaultLib = seenNoDefaultLib || file.hasNoDefaultLib; // Set the source file for normalized absolute path filesByName[canonicalAbsolutePath] = file; if (!options.noResolve) { var basePath = getDirectoryPath(filename); processReferencedFiles(file, basePath); processImportedModules(file, basePath); } if (isDefaultLib) { files.unshift(file); } else { files.push(file); } forEach(file.getSyntacticDiagnostics(), e => { errors.push(e); }); } } return file; function getSourceFileFromCache(filename: string, canonicalName: string, useAbsolutePath: boolean): SourceFile { var file = filesByName[canonicalName]; if (file && host.useCaseSensitiveFileNames()) { var sourceFileName = useAbsolutePath ? getNormalizedAbsolutePath(file.filename, host.getCurrentDirectory()) : file.filename; if (canonicalName !== sourceFileName) { errors.push(createFileDiagnostic(refFile, refStart, refLength, Diagnostics.Filename_0_differs_from_already_included_filename_1_only_in_casing, filename, sourceFileName)); } } return file; } } function processReferencedFiles(file: SourceFile, basePath: string) { forEach(file.referencedFiles, ref => { var referencedFilename = isRootedDiskPath(ref.filename) ? ref.filename : combinePaths(basePath, ref.filename); processSourceFile(normalizePath(referencedFilename), /* isDefaultLib */ false, file, ref.pos, ref.end); }); } function processImportedModules(file: SourceFile, basePath: string) { forEach(file.statements, node => { if (isExternalModuleImportDeclaration(node) && getExternalModuleImportDeclarationExpression(node).kind === SyntaxKind.StringLiteral) { var nameLiteral = getExternalModuleImportDeclarationExpression(node); var moduleName = nameLiteral.text; if (moduleName) { var searchPath = basePath; while (true) { var searchName = normalizePath(combinePaths(searchPath, moduleName)); if (findModuleSourceFile(searchName + ".ts", nameLiteral) || findModuleSourceFile(searchName + ".d.ts", nameLiteral)) { break; } var parentPath = getDirectoryPath(searchPath); if (parentPath === searchPath) { break; } searchPath = parentPath; } } } else if (node.kind === SyntaxKind.ModuleDeclaration && (node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) { // TypeScript 1.0 spec (April 2014): 12.1.6 // An AmbientExternalModuleDeclaration declares an external module. // This type of declaration is permitted only in the global module. // The StringLiteral must specify a top - level external module name. // Relative external module names are not permitted forEachChild((node).body, node => { if (isExternalModuleImportDeclaration(node) && getExternalModuleImportDeclarationExpression(node).kind === SyntaxKind.StringLiteral) { var nameLiteral = getExternalModuleImportDeclarationExpression(node); var moduleName = nameLiteral.text; if (moduleName) { // TypeScript 1.0 spec (April 2014): 12.1.6 // An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules // only through top - level external module names. Relative external module names are not permitted. var searchName = normalizePath(combinePaths(basePath, moduleName)); var tsFile = findModuleSourceFile(searchName + ".ts", nameLiteral); if (!tsFile) { findModuleSourceFile(searchName + ".d.ts", nameLiteral); } } } }); } }); function findModuleSourceFile(filename: string, nameLiteral: LiteralExpression) { return findSourceFile(filename, /* isDefaultLib */ false, file, nameLiteral.pos, nameLiteral.end - nameLiteral.pos); } } function verifyCompilerOptions() { if (!options.sourceMap && (options.mapRoot || options.sourceRoot)) { // Error to specify --mapRoot or --sourceRoot without mapSourceFiles if (options.mapRoot) { errors.push(createCompilerDiagnostic(Diagnostics.Option_mapRoot_cannot_be_specified_without_specifying_sourcemap_option)); } if (options.sourceRoot) { errors.push(createCompilerDiagnostic(Diagnostics.Option_sourceRoot_cannot_be_specified_without_specifying_sourcemap_option)); } return; } var firstExternalModule = forEach(files, f => isExternalModule(f) ? f : undefined); if (firstExternalModule && options.module === ModuleKind.None) { // We cannot use createDiagnosticFromNode because nodes do not have parents yet var externalModuleErrorSpan = getErrorSpanForNode(firstExternalModule.externalModuleIndicator); var errorStart = skipTrivia(firstExternalModule.text, externalModuleErrorSpan.pos); var errorLength = externalModuleErrorSpan.end - errorStart; errors.push(createFileDiagnostic(firstExternalModule, errorStart, errorLength, Diagnostics.Cannot_compile_external_modules_unless_the_module_flag_is_provided)); } // there has to be common source directory if user specified --outdir || --sourcRoot // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted if (options.outDir || // there is --outDir specified options.sourceRoot || // there is --sourceRoot specified (options.mapRoot && // there is --mapRoot Specified and there would be multiple js files generated (!options.out || firstExternalModule !== undefined))) { var commonPathComponents: string[]; forEach(files, sourceFile => { // Each file contributes into common source file path if (!(sourceFile.flags & NodeFlags.DeclarationFile) && !fileExtensionIs(sourceFile.filename, ".js")) { var sourcePathComponents = getNormalizedPathComponents(sourceFile.filename, host.getCurrentDirectory()); sourcePathComponents.pop(); // FileName is not part of directory if (commonPathComponents) { for (var i = 0; i < Math.min(commonPathComponents.length, sourcePathComponents.length); i++) { if (commonPathComponents[i] !== sourcePathComponents[i]) { if (i === 0) { errors.push(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files)); return; } // New common path found that is 0 -> i-1 commonPathComponents.length = i; break; } } // If the fileComponent path completely matched and less than already found update the length if (sourcePathComponents.length < commonPathComponents.length) { commonPathComponents.length = sourcePathComponents.length; } } else { // first file commonPathComponents = sourcePathComponents; } } }); commonSourceDirectory = getNormalizedPathFromPathComponents(commonPathComponents); if (commonSourceDirectory) { // Make sure directory path ends with directory separator so this string can directly // used to replace with "" to get the relative path of the source file and the relative path doesn't // start with / making it rooted path commonSourceDirectory += directorySeparator; } } } } }