Skip to content

Commit a45ac24

Browse files
committed
Use document registry to get sourceFiles in SyntaxTreeCache to share trees between semantic and syntaxtic oprations
1 parent 04d617d commit a45ac24

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
@@ -444,7 +444,7 @@ namespace ts {
444444
if (result && result.jsDocComment) {
445445
// because the jsDocComment was parsed out of the source file, it might
446446
// not be covered by the fixupParentReferences.
447-
Parser.fixupParentReferences(result.jsDocComment);
447+
fixupParentReferences(result.jsDocComment);
448448
}
449449

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

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

662-
export function fixupParentReferences(rootNode: Node) {
663-
// normally parent references are set during binding. However, for clients that only need
664-
// a syntax tree, and no semantic features, then the binding process is an unnecessary
665-
// overhead. This functions allows us to set all the parents, without all the expense of
666-
// binding.
667-
668-
let parent: Node = rootNode;
669-
forEachChild(rootNode, visitNode);
670-
return;
671-
672-
function visitNode(n: Node): void {
673-
// walk down setting parents that differ from the parent we think it should be. This
674-
// allows us to quickly bail out of setting parents for subtrees during incremental
675-
// parsing
676-
if (n.parent !== parent) {
677-
n.parent = parent;
678-
679-
const saveParent = parent;
680-
parent = n;
681-
forEachChild(n, visitNode);
682-
if (n.jsDocComments) {
683-
for (const jsDocComment of n.jsDocComments) {
684-
jsDocComment.parent = n;
685-
parent = jsDocComment;
686-
forEachChild(jsDocComment, visitNode);
687-
}
688-
}
689-
parent = saveParent;
690-
}
691-
}
692-
}
693-
694695
function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind): SourceFile {
695696
// code from createNode is inlined here so createNode won't have to deal with special case of creating source files
696697
// 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
@@ -366,7 +366,7 @@ namespace Utils {
366366
// call this on both nodes to ensure all propagated flags have been set (and thus can be
367367
// compared).
368368
assert.equal(ts.containsParseError(node1), ts.containsParseError(node2));
369-
assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags");
369+
assert.equal(node1.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, node2.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, "node1.flags !== node2.flags");
370370

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

src/services/services.ts

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,50 +1894,6 @@ namespace ts {
18941894
}
18951895
}
18961896

1897-
class SyntaxTreeCache {
1898-
// For our syntactic only features, we also keep a cache of the syntax tree for the
1899-
// currently edited file.
1900-
private currentFileName: string;
1901-
private currentFileVersion: string;
1902-
private currentFileScriptSnapshot: IScriptSnapshot;
1903-
private currentSourceFile: SourceFile;
1904-
1905-
constructor(private host: LanguageServiceHost) {
1906-
}
1907-
1908-
public getCurrentSourceFile(fileName: string): SourceFile {
1909-
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
1910-
if (!scriptSnapshot) {
1911-
// The host does not know about this file.
1912-
throw new Error("Could not find file: '" + fileName + "'.");
1913-
}
1914-
1915-
const scriptKind = getScriptKind(fileName, this.host);
1916-
const version = this.host.getScriptVersion(fileName);
1917-
let sourceFile: SourceFile;
1918-
1919-
if (this.currentFileName !== fileName) {
1920-
// This is a new file, just parse it
1921-
sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true, scriptKind);
1922-
}
1923-
else if (this.currentFileVersion !== version) {
1924-
// This is the same file, just a newer version. Incrementally parse the file.
1925-
const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot);
1926-
sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, editRange);
1927-
}
1928-
1929-
if (sourceFile) {
1930-
// All done, ensure state is up to date
1931-
this.currentFileVersion = version;
1932-
this.currentFileName = fileName;
1933-
this.currentFileScriptSnapshot = scriptSnapshot;
1934-
this.currentSourceFile = sourceFile;
1935-
}
1936-
1937-
return this.currentSourceFile;
1938-
}
1939-
}
1940-
19411897
function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) {
19421898
sourceFile.version = version;
19431899
sourceFile.scriptSnapshot = scriptSnapshot;
@@ -2919,7 +2875,7 @@ namespace ts {
29192875
export function createLanguageService(host: LanguageServiceHost,
29202876
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
29212877

2922-
const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
2878+
const syntaxTreeCache = createSyntaxTreeCache();
29232879
let ruleProvider: formatting.RulesProvider;
29242880
let program: Program;
29252881
let lastProjectVersion: string;
@@ -3153,6 +3109,64 @@ namespace ts {
31533109
}
31543110
}
31553111

3112+
function createSyntaxTreeCache() {
3113+
let currentFileName: string;
3114+
let currentFileVersion: string;
3115+
let currentFileScriptSnapshot: IScriptSnapshot;
3116+
let currentSourceFile: SourceFile;
3117+
let currentScriptKind: ScriptKind;
3118+
let currentCompilerOptions: CompilerOptions;
3119+
return {
3120+
getCurrentSourceFile: function (fileName: string) {
3121+
const scriptSnapshot = host.getScriptSnapshot(fileName);
3122+
if (!scriptSnapshot) {
3123+
// The host does not know about this file.
3124+
throw new Error("Could not find file: '" + fileName + "'.");
3125+
}
3126+
const version = host.getScriptVersion(fileName);
3127+
const scriptKind = ts.getScriptKind(fileName, host);
3128+
const compilerOptions = host.getCompilationSettings();
3129+
let sourceFile: SourceFile;
3130+
if (currentFileName !== fileName) {
3131+
// Release the current document
3132+
if (currentFileName) {
3133+
documentRegistry.releaseDocument(currentFileName, currentCompilerOptions);
3134+
}
3135+
// This is a new file, just parse it
3136+
sourceFile = documentRegistry.acquireDocument(fileName, compilerOptions, scriptSnapshot, version, scriptKind);
3137+
}
3138+
else if (currentFileVersion !== version) {
3139+
// This is the same file, just a newer version. Incrementally parse the file.
3140+
sourceFile = documentRegistry.updateDocument(fileName, compilerOptions, scriptSnapshot, version, scriptKind);
3141+
}
3142+
if (sourceFile) {
3143+
// All done, ensure state is up to date
3144+
currentFileVersion = version;
3145+
currentFileName = fileName;
3146+
currentScriptKind = scriptKind;
3147+
currentFileScriptSnapshot = scriptSnapshot;
3148+
currentSourceFile = sourceFile;
3149+
currentCompilerOptions = compilerOptions;
3150+
}
3151+
if (currentSourceFile && !currentSourceFile.locals) {
3152+
fixupParentReferences(currentSourceFile);
3153+
}
3154+
return currentSourceFile;
3155+
},
3156+
dispose: function () {
3157+
if (currentFileName) {
3158+
documentRegistry.releaseDocument(currentFileName, currentCompilerOptions);
3159+
currentFileVersion = undefined;
3160+
currentFileName = undefined;
3161+
currentScriptKind = undefined;
3162+
currentFileScriptSnapshot = undefined;
3163+
currentSourceFile = undefined;
3164+
currentCompilerOptions = undefined;
3165+
}
3166+
}
3167+
};
3168+
}
3169+
31563170
function getProgram(): Program {
31573171
synchronizeHostData();
31583172

@@ -3170,6 +3184,7 @@ namespace ts {
31703184
documentRegistry.releaseDocumentWithKey(file.path, key);
31713185
}
31723186
}
3187+
syntaxTreeCache.dispose();
31733188
}
31743189

31753190
/// Diagnostics

0 commit comments

Comments
 (0)