Skip to content

Commit 9763dc8

Browse files
authored
Merge pull request microsoft#9295 from Microsoft/reuseTrees
Reuse trees
2 parents c5189dd + 25da965 commit 9763dc8

3 files changed

Lines changed: 95 additions & 79 deletions

File tree

src/compiler/parser.ts

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ namespace ts {
443443
if (result && result.jsDocComment) {
444444
// because the jsDocComment was parsed out of the source file, it might
445445
// not be covered by the fixupParentReferences.
446-
Parser.fixupParentReferences(result.jsDocComment);
446+
fixupParentReferences(result.jsDocComment);
447447
}
448448

449449
return result;
@@ -455,6 +455,39 @@ namespace ts {
455455
return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length);
456456
}
457457

458+
/* @internal */
459+
export function fixupParentReferences(rootNode: Node) {
460+
// normally parent references are set during binding. However, for clients that only need
461+
// a syntax tree, and no semantic features, then the binding process is an unnecessary
462+
// overhead. This functions allows us to set all the parents, without all the expense of
463+
// binding.
464+
465+
let parent: Node = rootNode;
466+
forEachChild(rootNode, visitNode);
467+
return;
468+
469+
function visitNode(n: Node): void {
470+
// walk down setting parents that differ from the parent we think it should be. This
471+
// allows us to quickly bail out of setting parents for subtrees during incremental
472+
// parsing
473+
if (n.parent !== parent) {
474+
n.parent = parent;
475+
476+
const saveParent = parent;
477+
parent = n;
478+
forEachChild(n, visitNode);
479+
if (n.jsDocComments) {
480+
for (const jsDocComment of n.jsDocComments) {
481+
jsDocComment.parent = n;
482+
parent = jsDocComment;
483+
forEachChild(jsDocComment, visitNode);
484+
}
485+
}
486+
parent = saveParent;
487+
}
488+
}
489+
}
490+
458491
// Implement the parser as a singleton module. We do this for perf reasons because creating
459492
// parser instances can actually be expensive enough to impact us on projects with many source
460493
// files.
@@ -658,38 +691,6 @@ namespace ts {
658691
return node;
659692
}
660693

