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.
This commit is contained in:
Cyrus Najmabadi
2014-12-10 11:45:33 -08:00
parent aea499e572
commit 5bd49fec1d
14 changed files with 202 additions and 155 deletions

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -16,7 +16,7 @@
///<reference path='references.ts' />
module ts.formatting {
export class TokenSpan extends TextSpan {
export class TokenSpan extends TextSpanObject {
constructor(public kind: SyntaxKind, start: number, length: number) {
super(start, length);
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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<Statement>;
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<string>;
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 = <SourceFileObject><any>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 ? (<Identifier>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()));
}
}
}

View File

@@ -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;
});
}

View File

@@ -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 {

View File

@@ -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);
}
}
}