From 5bd49fec1d6e67baf1c13fa348aa2b3bb42b42f8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Dec 2014 11:45:33 -0800 Subject: [PATCH] Initial entrypoint in SourceFile for the LS to call to peform incremental parsing. Right now the entrypoint just causes a full parse to happen. But the LS code is cleaned up to take advantage of it appropriately. --- src/compiler/parser.ts | 101 +++++++++------ src/compiler/types.ts | 34 ++++- src/harness/fourslash.ts | 6 +- src/harness/harnessLanguageService.ts | 10 +- src/services/breakpoints.ts | 2 +- src/services/formatting.ts | 2 +- src/services/formatting/tokenSpan.ts | 2 +- src/services/navigationBar.ts | 4 +- src/services/outliningElementsCollector.ts | 6 +- src/services/services.ts | 141 ++++++++++----------- src/services/shims.ts | 8 +- src/services/signatureHelp.ts | 4 +- src/services/text.ts | 28 ++-- tests/cases/unittests/incrementalParser.ts | 9 +- 14 files changed, 202 insertions(+), 155 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4f4b7bd96e0..e29fade5453 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1034,10 +1034,13 @@ module ts { export function createSourceFile(filename: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false): SourceFile { var parsingContext: ParsingContext; - var identifiers: Map = {}; + var identifiers: Map; var identifierCount = 0; var nodeCount = 0; var lineStarts: number[]; + var syntacticDiagnostics: Diagnostic[]; + var scanner: Scanner; + var token: SyntaxKind; // 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 @@ -1085,7 +1088,7 @@ module ts { // Note: it should not be necessary to save/restore these flags during speculative/lookahead // parsing. These context flags are naturally stored and restored through normal recursive // descent parsing and unwinding. - var contextFlags: ParserContextFlags = 0; + var contextFlags: ParserContextFlags; // Whether or not we've had a parse error since creating the last AST node. If we have // encountered an error, it will be stored on the next AST node we create. Parse errors @@ -1114,48 +1117,69 @@ module ts { // // Note: any errors at the end of the file that do not precede a regular node, should get // attached to the EOF token. - var parseErrorBeforeNextFinishedNode = false; + var parseErrorBeforeNextFinishedNode: boolean; - var sourceFile = createNode(SyntaxKind.SourceFile, 0); - if (fileExtensionIs(filename, ".d.ts")) { - sourceFile.flags = NodeFlags.DeclarationFile; - } - sourceFile.end = sourceText.length; - sourceFile.filename = normalizePath(filename); - sourceFile.text = sourceText; + var sourceFile: SourceFile; - sourceFile.getLineAndCharacterFromPosition = getLineAndCharacterFromSourcePosition; - sourceFile.getPositionFromLineAndCharacter = getPositionFromSourceLineAndCharacter; - sourceFile.getLineStarts = getLineStarts; - sourceFile.getSyntacticDiagnostics = getSyntacticDiagnostics; + return parseSourceFile(sourceText, /*textChangeRange:*/ undefined, setParentNodes); - sourceFile.referenceDiagnostics = []; - sourceFile.parseDiagnostics = []; - sourceFile.grammarDiagnostics = []; - sourceFile.semanticDiagnostics = []; + function parseSourceFile(text: string, textChangeRange: TextChangeRange, setParentNodes: boolean): SourceFile { + // Set our initial state before parsing. + sourceText = text; + parsingContext = 0; + identifiers = {}; + lineStarts = undefined; + syntacticDiagnostics = undefined; + contextFlags = 0; + parseErrorBeforeNextFinishedNode = false; - processReferenceComments(); + sourceFile = createNode(SyntaxKind.SourceFile, 0); + sourceFile.referenceDiagnostics = []; + sourceFile.parseDiagnostics = []; + sourceFile.grammarDiagnostics = []; + sourceFile.semanticDiagnostics = []; - // Create and prime the scanner before parsing the source elements. - var scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError); - var token = nextToken(); + // Create and prime the scanner before parsing the source elements. + scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError); + token = nextToken(); - sourceFile.statements = parseList(ParsingContext.SourceElements, /*checkForStrictMode*/ true, parseSourceElement); - Debug.assert(token === SyntaxKind.EndOfFileToken); - sourceFile.endOfFileToken = parseTokenNode(); + sourceFile.flags = fileExtensionIs(filename, ".d.ts") ? NodeFlags.DeclarationFile : 0; + sourceFile.end = sourceText.length; + sourceFile.filename = normalizePath(filename); + sourceFile.text = sourceText; - sourceFile.externalModuleIndicator = getExternalModuleIndicator(); + sourceFile.getLineAndCharacterFromPosition = getLineAndCharacterFromSourcePosition; + sourceFile.getPositionFromLineAndCharacter = getPositionFromSourceLineAndCharacter; + sourceFile.getLineStarts = getLineStarts; + sourceFile.getSyntacticDiagnostics = getSyntacticDiagnostics; + sourceFile.update = update; - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.languageVersion = languageVersion; - sourceFile.identifiers = identifiers; + processReferenceComments(sourceFile); - if (setParentNodes) { - fixupParentReferences(sourceFile); + sourceFile.statements = parseList(ParsingContext.SourceElements, /*checkForStrictMode*/ true, parseSourceElement); + Debug.assert(token === SyntaxKind.EndOfFileToken); + sourceFile.endOfFileToken = parseTokenNode(); + + setExternalModuleIndicator(sourceFile); + + sourceFile.nodeCount = nodeCount; + sourceFile.identifierCount = identifierCount; + sourceFile.languageVersion = languageVersion; + sourceFile.identifiers = identifiers; + + if (setParentNodes) { + fixupParentReferences(sourceFile); + } + + return sourceFile; + } + + function update(newText: string, textChangeRange: TextChangeRange) { + // Don't pass along the text change range for now. We'll pass it along once incremental + // parsing is enabled. + return parseSourceFile(newText, /*textChangeRange:*/ undefined, /*setNodeParents*/ true); } - return sourceFile; function setContextFlag(val: Boolean, flag: ParserContextFlags) { if (val) { @@ -4467,7 +4491,7 @@ module ts { : parseStatement(); } - function processReferenceComments(): void { + function processReferenceComments(sourceFile: SourceFile): void { var triviaScanner = createScanner(languageVersion, /*skipTrivia*/false, sourceText); var referencedFiles: FileReference[] = []; var amdDependencies: string[] = []; @@ -4523,16 +4547,15 @@ module ts { sourceFile.amdModuleName = amdModuleName; } - function getExternalModuleIndicator() { - return forEach(sourceFile.statements, node => + function setExternalModuleIndicator(sourceFile: SourceFile) { + sourceFile.externalModuleIndicator = forEach(sourceFile.statements, node => node.flags & NodeFlags.Export || node.kind === SyntaxKind.ImportDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference || node.kind === SyntaxKind.ExportAssignment - ? node - : undefined); + ? node + : undefined); } - var syntacticDiagnostics: Diagnostic[]; function getSyntacticDiagnostics() { if (syntacticDiagnostics === undefined) { if (sourceFile.parseDiagnostics.length > 0) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 37a7775491e..07fc909a9aa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -866,9 +866,20 @@ module ts { filename: string; text: string; + getLineAndCharacterFromPosition(position: number): LineAndCharacter; getPositionFromLineAndCharacter(line: number, character: number): number; getLineStarts(): number[]; + + // Updates this source file to represent the 'newText' passed in. The 'textChangeRange' + // parameter indicates what changed between the 'text' that this SourceFile has and the + // 'newText'. + // + // Note: this function mutates nodes from this SourceFile. That means any existing nodes + // from this SourceFile that are being held onto may change as a result (including + // becoming detached from any SourceFile). + update(newText: string, textChangeRange: TextChangeRange): SourceFile; + amdDependencies: string[]; amdModuleName: string; referencedFiles: FileReference[]; @@ -1435,7 +1446,6 @@ module ts { character: number; } - export const enum ScriptTarget { ES3, ES5, @@ -1607,4 +1617,26 @@ module ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; } + + export interface TextChangeRange { + span(): TextSpan; + newLength(): number; + newSpan(): TextSpan; + isUnchanged(): boolean; + } + + export interface TextSpan { + start(): number; + length(): number; + end(): number; + isEmpty(): boolean; + containsPosition(position: number): boolean; + containsTextSpan(span: TextSpan): boolean; + overlapsWith(span: TextSpan): boolean; + overlap(span: TextSpan): TextSpan; + intersectsWithTextSpan(span: TextSpan): boolean; + intersectsWith(start: number, length: number): boolean; + intersectsWithPosition(position: number): boolean; + intersection(span: TextSpan): TextSpan; + } } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 0114d213398..c88364de8b8 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1755,14 +1755,14 @@ module FourSlash { public verifySemanticClassifications(expected: { classificationType: string; text: string }[]) { var actual = this.languageService.getSemanticClassifications(this.activeFile.fileName, - new ts.TextSpan(0, this.activeFile.content.length)); + new ts.TextSpanObject(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); } public verifySyntacticClassifications(expected: { classificationType: string; text: string }[]) { var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName, - new ts.TextSpan(0, this.activeFile.content.length)); + new ts.TextSpanObject(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); } @@ -1796,7 +1796,7 @@ module FourSlash { for (var i = 0; i < spans.length; i++) { var expectedSpan = spans[i]; var actualComment = actual[i]; - var actualCommentSpan = new ts.TextSpan(actualComment.position, actualComment.message.length); + var actualCommentSpan = new ts.TextSpanObject(actualComment.position, actualComment.message.length); if (expectedSpan.start !== actualCommentSpan.start() || expectedSpan.end !== actualCommentSpan.end()) { this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start() + ',' + actualCommentSpan.end() + ')'); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index fe635349ec8..c4a75772529 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -32,8 +32,8 @@ module Harness.LanguageService { // Store edit range + new length of script this.editRanges.push({ length: this.content.length, - textChangeRange: new ts.TextChangeRange( - ts.TextSpan.fromBounds(minChar, limChar), newText.length) + textChangeRange: new ts.TextChangeRangeObject( + ts.TextSpanObject.fromBounds(minChar, limChar), newText.length) }); // Update version # @@ -43,14 +43,14 @@ module Harness.LanguageService { public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { if (startVersion === endVersion) { // No edits! - return ts.TextChangeRange.unchanged; + return ts.TextChangeRangeObject.unchanged; } var initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); var lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); var entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); - return ts.TextChangeRange.collapseChangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); + return ts.TextChangeRangeObject.collapseChangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); } } @@ -126,7 +126,7 @@ module Harness.LanguageService { isOpen: boolean, textChangeRange: ts.TextChangeRange ): ts.SourceFile { - return document.update(scriptSnapshot, version, isOpen, textChangeRange); + return ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, isOpen, textChangeRange); } public releaseDocument(fileName: string, compilationSettings: ts.CompilerOptions): void { diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index 3a2cc2ad7e7..4e4ad585503 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -38,7 +38,7 @@ module ts.BreakpointResolver { return spanInNode(tokenAtLocation); function textSpan(startNode: Node, endNode?: Node) { - return TextSpan.fromBounds(startNode.getStart(), (endNode || startNode).getEnd()); + return TextSpanObject.fromBounds(startNode.getStart(), (endNode || startNode).getEnd()); } function spanInNodeIfStartsOnSameLine(node: Node, otherwiseOnNode?: Node): TextSpan { diff --git a/src/services/formatting.ts b/src/services/formatting.ts index 0c2dd4c51dc..0cba03664d4 100644 --- a/src/services/formatting.ts +++ b/src/services/formatting.ts @@ -868,7 +868,7 @@ module ts.formatting { } function newTextChange(start: number, len: number, newText: string): TextChange { - return { span: new TextSpan(start, len), newText } + return { span: new TextSpanObject(start, len), newText } } function recordDelete(start: number, len: number) { diff --git a/src/services/formatting/tokenSpan.ts b/src/services/formatting/tokenSpan.ts index 1d8173a8227..dd328931919 100644 --- a/src/services/formatting/tokenSpan.ts +++ b/src/services/formatting/tokenSpan.ts @@ -16,7 +16,7 @@ /// module ts.formatting { - export class TokenSpan extends TextSpan { + export class TokenSpan extends TextSpanObject { constructor(public kind: SyntaxKind, start: number, length: number) { super(start, length); } diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 29b2db72fc5..cfa3c98beb7 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -462,8 +462,8 @@ module ts.NavigationBar { function getNodeSpan(node: Node) { return node.kind === SyntaxKind.SourceFile - ? TextSpan.fromBounds(node.getFullStart(), node.getEnd()) - : TextSpan.fromBounds(node.getStart(), node.getEnd()); + ? TextSpanObject.fromBounds(node.getFullStart(), node.getEnd()) + : TextSpanObject.fromBounds(node.getStart(), node.getEnd()); } function getTextOfNode(node: Node): string { diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 83eef2f4378..9cbd2a69a38 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -38,8 +38,8 @@ module ts { function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean) { if (hintSpanNode && startElement && endElement) { var span: OutliningSpan = { - textSpan: TextSpan.fromBounds(startElement.pos, endElement.end), - hintSpan: TextSpan.fromBounds(hintSpanNode.getStart(), hintSpanNode.end), + textSpan: TextSpanObject.fromBounds(startElement.pos, endElement.end), + hintSpan: TextSpanObject.fromBounds(hintSpanNode.getStart(), hintSpanNode.end), bannerText: collapseText, autoCollapse: autoCollapse }; @@ -88,7 +88,7 @@ module ts { else { // Block was a standalone block. In this case we want to only collapse // the span of the block, independent of any parent span. - var span = TextSpan.fromBounds(n.getStart(), n.end); + var span = TextSpanObject.fromBounds(n.getStart(), n.end); elements.push({ textSpan: span, hintSpan: span, diff --git a/src/services/services.ts b/src/services/services.ts index 07eafbcbeaf..856e4cb2c9a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -61,10 +61,9 @@ module ts { export interface SourceFile { isOpen: boolean; version: string; + scriptSnapshot: IScriptSnapshot; - getScriptSnapshot(): IScriptSnapshot; getNamedDeclarations(): Declaration[]; - update(scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile; } /** @@ -724,6 +723,7 @@ module ts { public _declarationBrand: any; public filename: string; public text: string; + public scriptSnapshot: IScriptSnapshot; public statements: NodeArray; public endOfFileToken: Node; @@ -734,6 +734,7 @@ module ts { public getPositionFromLineAndCharacter: (line: number, character: number) => number; public getLineStarts: () => number[]; public getSyntacticDiagnostics: () => Diagnostic[]; + public update: (newText: string, textChangeRange: TextChangeRange) => SourceFile; public amdDependencies: string[]; public amdModuleName: string; @@ -754,13 +755,8 @@ module ts { public languageVersion: ScriptTarget; public identifiers: Map; - private scriptSnapshot: IScriptSnapshot; private namedDeclarations: Declaration[]; - public getScriptSnapshot(): IScriptSnapshot { - return this.scriptSnapshot; - } - public getNamedDeclarations() { if (!this.namedDeclarations) { var sourceFile = this; @@ -846,35 +842,6 @@ module ts { return this.namedDeclarations; } - - public update(scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile { - if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { - var oldText = this.scriptSnapshot; - var newText = scriptSnapshot; - - Debug.assert((oldText.getLength() - textChangeRange.span().length() + textChangeRange.newLength()) === newText.getLength()); - - if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - var oldTextPrefix = oldText.getText(0, textChangeRange.span().start()); - var newTextPrefix = newText.getText(0, textChangeRange.span().start()); - Debug.assert(oldTextPrefix === newTextPrefix); - - var oldTextSuffix = oldText.getText(textChangeRange.span().end(), oldText.getLength()); - var newTextSuffix = newText.getText(textChangeRange.newSpan().end(), newText.getLength()); - Debug.assert(oldTextSuffix === newTextSuffix); - } - } - - return SourceFileObject.createSourceFileObject(this.filename, scriptSnapshot, this.languageVersion, version, isOpen); - } - - public static createSourceFileObject(filename: string, scriptSnapshot: IScriptSnapshot, languageVersion: ScriptTarget, version: string, isOpen: boolean) { - var newSourceFile = createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), languageVersion, /*setParentNodes:*/ true); - newSourceFile.version = version; - newSourceFile.isOpen = isOpen; - newSourceFile.scriptSnapshot = scriptSnapshot; - return newSourceFile; - } } export interface Logger { @@ -1646,7 +1613,7 @@ module ts { public getChangeRange(filename: string, lastKnownVersion: string, oldScriptSnapshot: IScriptSnapshot): TextChangeRange { var currentVersion = this.getVersion(filename); if (lastKnownVersion === currentVersion) { - return TextChangeRange.unchanged; // "No changes" + return TextChangeRangeObject.unchanged; // "No changes" } var scriptSnapshot = this.getScriptSnapshot(filename); @@ -1679,25 +1646,17 @@ module ts { var scriptSnapshot = this.hostCache.getScriptSnapshot(filename); var start = new Date().getTime(); - sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true); + sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, ScriptTarget.Latest, version, /*isOpen*/ true, /*setNodeParents;*/ true); this.host.log("SyntaxTreeCache.Initialize: createSourceFile: " + (new Date().getTime() - start)); - - var start = new Date().getTime(); - this.host.log("SyntaxTreeCache.Initialize: fixupParentRefs : " + (new Date().getTime() - start)); } else if (this.currentFileVersion !== version) { var scriptSnapshot = this.hostCache.getScriptSnapshot(filename); - var editRange = this.hostCache.getChangeRange(filename, this.currentFileVersion, this.currentSourceFile.getScriptSnapshot()); + var editRange = this.hostCache.getChangeRange(filename, this.currentFileVersion, this.currentSourceFile.scriptSnapshot); var start = new Date().getTime(); - sourceFile = !editRange - ? createLanguageServiceSourceFile(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true) - : this.currentSourceFile.update(scriptSnapshot, version, /*isOpen*/ true, editRange); + sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, /*isOpen*/ true, editRange); this.host.log("SyntaxTreeCache.Initialize: updateSourceFile: " + (new Date().getTime() - start)); - - var start = new Date().getTime(); - this.host.log("SyntaxTreeCache.Initialize: fixupParentRefs : " + (new Date().getTime() - start)); } if (sourceFile) { @@ -1714,12 +1673,52 @@ module ts { } public getCurrentScriptSnapshot(filename: string): IScriptSnapshot { - return this.getCurrentSourceFile(filename).getScriptSnapshot(); + return this.getCurrentSourceFile(filename).scriptSnapshot; } } - export function createLanguageServiceSourceFile(filename: string, scriptSnapshot: IScriptSnapshot, settings: CompilerOptions, version: string, isOpen: boolean): SourceFile { - return SourceFileObject.createSourceFileObject(filename, scriptSnapshot, settings.target, version, isOpen); + function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean) { + sourceFile.version = version; + sourceFile.isOpen = isOpen; + sourceFile.scriptSnapshot = scriptSnapshot; + } + + export function createLanguageServiceSourceFile(filename: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, isOpen: boolean, setNodeParents: boolean): SourceFile { + var sourceFile = createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), scriptTarget, setNodeParents); + setSourceFileFields(sourceFile, scriptSnapshot, version, isOpen); + return sourceFile; + } + + export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile { + if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { + var oldText = sourceFile.scriptSnapshot; + var newText = scriptSnapshot; + + Debug.assert((oldText.getLength() - textChangeRange.span().length() + textChangeRange.newLength()) === newText.getLength()); + + if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { + var oldTextPrefix = oldText.getText(0, textChangeRange.span().start()); + var newTextPrefix = newText.getText(0, textChangeRange.span().start()); + Debug.assert(oldTextPrefix === newTextPrefix); + + var oldTextSuffix = oldText.getText(textChangeRange.span().end(), oldText.getLength()); + var newTextSuffix = newText.getText(textChangeRange.newSpan().end(), newText.getLength()); + Debug.assert(oldTextSuffix === newTextSuffix); + } + } + + // If we were given a text change range, and our version or open-ness changed, then + // incrementally parse this file. + if (textChangeRange) { + if (version !== sourceFile.version || isOpen != sourceFile.isOpen) { + var newSourceFile = sourceFile.update(scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange); + setSourceFileFields(newSourceFile, scriptSnapshot, version, isOpen); + return newSourceFile; + } + } + + // Otherwise, just create a new source file. + return createLanguageServiceSourceFile(sourceFile.filename, scriptSnapshot, sourceFile.languageVersion, version, isOpen, /*setNodeParents:*/ true); } export function createDocumentRegistry(): DocumentRegistry { @@ -1769,7 +1768,7 @@ module ts { var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true); var entry = lookUp(bucket, filename); if (!entry) { - var sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, compilationSettings, version, isOpen); + var sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, compilationSettings.target, version, isOpen, /*setNodeParents:*/ false); bucket[filename] = entry = { sourceFile: sourceFile, @@ -1797,11 +1796,7 @@ module ts { var entry = lookUp(bucket, filename); Debug.assert(entry !== undefined); - if (entry.sourceFile.isOpen === isOpen && entry.sourceFile.version === version) { - return entry.sourceFile; - } - - entry.sourceFile = entry.sourceFile.update(scriptSnapshot, version, isOpen, textChangeRange); + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, isOpen, textChangeRange); return entry.sourceFile; } @@ -2225,7 +2220,7 @@ module ts { // new text buffer). var textChangeRange: TextChangeRange = null; if (sourceFile.isOpen && isOpen) { - textChangeRange = hostCache.getChangeRange(filename, sourceFile.version, sourceFile.getScriptSnapshot()); + textChangeRange = hostCache.getChangeRange(filename, sourceFile.version, sourceFile.scriptSnapshot); } sourceFile = documentRegistry.updateDocument(sourceFile, filename, compilationSettings, scriptSnapshot, version, isOpen, textChangeRange); @@ -3223,7 +3218,7 @@ module ts { return { kind: ScriptElementKind.unknown, kindModifiers: ScriptElementKindModifier.none, - textSpan: new TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpanObject(node.getStart(), node.getWidth()), displayParts: typeToDisplayParts(typeInfoResolver, type, getContainerNode(node)), documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined }; @@ -3237,7 +3232,7 @@ module ts { return { kind: displayPartsDocumentationsAndKind.symbolKind, kindModifiers: getSymbolModifiers(symbol), - textSpan: new TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpanObject(node.getStart(), node.getWidth()), displayParts: displayPartsDocumentationsAndKind.displayParts, documentation: displayPartsDocumentationsAndKind.documentation }; @@ -3248,7 +3243,7 @@ module ts { function getDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo { return { fileName: node.getSourceFile().filename, - textSpan: TextSpan.fromBounds(node.getStart(), node.getEnd()), + textSpan: TextSpanObject.fromBounds(node.getStart(), node.getEnd()), kind: symbolKind, name: symbolName, containerKind: undefined, @@ -3325,7 +3320,7 @@ module ts { if (referenceFile) { return [{ fileName: referenceFile.filename, - textSpan: TextSpan.fromBounds(0, 0), + textSpan: TextSpanObject.fromBounds(0, 0), kind: ScriptElementKind.scriptElement, name: comment.filename, containerName: undefined, @@ -3516,7 +3511,7 @@ module ts { if (shouldHighlightNextKeyword) { result.push({ fileName: filename, - textSpan: TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), + textSpan: TextSpanObject.fromBounds(elseKeyword.getStart(), ifKeyword.end), isWriteAccess: false }); i++; // skip the next keyword @@ -4208,7 +4203,7 @@ module ts { (findInComments && isInComment(position))) { result.push({ fileName: sourceFile.filename, - textSpan: new TextSpan(position, searchText.length), + textSpan: new TextSpanObject(position, searchText.length), isWriteAccess: false }); } @@ -4591,7 +4586,7 @@ module ts { return { fileName: node.getSourceFile().filename, - textSpan: TextSpan.fromBounds(start, end), + textSpan: TextSpanObject.fromBounds(start, end), isWriteAccess: isWriteAccess(node) }; } @@ -4647,7 +4642,7 @@ module ts { kindModifiers: getNodeModifiers(declaration), matchKind: MatchKind[matchKind], fileName: filename, - textSpan: TextSpan.fromBounds(declaration.getStart(), declaration.getEnd()), + textSpan: TextSpanObject.fromBounds(declaration.getStart(), declaration.getEnd()), // TODO(jfreeman): What should be the containerName when the container has a computed name? containerName: container && container.name ? (container.name).text : "", containerKind: container && container.name ? getNodeKind(container) : "" @@ -4924,7 +4919,7 @@ module ts { } } - return TextSpan.fromBounds(nodeForStartPos.getStart(), node.getEnd()); + return TextSpanObject.fromBounds(nodeForStartPos.getStart(), node.getEnd()); } function getBreakpointStatementAtPosition(filename: string, position: number) { @@ -5001,7 +4996,7 @@ module ts { var type = classifySymbol(symbol, getMeaningFromLocation(node)); if (type) { result.push({ - textSpan: new TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpanObject(node.getStart(), node.getWidth()), classificationType: type }); } @@ -5027,7 +5022,7 @@ module ts { var width = comment.end - comment.pos; if (span.intersectsWith(comment.pos, width)) { result.push({ - textSpan: new TextSpan(comment.pos, width), + textSpan: new TextSpanObject(comment.pos, width), classificationType: ClassificationTypeNames.comment }); } @@ -5040,7 +5035,7 @@ module ts { var type = classifyTokenType(token); if (type) { result.push({ - textSpan: new TextSpan(token.getStart(), token.getWidth()), + textSpan: new TextSpanObject(token.getStart(), token.getWidth()), classificationType: type }); } @@ -5168,8 +5163,8 @@ module ts { var current = childNodes[i]; if (current.kind === matchKind) { - var range1 = new TextSpan(token.getStart(sourceFile), token.getWidth(sourceFile)); - var range2 = new TextSpan(current.getStart(sourceFile), current.getWidth(sourceFile)); + var range1 = new TextSpanObject(token.getStart(sourceFile), token.getWidth(sourceFile)); + var range2 = new TextSpanObject(current.getStart(sourceFile), current.getWidth(sourceFile)); // We want to order the braces when we return the result. if (range1.start() < range2.start()) { @@ -5420,7 +5415,7 @@ module ts { if (kind) { return getRenameInfo(symbol.name, typeInfoResolver.getFullyQualifiedName(symbol), kind, getSymbolModifiers(symbol), - new TextSpan(node.getStart(), node.getWidth())); + new TextSpanObject(node.getStart(), node.getWidth())); } } } diff --git a/src/services/shims.ts b/src/services/shims.ts index 5556743830f..1bafbafa005 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -328,8 +328,8 @@ module ts { } var decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded); - return new TextChangeRange( - new TextSpan(decoded.span.start, decoded.span.length), decoded.newLength); + return new TextChangeRangeObject( + new TextSpanObject(decoded.span.start, decoded.span.length), decoded.newLength); } } @@ -510,7 +510,7 @@ module ts { return this.forwardJSONCall( "getSyntacticClassifications('" + fileName + "', " + start + ", " + length + ")", () => { - var classifications = this.languageService.getSyntacticClassifications(fileName, new TextSpan(start, length)); + var classifications = this.languageService.getSyntacticClassifications(fileName, new TextSpanObject(start, length)); return classifications; }); } @@ -519,7 +519,7 @@ module ts { return this.forwardJSONCall( "getSemanticClassifications('" + fileName + "', " + start + ", " + length + ")", () => { - var classifications = this.languageService.getSemanticClassifications(fileName, new TextSpan(start, length)); + var classifications = this.languageService.getSemanticClassifications(fileName, new TextSpanObject(start, length)); return classifications; }); } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 408fe60a6cd..9291bd0d685 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -367,7 +367,7 @@ module ts.SignatureHelp { // but not including parentheses) var applicableSpanStart = argumentsList.getFullStart(); var applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); - return new TextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + return new TextSpanObject(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression): TextSpan { @@ -391,7 +391,7 @@ module ts.SignatureHelp { } } - return new TextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + return new TextSpanObject(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } function getContainingArgumentInfo(node: Node): ArgumentListInfo { diff --git a/src/services/text.ts b/src/services/text.ts index fd768785dea..85465ffdf46 100644 --- a/src/services/text.ts +++ b/src/services/text.ts @@ -1,5 +1,5 @@ module ts { - export class TextSpan { + export class TextSpanObject { private _start: number; private _length: number; @@ -49,7 +49,7 @@ module ts { * @param span The span to check. */ public containsTextSpan(span: TextSpan): boolean { - return span._start >= this._start && span.end() <= this.end(); + return span.start() >= this._start && span.end() <= this.end(); } /** @@ -59,7 +59,7 @@ module ts { * @param span The span to check. */ public overlapsWith(span: TextSpan): boolean { - var overlapStart = Math.max(this._start, span._start); + var overlapStart = Math.max(this._start, span.start()); var overlapEnd = Math.min(this.end(), span.end()); return overlapStart < overlapEnd; @@ -70,11 +70,11 @@ module ts { * @param span The span to check. */ public overlap(span: TextSpan): TextSpan { - var overlapStart = Math.max(this._start, span._start); + var overlapStart = Math.max(this._start, span.start()); var overlapEnd = Math.min(this.end(), span.end()); if (overlapStart < overlapEnd) { - return TextSpan.fromBounds(overlapStart, overlapEnd); + return TextSpanObject.fromBounds(overlapStart, overlapEnd); } return undefined; @@ -87,7 +87,7 @@ module ts { * @param The span to check. */ public intersectsWithTextSpan(span: TextSpan): boolean { - return span._start <= this.end() && span.end() >= this._start; + return span.start() <= this.end() && span.end() >= this._start; } public intersectsWith(start: number, length: number): boolean { @@ -110,11 +110,11 @@ module ts { * @param span The span to check. */ public intersection(span: TextSpan): TextSpan { - var intersectStart = Math.max(this._start, span._start); + var intersectStart = Math.max(this._start, span.start()); var intersectEnd = Math.min(this.end(), span.end()); if (intersectStart <= intersectEnd) { - return TextSpan.fromBounds(intersectStart, intersectEnd); + return TextSpanObject.fromBounds(intersectStart, intersectEnd); } return undefined; @@ -127,12 +127,12 @@ module ts { public static fromBounds(start: number, end: number): TextSpan { Debug.assert(start >= 0); Debug.assert(end - start >= 0); - return new TextSpan(start, end - start); + return new TextSpanObject(start, end - start); } } - export class TextChangeRange { - public static unchanged = new TextChangeRange(new TextSpan(0, 0), 0); + export class TextChangeRangeObject implements TextChangeRange { + public static unchanged = new TextChangeRangeObject(new TextSpanObject(0, 0), 0); private _span: TextSpan; private _newLength: number; @@ -162,7 +162,7 @@ module ts { } public newSpan(): TextSpan { - return new TextSpan(this.span().start(), this.newLength()); + return new TextSpanObject(this.span().start(), this.newLength()); } public isUnchanged(): boolean { @@ -179,7 +179,7 @@ module ts { */ public static collapseChangesAcrossMultipleVersions(changes: TextChangeRange[]): TextChangeRange { if (changes.length === 0) { - return TextChangeRange.unchanged; + return TextChangeRangeObject.unchanged; } if (changes.length === 1) { @@ -290,7 +290,7 @@ module ts { newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)); } - return new TextChangeRange(TextSpan.fromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); + return new TextChangeRangeObject(TextSpanObject.fromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); } } } \ No newline at end of file diff --git a/tests/cases/unittests/incrementalParser.ts b/tests/cases/unittests/incrementalParser.ts index f1e59809400..9bd9df5deca 100644 --- a/tests/cases/unittests/incrementalParser.ts +++ b/tests/cases/unittests/incrementalParser.ts @@ -6,7 +6,7 @@ module ts { var contents = text.getText(0, text.getLength()); var newContents = contents.substr(0, start) + newText + contents.substring(start + length); - return { text: ScriptSnapshot.fromString(newContents), textChangeRange: new TextChangeRange(new TextSpan(start, length), newText.length) } + return { text: ScriptSnapshot.fromString(newContents), textChangeRange: new TextChangeRangeObject(new TextSpanObject(start, length), newText.length) } } function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { @@ -18,10 +18,7 @@ module ts { } function createTree(text: IScriptSnapshot, version: string) { - var options: CompilerOptions = {}; - options.target = ScriptTarget.ES5; - - return createLanguageServiceSourceFile(/*fileName:*/ "", text, options, version, /*isOpen:*/ true) + return createLanguageServiceSourceFile(/*fileName:*/ "", text, ScriptTarget.Latest, version, /*isOpen:*/ true, /*setNodeParents:*/ true) } // NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new @@ -38,7 +35,7 @@ module ts { Utils.assertInvariants(newTree, /*parent:*/ undefined); // Create a tree for the new text, in an incremental fashion. - var incrementalNewTree = oldTree.update(newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange); + var incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange); Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); // We should get the same tree when doign a full or incremental parse.