661-
export function fixupParentReferences(rootNode: Node) {
662-
// normally parent references are set during binding. However, for clients that only need
663-
// a syntax tree, and no semantic features, then the binding process is an unnecessary
664-
// overhead. This functions allows us to set all the parents, without all the expense of
665-
// binding.
666-
667-
let parent: Node = rootNode;
668-
forEachChild(rootNode, visitNode);
669-
return;
670-
671-
function visitNode(n: Node): void {
672-
// walk down setting parents that differ from the parent we think it should be. This
673-
// allows us to quickly bail out of setting parents for subtrees during incremental
674-
// parsing
675-
if (n.parent !== parent) {
676-
n.parent = parent;
677-
678-
const saveParent = parent;
679-
parent = n;
680-
forEachChild(n, visitNode);
681-
if (n.jsDocComments) {
682-
for (const jsDocComment of n.jsDocComments) {
683-
jsDocComment.parent = n;
684-
parent = jsDocComment;
685-
forEachChild(jsDocComment, visitNode);
686-
}
687-
}
688-
parent = saveParent;
689-
}
690-
}
691-
}
692-
693694
function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind): SourceFile {
694695
// code from createNode is inlined here so createNode won't have to deal with special case of creating source files
695696
// this is quite rare comparing to other nodes and createNode should be as fast as possible

src/harness/harness.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ namespace Utils {
367367
// call this on both nodes to ensure all propagated flags have been set (and thus can be
368368
// compared).
369369
assert.equal(ts.containsParseError(node1), ts.containsParseError(node2));
370-
assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags");
370+
assert.equal(node1.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, node2.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, "node1.flags !== node2.flags");
371371

372372
ts.forEachChild(node1,
373373
child1 => {

src/services/services.ts

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,50 +1946,6 @@ namespace ts {
19461946
}
19471947
}
19481948

1949-
class SyntaxTreeCache {
1950-
// For our syntactic only features, we also keep a cache of the syntax tree for the
1951-
// currently edited file.
1952-
private currentFileName: string;
1953-
private currentFileVersion: string;
1954-
private currentFileScriptSnapshot: IScriptSnapshot;
1955-
private currentSourceFile: SourceFile;
1956-
1957-
constructor(private host: LanguageServiceHost) {
1958-
}
1959-
1960-
public getCurrentSourceFile(fileName: string): SourceFile {
1961-
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
1962-
if (!scriptSnapshot) {
1963-
// The host does not know about this file.
1964-
throw new Error("Could not find file: '" + fileName + "'.");
1965-
}
1966-
1967-
const scriptKind = getScriptKind(fileName, this.host);
1968-
const version = this.host.getScriptVersion(fileName);
1969-
let sourceFile: SourceFile;
1970-
1971-
if (this.currentFileName !== fileName) {
1972-
// This is a new file, just parse it
1973-
sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true, scriptKind);
1974-
}
1975-
else if (this.currentFileVersion !== version) {
1976-
// This is the same file, just a newer version. Incrementally parse the file.
1977-
const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot);
1978-
sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, editRange);
1979-
}
1980-
1981-
if (sourceFile) {
1982-
// All done, ensure state is up to date
1983-
this.currentFileVersion = version;
1984-
this.currentFileName = fileName;
1985-
this.currentFileScriptSnapshot = scriptSnapshot;
1986-
this.currentSourceFile = sourceFile;
1987-
}
1988-
1989-
return this.currentSourceFile;
1990-
}
1991-
}
1992-
19931949
function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) {
19941950
sourceFile.version = version;
19951951
sourceFile.scriptSnapshot = scriptSnapshot;
@@ -2983,7 +2939,7 @@ namespace ts {
29832939
export function createLanguageService(host: LanguageServiceHost,
29842940
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
29852941

2986-
const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
2942+
const syntaxTreeCache = createSyntaxTreeCache();
29872943
let ruleProvider: formatting.RulesProvider;
29882944
let program: Program;
29892945
let lastProjectVersion: string;
@@ -3218,6 +3174,64 @@ namespace ts {
32183174
}
32193175
}
32203176

3177+
function createSyntaxTreeCache() {
3178+
let currentFileName: string;
3179+
let currentFileVersion: string;
3180+
let currentFileScriptSnapshot: IScriptSnapshot;
3181+
let currentSourceFile: SourceFile;
3182+
let currentScriptKind: ScriptKind;
3183+
let currentCompilerOptions: CompilerOptions;
3184+
return {
3185+
getCurrentSourceFile: function (fileName: string) {
3186+
const scriptSnapshot = host.getScriptSnapshot(fileName);
3187+
if (!scriptSnapshot) {
3188+
// The host does not know about this file.
3189+
throw new Error("Could not find file: '" + fileName + "'.");
3190+
}
3191+
const version = host.getScriptVersion(fileName);
3192+
const scriptKind = ts.getScriptKind(fileName, host);
3193+
const compilerOptions = host.getCompilationSettings();
3194+
let sourceFile: SourceFile;
3195+
if (currentFileName !== fileName) {
3196+
// Release the current document
3197+
if (currentFileName) {
3198+
documentRegistry.releaseDocument(currentFileName, currentCompilerOptions);
3199+
}
3200+
// This is a new file, just parse it
3201+
sourceFile = documentRegistry.acquireDocument(fileName, compilerOptions, scriptSnapshot, version, scriptKind);
3202+
}
3203+
else if (currentFileVersion !== version) {
3204+
// This is the same file, just a newer version. Incrementally parse the file.
3205+
sourceFile = documentRegistry.updateDocument(fileName, compilerOptions, scriptSnapshot, version, scriptKind);
3206+
}
3207+
if (sourceFile) {
3208+
// All done, ensure state is up to date
3209+
currentFileVersion = version;
3210+
currentFileName = fileName;
3211+
currentScriptKind = scriptKind;
3212+
currentFileScriptSnapshot = scriptSnapshot;
3213+
currentSourceFile = sourceFile;
3214+
currentCompilerOptions = compilerOptions;
3215+
}
3216+
if (currentSourceFile && !currentSourceFile.locals) {
3217+
fixupParentReferences(currentSourceFile);
3218+
}
3219+
return currentSourceFile;
3220+
},
3221+
dispose: function () {
3222+
if (currentFileName) {
3223+
documentRegistry.releaseDocument(currentFileName, currentCompilerOptions);
3224+
currentFileVersion = undefined;
3225+
currentFileName = undefined;
3226+
currentScriptKind = undefined;
3227+
currentFileScriptSnapshot = undefined;
3228+
currentSourceFile = undefined;
3229+
currentCompilerOptions = undefined;
3230+
}
3231+
}
3232+
};
3233+
}
3234+
32213235
function getProgram(): Program {
32223236
synchronizeHostData();
32233237

@@ -3236,6 +3250,7 @@ namespace ts {
32363250
documentRegistry.releaseDocumentWithKey(file.path, key);
32373251
}
32383252
}
3253+
syntaxTreeCache.dispose();
32393254
}
32403255

32413256
/// Diagnostics

0 commit comments

Comments
 (0)