Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 34 additions & 33 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ namespace ts {
if (result && result.jsDocComment) {
// because the jsDocComment was parsed out of the source file, it might
// not be covered by the fixupParentReferences.
Parser.fixupParentReferences(result.jsDocComment);
fixupParentReferences(result.jsDocComment);
}

return result;
Expand All @@ -455,6 +455,39 @@ namespace ts {
return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length);
}

/* @internal */
export function fixupParentReferences(rootNode: Node) {
// 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 = rootNode;
forEachChild(rootNode, 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;

const saveParent = parent;
parent = n;
forEachChild(n, visitNode);
if (n.jsDocComments) {
for (const jsDocComment of n.jsDocComments) {
jsDocComment.parent = n;
parent = jsDocComment;
forEachChild(jsDocComment, visitNode);
}
}
parent = saveParent;
}
}
}

// 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.
Expand Down Expand Up @@ -658,38 +691,6 @@ namespace ts {
return node;
}

export function fixupParentReferences(rootNode: Node) {
// 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 = rootNode;
forEachChild(rootNode, 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;

const saveParent = parent;
parent = n;
forEachChild(n, visitNode);
if (n.jsDocComments) {
for (const jsDocComment of n.jsDocComments) {
jsDocComment.parent = n;
parent = jsDocComment;
forEachChild(jsDocComment, visitNode);
}
}
parent = saveParent;
}
}
}

function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind): SourceFile {
// code from createNode is inlined here so createNode won't have to deal with special case of creating source files
// this is quite rare comparing to other nodes and createNode should be as fast as possible
Expand Down
2 changes: 1 addition & 1 deletion src/harness/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ namespace Utils {
// call this on both nodes to ensure all propagated flags have been set (and thus can be
// compared).
assert.equal(ts.containsParseError(node1), ts.containsParseError(node2));
assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags");
assert.equal(node1.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, node2.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, "node1.flags !== node2.flags");

ts.forEachChild(node1,
child1 => {
Expand Down
105 changes: 60 additions & 45 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1946,50 +1946,6 @@ namespace ts {
}
}

class SyntaxTreeCache {
// For our syntactic only features, we also keep a cache of the syntax tree for the
// currently edited file.
private currentFileName: string;
private currentFileVersion: string;
private currentFileScriptSnapshot: IScriptSnapshot;
private currentSourceFile: SourceFile;

constructor(private host: LanguageServiceHost) {
}

public getCurrentSourceFile(fileName: string): SourceFile {
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
if (!scriptSnapshot) {
// The host does not know about this file.
throw new Error("Could not find file: '" + fileName + "'.");
}

const scriptKind = getScriptKind(fileName, this.host);
const version = this.host.getScriptVersion(fileName);
let sourceFile: SourceFile;

if (this.currentFileName !== fileName) {
// This is a new file, just parse it
sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, ScriptTarget.Latest, version, /*setNodeParents*/ true, scriptKind);
}
else if (this.currentFileVersion !== version) {
// This is the same file, just a newer version. Incrementally parse the file.
const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot);
sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, editRange);
}

if (sourceFile) {
// All done, ensure state is up to date
this.currentFileVersion = version;
this.currentFileName = fileName;
this.currentFileScriptSnapshot = scriptSnapshot;
this.currentSourceFile = sourceFile;
}

return this.currentSourceFile;
}
}

function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) {
sourceFile.version = version;
sourceFile.scriptSnapshot = scriptSnapshot;
Expand Down Expand Up @@ -2983,7 +2939,7 @@ namespace ts {
export function createLanguageService(host: LanguageServiceHost,
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {

const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
const syntaxTreeCache = createSyntaxTreeCache();
let ruleProvider: formatting.RulesProvider;
let program: Program;
let lastProjectVersion: string;
Expand Down Expand Up @@ -3218,6 +3174,64 @@ namespace ts {
}
}

function createSyntaxTreeCache() {
let currentFileName: string;
let currentFileVersion: string;
let currentFileScriptSnapshot: IScriptSnapshot;
let currentSourceFile: SourceFile;
let currentScriptKind: ScriptKind;
let currentCompilerOptions: CompilerOptions;
return {
getCurrentSourceFile: function (fileName: string) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: method syntax

const scriptSnapshot = host.getScriptSnapshot(fileName);
if (!scriptSnapshot) {
// The host does not know about this file.
throw new Error("Could not find file: '" + fileName + "'.");
}
const version = host.getScriptVersion(fileName);
const scriptKind = ts.getScriptKind(fileName, host);
const compilerOptions = host.getCompilationSettings();
let sourceFile: SourceFile;
if (currentFileName !== fileName) {
// Release the current document
if (currentFileName) {
documentRegistry.releaseDocument(currentFileName, currentCompilerOptions);
}
// This is a new file, just parse it
sourceFile = documentRegistry.acquireDocument(fileName, compilerOptions, scriptSnapshot, version, scriptKind);
}
else if (currentFileVersion !== version) {
// This is the same file, just a newer version. Incrementally parse the file.
sourceFile = documentRegistry.updateDocument(fileName, compilerOptions, scriptSnapshot, version, scriptKind);
}
if (sourceFile) {
// All done, ensure state is up to date
currentFileVersion = version;
currentFileName = fileName;
currentScriptKind = scriptKind;
currentFileScriptSnapshot = scriptSnapshot;
currentSourceFile = sourceFile;
currentCompilerOptions = compilerOptions;
}
if (currentSourceFile && !currentSourceFile.locals) {
fixupParentReferences(currentSourceFile);
}
return currentSourceFile;
},
dispose: function () {
if (currentFileName) {
documentRegistry.releaseDocument(currentFileName, currentCompilerOptions);
currentFileVersion = undefined;
currentFileName = undefined;
currentScriptKind = undefined;
currentFileScriptSnapshot = undefined;
currentSourceFile = undefined;
currentCompilerOptions = undefined;
}
}
};
}

function getProgram(): Program {
synchronizeHostData();

Expand All @@ -3236,6 +3250,7 @@ namespace ts {
documentRegistry.releaseDocumentWithKey(file.path, key);
}
}
syntaxTreeCache.dispose();
}

/// Diagnostics
Expand Down