diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c5151c8d438..71af6e342ba 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12953,6 +12953,11 @@ module ts { } } + function isEvalOrArgumentsIdentifier(node: Node): boolean { + return node.kind === SyntaxKind.Identifier && + ((node).text === "eval" || (node).text === "arguments"); + } + function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { if (node.typeParameters) { return grammarErrorAtPos(getSourceFileOfNode(node), node.typeParameters.pos, node.typeParameters.end - node.typeParameters.pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 93f8404d881..5fce6df3bd5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -318,452 +318,12 @@ module ts { } } - 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 - HeritageClauseElement, // Elements in a heritage 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. - ImportOrExportSpecifiers, // Named import clause's import specifier list - Count // Number of parsing contexts - } + export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false): SourceFile { + let start = new Date().getTime(); + let result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes); - 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.HeritageClauseElement: return Diagnostics.Expression_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; - case ParsingContext.ImportOrExportSpecifiers: return Diagnostics.Identifier_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; - case SyntaxKind.DefaultKeyword: return NodeFlags.Default; - } - return 0; - } - - function fixupParentReferences(sourceFile: SourceFile) { - // normally parent references are set during binding. However, for clients that only need - // a syntax tree, and no semantic features, then the binding process is an unnecessary - // overhead. This functions allows us to set all the parents, without all the expense of - // binding. - - let parent: Node = sourceFile; - forEachChild(sourceFile, visitNode); - return; - - function visitNode(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; - - let saveParent = parent; - parent = n; - forEachChild(n, visitNode); - parent = saveParent; - } - } - } - - function shouldCheckNode(node: Node) { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.Identifier: - return true; - } - - return false; - } - - function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { - if (isArray) { - visitArray(element); - } - else { - visitNode(element); - } - return; - - function visitNode(node: IncrementalNode) { - if (aggressiveChecks && shouldCheckNode(node)) { - var text = oldText.substring(node.pos, node.end); - } - - // 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; - - if (aggressiveChecks && shouldCheckNode(node)) { - Debug.assert(text === newText.substring(node.pos, node.end)); - } - - forEachChild(node, visitNode, visitArray); - checkNodePositions(node, aggressiveChecks); - } - - function visitArray(array: IncrementalNodeArray) { - array._children = undefined; - array.pos += delta; - array.end += delta; - - for (let node of array) { - visitNode(node); - } - } - } - - 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"); - Debug.assert(element.pos <= element.end); - - // 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 checkNodePositions(node: Node, aggressiveChecks: boolean) { - if (aggressiveChecks) { - let pos = node.pos; - forEachChild(node, child => { - Debug.assert(child.pos >= pos); - pos = child.end; - }); - Debug.assert(pos <= node.end); - } - } - - function updateTokenPositionsAndMarkElements( - sourceFile: IncrementalNode, - changeStart: number, - changeRangeOldEnd: number, - changeRangeNewEnd: number, - delta: number, - oldText: string, - newText: string, - aggressiveChecks: boolean): void { - - visitNode(sourceFile); - return; - - function visitNode(child: IncrementalNode) { - Debug.assert(child.pos <= child.end); - 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, /*isArray:*/ false, delta, oldText, newText, aggressiveChecks); - 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. - let fullEnd = child.end; - if (fullEnd >= changeStart) { - child.intersectsChange = true; - child._children = undefined; - - // Adjust the pos or end (or both) of the intersecting element accordingly. - adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); - forEachChild(child, visitNode, visitArray); - - checkNodePositions(child, aggressiveChecks); - return; - } - - // Otherwise, the node is entirely before the change range. No need to do anything with it. - Debug.assert(fullEnd < changeStart); - } - - function visitArray(array: IncrementalNodeArray) { - Debug.assert(array.pos <= array.end); - if (array.pos > changeRangeOldEnd) { - // Array is entirely after the change range. We need to move it, and move any of - // its children. - moveElementEntirelyPastChangeRange(array, /*isArray:*/ true, delta, oldText, newText, aggressiveChecks); - 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. - let fullEnd = array.end; - if (fullEnd >= changeStart) { - array.intersectsChange = true; - array._children = undefined; - - // Adjust the pos or end (or both) of the intersecting array accordingly. - adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); - for (let node of array) { - visitNode(node); - } - return; - } - - // Otherwise, the array is entirely before the change range. No need to do anything with it. - Debug.assert(fullEnd < changeStart); - } - } - - function extendToAffectedRange(sourceFile: SourceFile, 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. - let maxLookahead = 1; - - let 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 (let i = 0; start > 0 && i <= maxLookahead; i++) { - let nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start); - Debug.assert(nearestNode.pos <= start); - let position = nearestNode.pos; - - start = Math.max(0, position - 1); - } - - let finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); - let finalLength = changeRange.newLength + (changeRange.span.start - start); - - return createTextChangeRange(finalSpan, finalLength); - } - - function findNearestNodeStartingBeforeOrAtPosition(sourceFile: SourceFile, position: number): Node { - let bestResult: Node = sourceFile; - let lastNodeEntirelyBeforePosition: Node; - - forEachChild(sourceFile, visit); - - if (lastNodeEntirelyBeforePosition) { - let lastChildOfLastEntireNodeBeforePosition = getLastChild(lastNodeEntirelyBeforePosition); - if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { - bestResult = lastChildOfLastEntireNodeBeforePosition; - } - } - - return bestResult; - - function getLastChild(node: Node): Node { - while (true) { - let lastChild = getLastChildWorker(node); - if (lastChild) { - node = lastChild; - } - else { - return node; - } - } - } - - function getLastChildWorker(node: Node): Node { - let 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 checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) { - let oldText = sourceFile.text; - if (textChangeRange) { - Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); - - if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - let oldTextPrefix = oldText.substr(0, textChangeRange.span.start); - let newTextPrefix = newText.substr(0, textChangeRange.span.start); - Debug.assert(oldTextPrefix === newTextPrefix); - - let oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); - let newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); - Debug.assert(oldTextSuffix === newTextSuffix); - } - } + parseTime += new Date().getTime() - start; + return result; } // Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter @@ -776,256 +336,28 @@ module ts { // becoming detached from any SourceFile). It is recommended that this SourceFile not // be used once 'update' is called on it. export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { - aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); - - checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); - 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(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setNodeParents*/ true) - } - - // Make sure we're not trying to incrementally update a source file more than once. Once - // we do an update the original source file is considered unusbale from that point onwards. - // - // This is because we do incremental parsing in-place. i.e. we take nodes from the old - // tree and give them new positions and parents. From that point on, trusting the old - // tree at all is not possible as far too much of it may violate invariants. - let incrementalSourceFile = sourceFile; - Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); - incrementalSourceFile.hasBeenIncrementallyParsed = true; - - let oldText = sourceFile.text; - let syntaxCursor = createSyntaxCursor(sourceFile); - - // Make the actual change larger so that we know to reparse anything whose lookahead - // might have intersected the change. - let changeRange = extendToAffectedRange(sourceFile, textChangeRange); - checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks); - - // Ensure that extending the affected range only moved the start of the change range - // earlier in the file. - Debug.assert(changeRange.span.start <= textChangeRange.span.start); - Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span)); - Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(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). - let 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 (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 the 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(incrementalSourceFile, - changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); - - // 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. - let result = parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /* setParentNode */ true) - - return result; + return IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); } - 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)); - let 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 { - hasBeenIncrementallyParsed: boolean - } - - 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 { - let currentArray: NodeArray = sourceFile.statements; - let currentArrayIndex = 0; - - Debug.assert(currentArrayIndex < currentArray.length); - let current = currentArray[currentArrayIndex]; - let 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 - 1)) { - 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); - return; - - 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 (let i = 0, n = array.length; i < n; i++) { - let 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 { - let start = new Date().getTime(); - let result = parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes); - - parseTime += new Date().getTime() - start; - return result; - } - - function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: SyntaxCursor, setParentNodes = false): SourceFile { + // Implement the parser as a singleton module. We do this for perf reasons because creating + // parser instances can actually be expensive enough to impact us on projects with many source + // files. + module Parser { + // Share a single scanner across all calls to parse a source file. This helps speed things + // up by avoiding the cost of creating/compiling scanners over and over again. + const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia:*/ true); const disallowInAndDecoratorContext = ParserContextFlags.DisallowIn | ParserContextFlags.Decorator; - let parsingContext: ParsingContext = 0; - let identifiers: Map = {}; - let identifierCount = 0; - let nodeCount = 0; + let sourceFile: SourceFile; + let syntaxCursor: IncrementalParser.SyntaxCursor; + let token: SyntaxKind; + let sourceText: string; + let nodeCount: number; + let identifiers: Map; + let identifierCount: number; - let sourceFile = createNode(SyntaxKind.SourceFile, /*pos*/ 0); - - sourceFile.pos = 0; - sourceFile.end = sourceText.length; - sourceFile.text = sourceText; - - sourceFile.parseDiagnostics = []; - sourceFile.bindDiagnostics = []; - sourceFile.languageVersion = languageVersion; - sourceFile.fileName = normalizePath(fileName); - sourceFile.flags = fileExtensionIs(sourceFile.fileName, ".d.ts") ? NodeFlags.DeclarationFile : 0; + let parsingContext: ParsingContext; // 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 @@ -1104,28 +436,97 @@ module ts { // attached to the EOF token. let parseErrorBeforeNextFinishedNode: boolean = false; - // Create and prime the scanner before parsing the source elements. - let scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError); - token = nextToken(); + export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean): SourceFile { + sourceText = _sourceText; + syntaxCursor = _syntaxCursor; - processReferenceComments(sourceFile); + parsingContext = 0; + identifiers = {}; + identifierCount = 0; + nodeCount = 0; - sourceFile.statements = parseList(ParsingContext.SourceElements, /*checkForStrictMode*/ true, parseSourceElement); - Debug.assert(token === SyntaxKind.EndOfFileToken); - sourceFile.endOfFileToken = parseTokenNode(); + contextFlags = 0; + parseErrorBeforeNextFinishedNode = false; - setExternalModuleIndicator(sourceFile); + createSourceFile(fileName, languageVersion); - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.identifiers = identifiers; + // Initialize and prime the scanner before parsing the source elements. + scanner.setText(sourceText); + scanner.setOnError(scanError); + scanner.setScriptTarget(languageVersion); + token = nextToken(); - if (setParentNodes) { - fixupParentReferences(sourceFile); + 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.identifiers = identifiers; + + if (setParentNodes) { + fixupParentReferences(sourceFile); + } + + syntaxCursor = undefined; + + // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily. + scanner.setText(""); + scanner.setOnError(undefined); + + let result = sourceFile; + + // Clear any data. We don't want to accidently hold onto it for too long. + sourceFile = undefined; + identifiers = undefined; + syntaxCursor = undefined; + sourceText = undefined; + + return result; } - syntaxCursor = undefined; - return sourceFile; + function fixupParentReferences(sourceFile: SourceFile) { + // normally parent references are set during binding. However, for clients that only need + // a syntax tree, and no semantic features, then the binding process is an unnecessary + // overhead. This functions allows us to set all the parents, without all the expense of + // binding. + + let parent: Node = sourceFile; + forEachChild(sourceFile, visitNode); + return; + + function visitNode(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; + + let saveParent = parent; + parent = n; + forEachChild(n, visitNode); + parent = saveParent; + } + } + } + + function createSourceFile(fileName: string, languageVersion: ScriptTarget) { + sourceFile = createNode(SyntaxKind.SourceFile, /*pos*/ 0); + + sourceFile.pos = 0; + sourceFile.end = sourceText.length; + sourceFile.text = sourceText; + + sourceFile.parseDiagnostics = []; + sourceFile.bindDiagnostics = []; + sourceFile.languageVersion = languageVersion; + sourceFile.fileName = normalizePath(fileName); + sourceFile.flags = fileExtensionIs(sourceFile.fileName, ".d.ts") ? NodeFlags.DeclarationFile : 0; + } function setContextFlag(val: Boolean, flag: ParserContextFlags) { if (val) { @@ -1459,7 +860,7 @@ module ts { return node; } - + function createMissingNode(kind: SyntaxKind, reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): Node { if (reportAtCurrentPosition) { parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); @@ -1488,7 +889,7 @@ module ts { // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker if (token !== SyntaxKind.Identifier) { - node.originalKeywordKind = token; + node.originalKeywordKind = token; } node.text = internIdentifier(scanner.getTokenValue()); nextToken(); @@ -1827,6 +1228,16 @@ module ts { return result; } + /// Should be called only on prologue directives (isPrologueDirective(node) should be true) + function isUseStrictPrologueDirective(sourceFile: SourceFile, node: Node): boolean { + Debug.assert(isPrologueDirective(node)); + let 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'"; + } + function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { let node = currentNode(parsingContext); if (node) { @@ -2118,6 +1529,32 @@ module ts { return false; } + 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.HeritageClauseElement: return Diagnostics.Expression_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; + case ParsingContext.ImportOrExportSpecifiers: return Diagnostics.Identifier_expected; + } + }; + // Parses a comma-delimited list of elements function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimeter?: boolean): NodeArray { let saveParsingContext = parsingContext; @@ -2428,10 +1865,10 @@ module ts { } function fillSignature( - returnToken: SyntaxKind, - yieldAndGeneratorParameterContext: boolean, - requireCompleteParameterList: boolean, - signature: SignatureDeclaration): void { + returnToken: SyntaxKind, + yieldAndGeneratorParameterContext: boolean, + requireCompleteParameterList: boolean, + signature: SignatureDeclaration): void { let returnTokenRequired = returnToken === SyntaxKind.EqualsGreaterThanToken; signature.typeParameters = parseTypeParameters(); signature.parameters = parseParameterList(yieldAndGeneratorParameterContext, requireCompleteParameterList); @@ -3765,9 +3202,9 @@ module ts { case SyntaxKind.CommaToken: // foo, case SyntaxKind.OpenBraceToken: // foo { - // We don't want to treat these as type arguments. Otherwise we'll parse this - // as an invocation expression. Instead, we want to parse out the expression - // in isolation from the type arguments. + // We don't want to treat these as type arguments. Otherwise we'll parse this + // as an invocation expression. Instead, we want to parse out the expression + // in isolation from the type arguments. default: // Anything else treat as an expression. @@ -3830,7 +3267,7 @@ module ts { function parseArgumentOrArrayLiteralElement(): Expression { return token === SyntaxKind.DotDotDotToken ? parseSpreadElement() : token === SyntaxKind.CommaToken ? createNode(SyntaxKind.OmittedExpression) : - parseAssignmentExpressionOrHigher(); + parseAssignmentExpressionOrHigher(); } function parseArgumentExpression(): Expression { @@ -5315,7 +4752,7 @@ module ts { function processReferenceComments(sourceFile: SourceFile): void { let triviaScanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/false, sourceText); let referencedFiles: FileReference[] = []; - let amdDependencies: {path: string; name: string}[] = []; + let amdDependencies: { path: string; name: string }[] = []; let amdModuleName: string; // Keep scanning all the leading trivia in the file until we get to something that @@ -5363,7 +4800,7 @@ module ts { let pathMatchResult = pathRegex.exec(comment); let nameMatchResult = nameRegex.exec(comment); if (pathMatchResult) { - let amdDependency = {path: pathMatchResult[2], name: nameMatchResult ? nameMatchResult[2] : undefined }; + let amdDependency = { path: pathMatchResult[2], name: nameMatchResult ? nameMatchResult[2] : undefined }; amdDependencies.push(amdDependency); } } @@ -5378,47 +4815,605 @@ module ts { function setExternalModuleIndicator(sourceFile: SourceFile) { sourceFile.externalModuleIndicator = forEach(sourceFile.statements, node => node.flags & NodeFlags.Export - || node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference - || node.kind === SyntaxKind.ImportDeclaration - || node.kind === SyntaxKind.ExportAssignment - || node.kind === SyntaxKind.ExportDeclaration + || node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference + || node.kind === SyntaxKind.ImportDeclaration + || node.kind === SyntaxKind.ExportAssignment + || node.kind === SyntaxKind.ExportDeclaration ? node : undefined); } + + 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 + HeritageClauseElement, // Elements in a heritage 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. + ImportOrExportSpecifiers, // Named import clause's import specifier list + Count // Number of parsing contexts + } + + const enum Tristate { + False, + True, + Unknown + } } - 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.ClassExpression: - 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; + module IncrementalParser { + export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile { + aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); + + checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); + 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 Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setNodeParents*/ true) + } + + // Make sure we're not trying to incrementally update a source file more than once. Once + // we do an update the original source file is considered unusbale from that point onwards. + // + // This is because we do incremental parsing in-place. i.e. we take nodes from the old + // tree and give them new positions and parents. From that point on, trusting the old + // tree at all is not possible as far too much of it may violate invariants. + let incrementalSourceFile = sourceFile; + Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); + incrementalSourceFile.hasBeenIncrementallyParsed = true; + + let oldText = sourceFile.text; + let syntaxCursor = createSyntaxCursor(sourceFile); + + // Make the actual change larger so that we know to reparse anything whose lookahead + // might have intersected the change. + let changeRange = extendToAffectedRange(sourceFile, textChangeRange); + checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks); + + // Ensure that extending the affected range only moved the start of the change range + // earlier in the file. + Debug.assert(changeRange.span.start <= textChangeRange.span.start); + Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span)); + Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(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). + let 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 (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 the 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(incrementalSourceFile, + changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); + + // 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. + let result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /* setParentNode */ true) + + return result; + } + + function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { + if (isArray) { + visitArray(element); + } + else { + visitNode(element); + } + return; + + function visitNode(node: IncrementalNode) { + if (aggressiveChecks && shouldCheckNode(node)) { + var text = oldText.substring(node.pos, node.end); + } + + // 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; + + if (aggressiveChecks && shouldCheckNode(node)) { + Debug.assert(text === newText.substring(node.pos, node.end)); + } + + forEachChild(node, visitNode, visitArray); + checkNodePositions(node, aggressiveChecks); + } + + function visitArray(array: IncrementalNodeArray) { + array._children = undefined; + array.pos += delta; + array.end += delta; + + for (let node of array) { + visitNode(node); + } } } - return false; - } + function shouldCheckNode(node: Node) { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.Identifier: + return true; + } - export function isAssignmentOperator(token: SyntaxKind): boolean { - return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment; + return false; + } + + 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"); + Debug.assert(element.pos <= element.end); + + // 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 checkNodePositions(node: Node, aggressiveChecks: boolean) { + if (aggressiveChecks) { + let pos = node.pos; + forEachChild(node, child => { + Debug.assert(child.pos >= pos); + pos = child.end; + }); + Debug.assert(pos <= node.end); + } + } + + function updateTokenPositionsAndMarkElements( + sourceFile: IncrementalNode, + changeStart: number, + changeRangeOldEnd: number, + changeRangeNewEnd: number, + delta: number, + oldText: string, + newText: string, + aggressiveChecks: boolean): void { + + visitNode(sourceFile); + return; + + function visitNode(child: IncrementalNode) { + Debug.assert(child.pos <= child.end); + 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, /*isArray:*/ false, delta, oldText, newText, aggressiveChecks); + 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. + let fullEnd = child.end; + if (fullEnd >= changeStart) { + child.intersectsChange = true; + child._children = undefined; + + // Adjust the pos or end (or both) of the intersecting element accordingly. + adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); + forEachChild(child, visitNode, visitArray); + + checkNodePositions(child, aggressiveChecks); + return; + } + + // Otherwise, the node is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); + } + + function visitArray(array: IncrementalNodeArray) { + Debug.assert(array.pos <= array.end); + if (array.pos > changeRangeOldEnd) { + // Array is entirely after the change range. We need to move it, and move any of + // its children. + moveElementEntirelyPastChangeRange(array, /*isArray:*/ true, delta, oldText, newText, aggressiveChecks); + 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. + let fullEnd = array.end; + if (fullEnd >= changeStart) { + array.intersectsChange = true; + array._children = undefined; + + // Adjust the pos or end (or both) of the intersecting array accordingly. + adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); + for (let node of array) { + visitNode(node); + } + return; + } + + // Otherwise, the array is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); + } + } + + function extendToAffectedRange(sourceFile: SourceFile, 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. + let maxLookahead = 1; + + let 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 (let i = 0; start > 0 && i <= maxLookahead; i++) { + let nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start); + Debug.assert(nearestNode.pos <= start); + let position = nearestNode.pos; + + start = Math.max(0, position - 1); + } + + let finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); + let finalLength = changeRange.newLength + (changeRange.span.start - start); + + return createTextChangeRange(finalSpan, finalLength); + } + + function findNearestNodeStartingBeforeOrAtPosition(sourceFile: SourceFile, position: number): Node { + let bestResult: Node = sourceFile; + let lastNodeEntirelyBeforePosition: Node; + + forEachChild(sourceFile, visit); + + if (lastNodeEntirelyBeforePosition) { + let lastChildOfLastEntireNodeBeforePosition = getLastChild(lastNodeEntirelyBeforePosition); + if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { + bestResult = lastChildOfLastEntireNodeBeforePosition; + } + } + + return bestResult; + + function getLastChild(node: Node): Node { + while (true) { + let lastChild = getLastChildWorker(node); + if (lastChild) { + node = lastChild; + } + else { + return node; + } + } + } + + function getLastChildWorker(node: Node): Node { + let 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 checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) { + let oldText = sourceFile.text; + if (textChangeRange) { + Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); + + if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) { + let oldTextPrefix = oldText.substr(0, textChangeRange.span.start); + let newTextPrefix = newText.substr(0, textChangeRange.span.start); + Debug.assert(oldTextPrefix === newTextPrefix); + + let oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); + let newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); + Debug.assert(oldTextSuffix === newTextSuffix); + } + } + } + + interface IncrementalElement extends TextRange { + parent?: Node; + intersectsChange: boolean + length?: number; + _children: Node[]; + } + + export interface IncrementalNode extends Node, IncrementalElement { + hasBeenIncrementallyParsed: boolean + } + + 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. + export interface SyntaxCursor { + currentNode(position: number): IncrementalNode; + } + + function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { + let currentArray: NodeArray = sourceFile.statements; + let currentArrayIndex = 0; + + Debug.assert(currentArrayIndex < currentArray.length); + let current = currentArray[currentArrayIndex]; + let 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 - 1)) { + 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); + return; + + 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 (let i = 0, n = array.length; i < n; i++) { + let 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; + } + } + } + + const enum InvalidPosition { + Value = -1 + } } -} +} \ No newline at end of file diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 46466478029..3cf0f34e54f 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -54,6 +54,7 @@ module ts { } text = ""; } + return text !== undefined ? createSourceFile(fileName, text, languageVersion, setParentNodes) : undefined; } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 327fb5261a0..53d213f5bd5 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -25,6 +25,8 @@ module ts { reScanTemplateToken(): SyntaxKind; scan(): SyntaxKind; setText(text: string): void; + setOnError(onError: ErrorCallback): void; + setScriptTarget(scriptTarget: ScriptTarget): void; setTextPos(textPos: number): void; // Invokes the provided callback then unconditionally restores the scanner to the state it // was in immediately prior to invoking the callback. The result of invoking the callback @@ -599,6 +601,7 @@ module ts { export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean, text?: string, onError?: ErrorCallback): Scanner { let pos: number; // Current position (end position of text of current token) let len: number; // Length of text + let startPos: number; // Start position of whitespace before current token let tokenPos: number; // Start position of text of current token let token: SyntaxKind; @@ -607,6 +610,32 @@ module ts { let hasExtendedUnicodeEscape: boolean; let tokenIsUnterminated: boolean; + setText(text); + + return { + getStartPos: () => startPos, + getTextPos: () => pos, + getToken: () => token, + getTokenPos: () => tokenPos, + getTokenText: () => text.substring(tokenPos, pos), + getTokenValue: () => tokenValue, + hasExtendedUnicodeEscape: () => hasExtendedUnicodeEscape, + hasPrecedingLineBreak: () => precedingLineBreak, + isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, + isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, + isUnterminated: () => tokenIsUnterminated, + reScanGreaterToken, + reScanSlashToken, + reScanTemplateToken, + scan, + setText, + setScriptTarget, + setOnError, + setTextPos, + tryScan, + lookAhead, + }; + function error(message: DiagnosticMessage, length?: number): void { if (onError) { onError(message, length || 0); @@ -1450,37 +1479,24 @@ module ts { setTextPos(0); } + function setOnError(errorCallback: ErrorCallback) { + onError = errorCallback; + } + + function setScriptTarget(scriptTarget: ScriptTarget) { + languageVersion = scriptTarget; + } + function setTextPos(textPos: number) { pos = textPos; startPos = textPos; tokenPos = textPos; token = SyntaxKind.Unknown; precedingLineBreak = false; + + tokenValue = undefined; + hasExtendedUnicodeEscape = false; + tokenIsUnterminated = false; } - - setText(text); - - - return { - getStartPos: () => startPos, - getTextPos: () => pos, - getToken: () => token, - getTokenPos: () => tokenPos, - getTokenText: () => text.substring(tokenPos, pos), - getTokenValue: () => tokenValue, - hasExtendedUnicodeEscape: () => hasExtendedUnicodeEscape, - hasPrecedingLineBreak: () => precedingLineBreak, - isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, - isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, - isUnterminated: () => tokenIsUnterminated, - reScanGreaterToken, - reScanSlashToken, - reScanTemplateToken, - scan, - setText, - setTextPos, - tryScan, - lookAhead, - }; } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8a34d2f315b..2530e358662 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1612,6 +1612,55 @@ module ts { } } + 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; + case SyntaxKind.DefaultKeyword: return NodeFlags.Default; + } + return 0; + } + + 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.ClassExpression: + 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; + } + // Returns false if this heritage clause element's expression contains something unsupported // (i.e. not a name or dotted name). export function isSupportedHeritageClauseElement(node: HeritageClauseElement): boolean { diff --git a/tests/baselines/reference/APISample_compile.types b/tests/baselines/reference/APISample_compile.types index 8c92b99cada..f5ad75b8bcb 100644 --- a/tests/baselines/reference/APISample_compile.types +++ b/tests/baselines/reference/APISample_compile.types @@ -28,9 +28,9 @@ export function compile(fileNames: string[], options: ts.CompilerOptions): void var program = ts.createProgram(fileNames, options); >program : ts.Program, Symbol(program, Decl(APISample_compile.ts, 14, 7)) >ts.createProgram(fileNames, options) : ts.Program ->ts.createProgram : (rootNames: string[], options: ts.CompilerOptions, host?: ts.CompilerHost) => ts.Program, Symbol(ts.createProgram, Decl(typescript.d.ts, 1229, 113)) +>ts.createProgram : (rootNames: string[], options: ts.CompilerOptions, host?: ts.CompilerHost) => ts.Program, Symbol(ts.createProgram, Decl(typescript.d.ts, 1225, 113)) >ts : typeof ts, Symbol(ts, Decl(APISample_compile.ts, 9, 20)) ->createProgram : (rootNames: string[], options: ts.CompilerOptions, host?: ts.CompilerHost) => ts.Program, Symbol(ts.createProgram, Decl(typescript.d.ts, 1229, 113)) +>createProgram : (rootNames: string[], options: ts.CompilerOptions, host?: ts.CompilerHost) => ts.Program, Symbol(ts.createProgram, Decl(typescript.d.ts, 1225, 113)) >fileNames : string[], Symbol(fileNames, Decl(APISample_compile.ts, 13, 24)) >options : ts.CompilerOptions, Symbol(options, Decl(APISample_compile.ts, 13, 44)) @@ -46,9 +46,9 @@ export function compile(fileNames: string[], options: ts.CompilerOptions): void >ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics) : ts.Diagnostic[] >ts.getPreEmitDiagnostics(program).concat : { (...items: U[]): ts.Diagnostic[]; (...items: ts.Diagnostic[]): ts.Diagnostic[]; }, Symbol(Array.concat, Decl(lib.d.ts, 1025, 13), Decl(lib.d.ts, 1030, 46)) >ts.getPreEmitDiagnostics(program) : ts.Diagnostic[] ->ts.getPreEmitDiagnostics : (program: ts.Program) => ts.Diagnostic[], Symbol(ts.getPreEmitDiagnostics, Decl(typescript.d.ts, 1227, 98)) +>ts.getPreEmitDiagnostics : (program: ts.Program) => ts.Diagnostic[], Symbol(ts.getPreEmitDiagnostics, Decl(typescript.d.ts, 1223, 98)) >ts : typeof ts, Symbol(ts, Decl(APISample_compile.ts, 9, 20)) ->getPreEmitDiagnostics : (program: ts.Program) => ts.Diagnostic[], Symbol(ts.getPreEmitDiagnostics, Decl(typescript.d.ts, 1227, 98)) +>getPreEmitDiagnostics : (program: ts.Program) => ts.Diagnostic[], Symbol(ts.getPreEmitDiagnostics, Decl(typescript.d.ts, 1223, 98)) >program : ts.Program, Symbol(program, Decl(APISample_compile.ts, 14, 7)) >concat : { (...items: U[]): ts.Diagnostic[]; (...items: ts.Diagnostic[]): ts.Diagnostic[]; }, Symbol(Array.concat, Decl(lib.d.ts, 1025, 13), Decl(lib.d.ts, 1030, 46)) >emitResult.diagnostics : ts.Diagnostic[], Symbol(ts.EmitResult.diagnostics, Decl(typescript.d.ts, 820, 29)) @@ -67,11 +67,11 @@ export function compile(fileNames: string[], options: ts.CompilerOptions): void >line : number, Symbol(line, Decl(APISample_compile.ts, 20, 13)) >character : number, Symbol(character, Decl(APISample_compile.ts, 20, 19)) >diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start) : ts.LineAndCharacter ->diagnostic.file.getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1290, 26)) +>diagnostic.file.getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1286, 26)) >diagnostic.file : ts.SourceFile, Symbol(ts.Diagnostic.file, Decl(typescript.d.ts, 1062, 26)) >diagnostic : ts.Diagnostic, Symbol(diagnostic, Decl(APISample_compile.ts, 19, 27)) >file : ts.SourceFile, Symbol(ts.Diagnostic.file, Decl(typescript.d.ts, 1062, 26)) ->getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1290, 26)) +>getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1286, 26)) >diagnostic.start : number, Symbol(ts.Diagnostic.start, Decl(typescript.d.ts, 1063, 25)) >diagnostic : ts.Diagnostic, Symbol(diagnostic, Decl(APISample_compile.ts, 19, 27)) >start : number, Symbol(ts.Diagnostic.start, Decl(typescript.d.ts, 1063, 25)) @@ -79,9 +79,9 @@ export function compile(fileNames: string[], options: ts.CompilerOptions): void var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); >message : string, Symbol(message, Decl(APISample_compile.ts, 21, 11)) >ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n') : string ->ts.flattenDiagnosticMessageText : (messageText: string | ts.DiagnosticMessageChain, newLine: string) => string, Symbol(ts.flattenDiagnosticMessageText, Decl(typescript.d.ts, 1228, 67)) +>ts.flattenDiagnosticMessageText : (messageText: string | ts.DiagnosticMessageChain, newLine: string) => string, Symbol(ts.flattenDiagnosticMessageText, Decl(typescript.d.ts, 1224, 67)) >ts : typeof ts, Symbol(ts, Decl(APISample_compile.ts, 9, 20)) ->flattenDiagnosticMessageText : (messageText: string | ts.DiagnosticMessageChain, newLine: string) => string, Symbol(ts.flattenDiagnosticMessageText, Decl(typescript.d.ts, 1228, 67)) +>flattenDiagnosticMessageText : (messageText: string | ts.DiagnosticMessageChain, newLine: string) => string, Symbol(ts.flattenDiagnosticMessageText, Decl(typescript.d.ts, 1224, 67)) >diagnostic.messageText : string | ts.DiagnosticMessageChain, Symbol(ts.Diagnostic.messageText, Decl(typescript.d.ts, 1065, 23)) >diagnostic : ts.Diagnostic, Symbol(diagnostic, Decl(APISample_compile.ts, 19, 27)) >messageText : string | ts.DiagnosticMessageChain, Symbol(ts.Diagnostic.messageText, Decl(typescript.d.ts, 1065, 23)) diff --git a/tests/baselines/reference/APISample_linter.types b/tests/baselines/reference/APISample_linter.types index e7ea3c8282c..e566af8cfa6 100644 --- a/tests/baselines/reference/APISample_linter.types +++ b/tests/baselines/reference/APISample_linter.types @@ -22,7 +22,7 @@ export function delint(sourceFile: ts.SourceFile) { >delint : (sourceFile: ts.SourceFile) => void, Symbol(delint, Decl(APISample_linter.ts, 11, 33)) >sourceFile : ts.SourceFile, Symbol(sourceFile, Decl(APISample_linter.ts, 13, 23)) >ts : any, Symbol(ts, Decl(APISample_linter.ts, 11, 6)) ->SourceFile : ts.SourceFile, Symbol(ts.SourceFile, Decl(typescript.d.ts, 740, 5), Decl(typescript.d.ts, 1289, 5)) +>SourceFile : ts.SourceFile, Symbol(ts.SourceFile, Decl(typescript.d.ts, 740, 5), Decl(typescript.d.ts, 1285, 5)) delintNode(sourceFile); >delintNode(sourceFile) : void @@ -33,7 +33,7 @@ export function delint(sourceFile: ts.SourceFile) { >delintNode : (node: ts.Node) => void, Symbol(delintNode, Decl(APISample_linter.ts, 14, 27)) >node : ts.Node, Symbol(node, Decl(APISample_linter.ts, 16, 24)) >ts : any, Symbol(ts, Decl(APISample_linter.ts, 11, 6)) ->Node : ts.Node, Symbol(ts.Node, Decl(typescript.d.ts, 296, 5), Decl(typescript.d.ts, 1249, 32)) +>Node : ts.Node, Symbol(ts.Node, Decl(typescript.d.ts, 296, 5), Decl(typescript.d.ts, 1245, 32)) switch (node.kind) { >node.kind : ts.SyntaxKind, Symbol(ts.Node.kind, Decl(typescript.d.ts, 297, 38)) @@ -230,20 +230,20 @@ export function delint(sourceFile: ts.SourceFile) { >report : (node: ts.Node, message: string) => void, Symbol(report, Decl(APISample_linter.ts, 48, 5)) >node : ts.Node, Symbol(node, Decl(APISample_linter.ts, 50, 20)) >ts : any, Symbol(ts, Decl(APISample_linter.ts, 11, 6)) ->Node : ts.Node, Symbol(ts.Node, Decl(typescript.d.ts, 296, 5), Decl(typescript.d.ts, 1249, 32)) +>Node : ts.Node, Symbol(ts.Node, Decl(typescript.d.ts, 296, 5), Decl(typescript.d.ts, 1245, 32)) >message : string, Symbol(message, Decl(APISample_linter.ts, 50, 34)) let { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); >line : number, Symbol(line, Decl(APISample_linter.ts, 51, 13)) >character : number, Symbol(character, Decl(APISample_linter.ts, 51, 19)) >sourceFile.getLineAndCharacterOfPosition(node.getStart()) : ts.LineAndCharacter ->sourceFile.getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1290, 26)) +>sourceFile.getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1286, 26)) >sourceFile : ts.SourceFile, Symbol(sourceFile, Decl(APISample_linter.ts, 13, 23)) ->getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1290, 26)) +>getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1286, 26)) >node.getStart() : number ->node.getStart : (sourceFile?: ts.SourceFile) => number, Symbol(ts.Node.getStart, Decl(typescript.d.ts, 1254, 53)) +>node.getStart : (sourceFile?: ts.SourceFile) => number, Symbol(ts.Node.getStart, Decl(typescript.d.ts, 1250, 53)) >node : ts.Node, Symbol(node, Decl(APISample_linter.ts, 50, 20)) ->getStart : (sourceFile?: ts.SourceFile) => number, Symbol(ts.Node.getStart, Decl(typescript.d.ts, 1254, 53)) +>getStart : (sourceFile?: ts.SourceFile) => number, Symbol(ts.Node.getStart, Decl(typescript.d.ts, 1250, 53)) console.log(`${sourceFile.fileName} (${line + 1},${character + 1}): ${message}`); >console.log(`${sourceFile.fileName} (${line + 1},${character + 1}): ${message}`) : any @@ -286,9 +286,9 @@ fileNames.forEach(fileName => { let sourceFile = ts.createSourceFile(fileName, readFileSync(fileName).toString(), ts.ScriptTarget.ES6, /*setParentNodes */ true); >sourceFile : ts.SourceFile, Symbol(sourceFile, Decl(APISample_linter.ts, 59, 7)) >ts.createSourceFile(fileName, readFileSync(fileName).toString(), ts.ScriptTarget.ES6, /*setParentNodes */ true) : ts.SourceFile ->ts.createSourceFile : (fileName: string, sourceText: string, languageVersion: ts.ScriptTarget, setParentNodes?: boolean) => ts.SourceFile, Symbol(ts.createSourceFile, Decl(typescript.d.ts, 1218, 62)) +>ts.createSourceFile : (fileName: string, sourceText: string, languageVersion: ts.ScriptTarget, setParentNodes?: boolean) => ts.SourceFile, Symbol(ts.createSourceFile, Decl(typescript.d.ts, 1215, 107)) >ts : typeof ts, Symbol(ts, Decl(APISample_linter.ts, 11, 6)) ->createSourceFile : (fileName: string, sourceText: string, languageVersion: ts.ScriptTarget, setParentNodes?: boolean) => ts.SourceFile, Symbol(ts.createSourceFile, Decl(typescript.d.ts, 1218, 62)) +>createSourceFile : (fileName: string, sourceText: string, languageVersion: ts.ScriptTarget, setParentNodes?: boolean) => ts.SourceFile, Symbol(ts.createSourceFile, Decl(typescript.d.ts, 1215, 107)) >fileName : any, Symbol(fileName, Decl(APISample_linter.ts, 57, 18)) >readFileSync(fileName).toString() : any >readFileSync(fileName).toString : any diff --git a/tests/baselines/reference/APISample_transform.types b/tests/baselines/reference/APISample_transform.types index daacf3c3736..524d7c27426 100644 --- a/tests/baselines/reference/APISample_transform.types +++ b/tests/baselines/reference/APISample_transform.types @@ -19,9 +19,9 @@ const source = "let x: string = 'string'"; let result = ts.transpile(source, { module: ts.ModuleKind.CommonJS }); >result : string, Symbol(result, Decl(APISample_transform.ts, 13, 3)) >ts.transpile(source, { module: ts.ModuleKind.CommonJS }) : string ->ts.transpile : (input: string, compilerOptions?: ts.CompilerOptions, fileName?: string, diagnostics?: ts.Diagnostic[]) => string, Symbol(ts.transpile, Decl(typescript.d.ts, 1756, 5)) +>ts.transpile : (input: string, compilerOptions?: ts.CompilerOptions, fileName?: string, diagnostics?: ts.Diagnostic[]) => string, Symbol(ts.transpile, Decl(typescript.d.ts, 1752, 5)) >ts : typeof ts, Symbol(ts, Decl(APISample_transform.ts, 9, 6)) ->transpile : (input: string, compilerOptions?: ts.CompilerOptions, fileName?: string, diagnostics?: ts.Diagnostic[]) => string, Symbol(ts.transpile, Decl(typescript.d.ts, 1756, 5)) +>transpile : (input: string, compilerOptions?: ts.CompilerOptions, fileName?: string, diagnostics?: ts.Diagnostic[]) => string, Symbol(ts.transpile, Decl(typescript.d.ts, 1752, 5)) >source : string, Symbol(source, Decl(APISample_transform.ts, 11, 5)) >{ module: ts.ModuleKind.CommonJS } : { [x: string]: ts.ModuleKind; module: ts.ModuleKind; } >module : ts.ModuleKind, Symbol(module, Decl(APISample_transform.ts, 13, 35)) diff --git a/tests/baselines/reference/APISample_watcher.types b/tests/baselines/reference/APISample_watcher.types index b08a624a33e..5fb394e6418 100644 --- a/tests/baselines/reference/APISample_watcher.types +++ b/tests/baselines/reference/APISample_watcher.types @@ -59,7 +59,7 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { const servicesHost: ts.LanguageServiceHost = { >servicesHost : ts.LanguageServiceHost, Symbol(servicesHost, Decl(APISample_watcher.ts, 23, 9)) >ts : any, Symbol(ts, Decl(APISample_watcher.ts, 12, 6)) ->LanguageServiceHost : ts.LanguageServiceHost, Symbol(ts.LanguageServiceHost, Decl(typescript.d.ts, 1322, 5)) +>LanguageServiceHost : ts.LanguageServiceHost, Symbol(ts.LanguageServiceHost, Decl(typescript.d.ts, 1318, 5)) >{ getScriptFileNames: () => rootFileNames, getScriptVersion: (fileName) => files[fileName] && files[fileName].version.toString(), getScriptSnapshot: (fileName) => { if (!fs.existsSync(fileName)) { return undefined; } return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); }, getCurrentDirectory: () => process.cwd(), getCompilationSettings: () => options, getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options), } : { getScriptFileNames: () => string[]; getScriptVersion: (fileName: string) => string; getScriptSnapshot: (fileName: string) => ts.IScriptSnapshot; getCurrentDirectory: () => any; getCompilationSettings: () => ts.CompilerOptions; getDefaultLibFileName: (options: ts.CompilerOptions) => string; } getScriptFileNames: () => rootFileNames, @@ -103,11 +103,11 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); >ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()) : ts.IScriptSnapshot ->ts.ScriptSnapshot.fromString : (text: string) => ts.IScriptSnapshot, Symbol(ts.ScriptSnapshot.fromString, Decl(typescript.d.ts, 1315, 27)) ->ts.ScriptSnapshot : typeof ts.ScriptSnapshot, Symbol(ts.ScriptSnapshot, Decl(typescript.d.ts, 1314, 5)) +>ts.ScriptSnapshot.fromString : (text: string) => ts.IScriptSnapshot, Symbol(ts.ScriptSnapshot.fromString, Decl(typescript.d.ts, 1311, 27)) +>ts.ScriptSnapshot : typeof ts.ScriptSnapshot, Symbol(ts.ScriptSnapshot, Decl(typescript.d.ts, 1310, 5)) >ts : typeof ts, Symbol(ts, Decl(APISample_watcher.ts, 12, 6)) ->ScriptSnapshot : typeof ts.ScriptSnapshot, Symbol(ts.ScriptSnapshot, Decl(typescript.d.ts, 1314, 5)) ->fromString : (text: string) => ts.IScriptSnapshot, Symbol(ts.ScriptSnapshot.fromString, Decl(typescript.d.ts, 1315, 27)) +>ScriptSnapshot : typeof ts.ScriptSnapshot, Symbol(ts.ScriptSnapshot, Decl(typescript.d.ts, 1310, 5)) +>fromString : (text: string) => ts.IScriptSnapshot, Symbol(ts.ScriptSnapshot.fromString, Decl(typescript.d.ts, 1311, 27)) >fs.readFileSync(fileName).toString() : any >fs.readFileSync(fileName).toString : any >fs.readFileSync(fileName) : any @@ -136,9 +136,9 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { >(options) => ts.getDefaultLibFilePath(options) : (options: ts.CompilerOptions) => string >options : ts.CompilerOptions, Symbol(options, Decl(APISample_watcher.ts, 35, 32)) >ts.getDefaultLibFilePath(options) : string ->ts.getDefaultLibFilePath : (options: ts.CompilerOptions) => string, Symbol(ts.getDefaultLibFilePath, Decl(typescript.d.ts, 1764, 44)) +>ts.getDefaultLibFilePath : (options: ts.CompilerOptions) => string, Symbol(ts.getDefaultLibFilePath, Decl(typescript.d.ts, 1760, 44)) >ts : typeof ts, Symbol(ts, Decl(APISample_watcher.ts, 12, 6)) ->getDefaultLibFilePath : (options: ts.CompilerOptions) => string, Symbol(ts.getDefaultLibFilePath, Decl(typescript.d.ts, 1764, 44)) +>getDefaultLibFilePath : (options: ts.CompilerOptions) => string, Symbol(ts.getDefaultLibFilePath, Decl(typescript.d.ts, 1760, 44)) >options : ts.CompilerOptions, Symbol(options, Decl(APISample_watcher.ts, 35, 32)) }; @@ -147,14 +147,14 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()) >services : ts.LanguageService, Symbol(services, Decl(APISample_watcher.ts, 39, 9)) >ts.createLanguageService(servicesHost, ts.createDocumentRegistry()) : ts.LanguageService ->ts.createLanguageService : (host: ts.LanguageServiceHost, documentRegistry?: ts.DocumentRegistry) => ts.LanguageService, Symbol(ts.createLanguageService, Decl(typescript.d.ts, 1762, 97)) +>ts.createLanguageService : (host: ts.LanguageServiceHost, documentRegistry?: ts.DocumentRegistry) => ts.LanguageService, Symbol(ts.createLanguageService, Decl(typescript.d.ts, 1758, 97)) >ts : typeof ts, Symbol(ts, Decl(APISample_watcher.ts, 12, 6)) ->createLanguageService : (host: ts.LanguageServiceHost, documentRegistry?: ts.DocumentRegistry) => ts.LanguageService, Symbol(ts.createLanguageService, Decl(typescript.d.ts, 1762, 97)) +>createLanguageService : (host: ts.LanguageServiceHost, documentRegistry?: ts.DocumentRegistry) => ts.LanguageService, Symbol(ts.createLanguageService, Decl(typescript.d.ts, 1758, 97)) >servicesHost : ts.LanguageServiceHost, Symbol(servicesHost, Decl(APISample_watcher.ts, 23, 9)) >ts.createDocumentRegistry() : ts.DocumentRegistry ->ts.createDocumentRegistry : () => ts.DocumentRegistry, Symbol(ts.createDocumentRegistry, Decl(typescript.d.ts, 1760, 193)) +>ts.createDocumentRegistry : () => ts.DocumentRegistry, Symbol(ts.createDocumentRegistry, Decl(typescript.d.ts, 1756, 193)) >ts : typeof ts, Symbol(ts, Decl(APISample_watcher.ts, 12, 6)) ->createDocumentRegistry : () => ts.DocumentRegistry, Symbol(ts.createDocumentRegistry, Decl(typescript.d.ts, 1760, 193)) +>createDocumentRegistry : () => ts.DocumentRegistry, Symbol(ts.createDocumentRegistry, Decl(typescript.d.ts, 1756, 193)) // Now let's watch the files rootFileNames.forEach(fileName => { @@ -231,16 +231,16 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { let output = services.getEmitOutput(fileName); >output : ts.EmitOutput, Symbol(output, Decl(APISample_watcher.ts, 64, 11)) >services.getEmitOutput(fileName) : ts.EmitOutput ->services.getEmitOutput : (fileName: string) => ts.EmitOutput, Symbol(ts.LanguageService.getEmitOutput, Decl(typescript.d.ts, 1366, 132)) +>services.getEmitOutput : (fileName: string) => ts.EmitOutput, Symbol(ts.LanguageService.getEmitOutput, Decl(typescript.d.ts, 1362, 132)) >services : ts.LanguageService, Symbol(services, Decl(APISample_watcher.ts, 39, 9)) ->getEmitOutput : (fileName: string) => ts.EmitOutput, Symbol(ts.LanguageService.getEmitOutput, Decl(typescript.d.ts, 1366, 132)) +>getEmitOutput : (fileName: string) => ts.EmitOutput, Symbol(ts.LanguageService.getEmitOutput, Decl(typescript.d.ts, 1362, 132)) >fileName : string, Symbol(fileName, Decl(APISample_watcher.ts, 63, 22)) if (!output.emitSkipped) { >!output.emitSkipped : boolean ->output.emitSkipped : boolean, Symbol(ts.EmitOutput.emitSkipped, Decl(typescript.d.ts, 1569, 34)) +>output.emitSkipped : boolean, Symbol(ts.EmitOutput.emitSkipped, Decl(typescript.d.ts, 1565, 34)) >output : ts.EmitOutput, Symbol(output, Decl(APISample_watcher.ts, 64, 11)) ->emitSkipped : boolean, Symbol(ts.EmitOutput.emitSkipped, Decl(typescript.d.ts, 1569, 34)) +>emitSkipped : boolean, Symbol(ts.EmitOutput.emitSkipped, Decl(typescript.d.ts, 1565, 34)) console.log(`Emitting ${fileName}`); >console.log(`Emitting ${fileName}`) : any @@ -268,9 +268,9 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { output.outputFiles.forEach(o => { >output.outputFiles.forEach(o => { fs.writeFileSync(o.name, o.text, "utf8"); }) : void >output.outputFiles.forEach : (callbackfn: (value: ts.OutputFile, index: number, array: ts.OutputFile[]) => void, thisArg?: any) => void, Symbol(Array.forEach, Decl(lib.d.ts, 1108, 95)) ->output.outputFiles : ts.OutputFile[], Symbol(ts.EmitOutput.outputFiles, Decl(typescript.d.ts, 1568, 26)) +>output.outputFiles : ts.OutputFile[], Symbol(ts.EmitOutput.outputFiles, Decl(typescript.d.ts, 1564, 26)) >output : ts.EmitOutput, Symbol(output, Decl(APISample_watcher.ts, 64, 11)) ->outputFiles : ts.OutputFile[], Symbol(ts.EmitOutput.outputFiles, Decl(typescript.d.ts, 1568, 26)) +>outputFiles : ts.OutputFile[], Symbol(ts.EmitOutput.outputFiles, Decl(typescript.d.ts, 1564, 26)) >forEach : (callbackfn: (value: ts.OutputFile, index: number, array: ts.OutputFile[]) => void, thisArg?: any) => void, Symbol(Array.forEach, Decl(lib.d.ts, 1108, 95)) >o => { fs.writeFileSync(o.name, o.text, "utf8"); } : (o: ts.OutputFile) => void >o : ts.OutputFile, Symbol(o, Decl(APISample_watcher.ts, 74, 35)) @@ -280,12 +280,12 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { >fs.writeFileSync : any >fs : any, Symbol(fs, Decl(APISample_watcher.ts, 9, 11)) >writeFileSync : any ->o.name : string, Symbol(ts.OutputFile.name, Decl(typescript.d.ts, 1577, 26)) +>o.name : string, Symbol(ts.OutputFile.name, Decl(typescript.d.ts, 1573, 26)) >o : ts.OutputFile, Symbol(o, Decl(APISample_watcher.ts, 74, 35)) ->name : string, Symbol(ts.OutputFile.name, Decl(typescript.d.ts, 1577, 26)) ->o.text : string, Symbol(ts.OutputFile.text, Decl(typescript.d.ts, 1579, 36)) +>name : string, Symbol(ts.OutputFile.name, Decl(typescript.d.ts, 1573, 26)) +>o.text : string, Symbol(ts.OutputFile.text, Decl(typescript.d.ts, 1575, 36)) >o : ts.OutputFile, Symbol(o, Decl(APISample_watcher.ts, 74, 35)) ->text : string, Symbol(ts.OutputFile.text, Decl(typescript.d.ts, 1579, 36)) +>text : string, Symbol(ts.OutputFile.text, Decl(typescript.d.ts, 1575, 36)) >"utf8" : string }); @@ -302,24 +302,24 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { >services.getCompilerOptionsDiagnostics() .concat(services.getSyntacticDiagnostics(fileName)) : ts.Diagnostic[] >services.getCompilerOptionsDiagnostics() .concat : { (...items: U[]): ts.Diagnostic[]; (...items: ts.Diagnostic[]): ts.Diagnostic[]; }, Symbol(Array.concat, Decl(lib.d.ts, 1025, 13), Decl(lib.d.ts, 1030, 46)) >services.getCompilerOptionsDiagnostics() : ts.Diagnostic[] ->services.getCompilerOptionsDiagnostics : () => ts.Diagnostic[], Symbol(ts.LanguageService.getCompilerOptionsDiagnostics, Decl(typescript.d.ts, 1340, 63)) +>services.getCompilerOptionsDiagnostics : () => ts.Diagnostic[], Symbol(ts.LanguageService.getCompilerOptionsDiagnostics, Decl(typescript.d.ts, 1336, 63)) >services : ts.LanguageService, Symbol(services, Decl(APISample_watcher.ts, 39, 9)) ->getCompilerOptionsDiagnostics : () => ts.Diagnostic[], Symbol(ts.LanguageService.getCompilerOptionsDiagnostics, Decl(typescript.d.ts, 1340, 63)) +>getCompilerOptionsDiagnostics : () => ts.Diagnostic[], Symbol(ts.LanguageService.getCompilerOptionsDiagnostics, Decl(typescript.d.ts, 1336, 63)) .concat(services.getSyntacticDiagnostics(fileName)) >concat : { (...items: U[]): ts.Diagnostic[]; (...items: ts.Diagnostic[]): ts.Diagnostic[]; }, Symbol(Array.concat, Decl(lib.d.ts, 1025, 13), Decl(lib.d.ts, 1030, 46)) >services.getSyntacticDiagnostics(fileName) : ts.Diagnostic[] ->services.getSyntacticDiagnostics : (fileName: string) => ts.Diagnostic[], Symbol(ts.LanguageService.getSyntacticDiagnostics, Decl(typescript.d.ts, 1338, 37)) +>services.getSyntacticDiagnostics : (fileName: string) => ts.Diagnostic[], Symbol(ts.LanguageService.getSyntacticDiagnostics, Decl(typescript.d.ts, 1334, 37)) >services : ts.LanguageService, Symbol(services, Decl(APISample_watcher.ts, 39, 9)) ->getSyntacticDiagnostics : (fileName: string) => ts.Diagnostic[], Symbol(ts.LanguageService.getSyntacticDiagnostics, Decl(typescript.d.ts, 1338, 37)) +>getSyntacticDiagnostics : (fileName: string) => ts.Diagnostic[], Symbol(ts.LanguageService.getSyntacticDiagnostics, Decl(typescript.d.ts, 1334, 37)) >fileName : string, Symbol(fileName, Decl(APISample_watcher.ts, 79, 23)) .concat(services.getSemanticDiagnostics(fileName)); >concat : { (...items: U[]): ts.Diagnostic[]; (...items: ts.Diagnostic[]): ts.Diagnostic[]; }, Symbol(Array.concat, Decl(lib.d.ts, 1025, 13), Decl(lib.d.ts, 1030, 46)) >services.getSemanticDiagnostics(fileName) : ts.Diagnostic[] ->services.getSemanticDiagnostics : (fileName: string) => ts.Diagnostic[], Symbol(ts.LanguageService.getSemanticDiagnostics, Decl(typescript.d.ts, 1339, 64)) +>services.getSemanticDiagnostics : (fileName: string) => ts.Diagnostic[], Symbol(ts.LanguageService.getSemanticDiagnostics, Decl(typescript.d.ts, 1335, 64)) >services : ts.LanguageService, Symbol(services, Decl(APISample_watcher.ts, 39, 9)) ->getSemanticDiagnostics : (fileName: string) => ts.Diagnostic[], Symbol(ts.LanguageService.getSemanticDiagnostics, Decl(typescript.d.ts, 1339, 64)) +>getSemanticDiagnostics : (fileName: string) => ts.Diagnostic[], Symbol(ts.LanguageService.getSemanticDiagnostics, Decl(typescript.d.ts, 1335, 64)) >fileName : string, Symbol(fileName, Decl(APISample_watcher.ts, 79, 23)) allDiagnostics.forEach(diagnostic => { @@ -333,9 +333,9 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); >message : string, Symbol(message, Decl(APISample_watcher.ts, 85, 15)) >ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n") : string ->ts.flattenDiagnosticMessageText : (messageText: string | ts.DiagnosticMessageChain, newLine: string) => string, Symbol(ts.flattenDiagnosticMessageText, Decl(typescript.d.ts, 1228, 67)) +>ts.flattenDiagnosticMessageText : (messageText: string | ts.DiagnosticMessageChain, newLine: string) => string, Symbol(ts.flattenDiagnosticMessageText, Decl(typescript.d.ts, 1224, 67)) >ts : typeof ts, Symbol(ts, Decl(APISample_watcher.ts, 12, 6)) ->flattenDiagnosticMessageText : (messageText: string | ts.DiagnosticMessageChain, newLine: string) => string, Symbol(ts.flattenDiagnosticMessageText, Decl(typescript.d.ts, 1228, 67)) +>flattenDiagnosticMessageText : (messageText: string | ts.DiagnosticMessageChain, newLine: string) => string, Symbol(ts.flattenDiagnosticMessageText, Decl(typescript.d.ts, 1224, 67)) >diagnostic.messageText : string | ts.DiagnosticMessageChain, Symbol(ts.Diagnostic.messageText, Decl(typescript.d.ts, 1065, 23)) >diagnostic : ts.Diagnostic, Symbol(diagnostic, Decl(APISample_watcher.ts, 84, 31)) >messageText : string | ts.DiagnosticMessageChain, Symbol(ts.Diagnostic.messageText, Decl(typescript.d.ts, 1065, 23)) @@ -350,11 +350,11 @@ function watch(rootFileNames: string[], options: ts.CompilerOptions) { >line : number, Symbol(line, Decl(APISample_watcher.ts, 87, 21)) >character : number, Symbol(character, Decl(APISample_watcher.ts, 87, 27)) >diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start) : ts.LineAndCharacter ->diagnostic.file.getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1290, 26)) +>diagnostic.file.getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1286, 26)) >diagnostic.file : ts.SourceFile, Symbol(ts.Diagnostic.file, Decl(typescript.d.ts, 1062, 26)) >diagnostic : ts.Diagnostic, Symbol(diagnostic, Decl(APISample_watcher.ts, 84, 31)) >file : ts.SourceFile, Symbol(ts.Diagnostic.file, Decl(typescript.d.ts, 1062, 26)) ->getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1290, 26)) +>getLineAndCharacterOfPosition : (pos: number) => ts.LineAndCharacter, Symbol(ts.SourceFile.getLineAndCharacterOfPosition, Decl(typescript.d.ts, 1286, 26)) >diagnostic.start : number, Symbol(ts.Diagnostic.start, Decl(typescript.d.ts, 1063, 25)) >diagnostic : ts.Diagnostic, Symbol(diagnostic, Decl(APISample_watcher.ts, 84, 31)) >start : number, Symbol(ts.Diagnostic.start, Decl(typescript.d.ts, 1063, 25))