Skip to content

Commit 9628191

Browse files
committed
Allow the LS API to resolve referenced files
1 parent 1945e11 commit 9628191

File tree

3 files changed

+136
-112
lines changed

3 files changed

+136
-112
lines changed

src/harness/harnessLanguageService.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,17 @@ module Harness.LanguageService {
156156
}
157157

158158
private getScriptInfo(fileName: string): ScriptInfo {
159-
return this.fileNameToScript[fileName];
159+
return ts.lookUp(this.fileNameToScript, fileName);
160160
}
161161

162162
public addScript(fileName: string, content: string) {
163163
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content);
164164
}
165165

166+
private contains(fileName: string): boolean {
167+
return ts.hasProperty(this.fileNameToScript, fileName);
168+
}
169+
166170
public updateScript(fileName: string, content: string) {
167171
var script = this.getScriptInfo(fileName);
168172
if (script !== null) {
@@ -214,18 +218,28 @@ module Harness.LanguageService {
214218
return "";
215219
}
216220

221+
public getDefaultLibFilename(): string {
222+
return "";
223+
}
224+
217225
public getScriptFileNames(): string {
218226
var fileNames: string[] = [];
219-
ts.forEachKey(this.fileNameToScript, (fileName) => { fileNames.push(fileName); });
227+
ts.forEachKey(this.fileNameToScript,(fileName) => { fileNames.push(fileName); });
220228
return JSON.stringify(fileNames);
221229
}
222230

223231
public getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim {
224-
return new ScriptSnapshotShim(this.getScriptInfo(fileName));
232+
if (this.contains(fileName)) {
233+
return new ScriptSnapshotShim(this.getScriptInfo(fileName));
234+
}
235+
return undefined;
225236
}
226237

227238
public getScriptVersion(fileName: string): string {
228-
return this.getScriptInfo(fileName).version.toString();
239+
if (this.contains(fileName)) {
240+
return this.getScriptInfo(fileName).version.toString();
241+
}
242+
return undefined;
229243
}
230244

231245
public getLocalizedDiagnosticMessages(): string {

src/services/services.ts

Lines changed: 111 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,7 @@ module ts {
862862
getLocalizedDiagnosticMessages?(): any;
863863
getCancellationToken?(): CancellationToken;
864864
getCurrentDirectory(): string;
865+
getDefaultLibFilename(options: CompilerOptions): string;
865866
log? (s: string): void;
866867
trace? (s: string): void;
867868
error? (s: string): void;
@@ -1319,9 +1320,9 @@ module ts {
13191320

13201321
// Information about a specific host file.
13211322
interface HostFileInformation {
1322-
filename: string;
1323+
hostFilename: string;
13231324
version: string;
1324-
sourceText?: IScriptSnapshot;
1325+
scriptSnapshot: IScriptSnapshot;
13251326
}
13261327

13271328
interface DocumentRegistryEntry {
@@ -1409,60 +1410,69 @@ module ts {
14091410
// script id => script index
14101411
this.filenameToEntry = {};
14111412

1412-
var filenames = host.getScriptFileNames();
1413-
for (var i = 0, n = filenames.length; i < n; i++) {
1414-
var filename = filenames[i];
1415-
this.filenameToEntry[normalizeSlashes(filename)] = {
1416-
filename: filename,
1417-
version: host.getScriptVersion(filename)
1418-
};
1413+
// Initialize the list with the root file names
1414+
var rootFilenames = host.getScriptFileNames();
1415+
for (var i = 0, n = rootFilenames.length; i < n; i++) {
1416+
this.createEntry(rootFilenames[i]);
14191417
}
14201418

1419+
// store the compilation settings
14211420
this._compilationSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
14221421
}
14231422

14241423
public compilationSettings() {
14251424
return this._compilationSettings;
14261425
}
14271426

1427+
private createEntry(filename: string) {
1428+
var entry: HostFileInformation;
1429+
var scriptSnapshot = this.host.getScriptSnapshot(filename);
1430+
if (scriptSnapshot) {
1431+
entry = {
1432+
hostFilename: filename,
1433+
version: this.host.getScriptVersion(filename),
1434+
scriptSnapshot: scriptSnapshot
1435+
};
1436+
}
1437+
1438+
return this.filenameToEntry[normalizeSlashes(filename)] = entry;
1439+
}
1440+
14281441
public getEntry(filename: string): HostFileInformation {
1429-
filename = normalizeSlashes(filename);
1430-
return lookUp(this.filenameToEntry, filename);
1442+
return lookUp(this.filenameToEntry, normalizeSlashes(filename));
14311443
}
14321444

14331445
public contains(filename: string): boolean {
1434-
return !!this.getEntry(filename);
1446+
return hasProperty(this.filenameToEntry, normalizeSlashes(filename));
14351447
}
14361448

1437-
public getHostfilename(filename: string) {
1438-
var hostCacheEntry = this.getEntry(filename);
1439-
if (hostCacheEntry) {
1440-
return hostCacheEntry.filename;
1449+
public getOrCreateEntry(filename: string): HostFileInformation {
1450+
if (this.contains(filename)) {
1451+
return this.getEntry(filename);
14411452
}
1442-
return filename;
1453+
1454+
return this.createEntry(filename);
14431455
}
14441456

1445-
public getFilenames(): string[] {
1457+
public getRootFilenames(): string[] {
14461458
var fileNames: string[] = [];
14471459

14481460
forEachKey(this.filenameToEntry, key => {
1449-
if (hasProperty(this.filenameToEntry, key))
1461+
if (hasProperty(this.filenameToEntry, key) && this.filenameToEntry[key])
14501462
fileNames.push(key);
14511463
});
14521464

14531465
return fileNames;
14541466
}
14551467

14561468
public getVersion(filename: string): string {
1457-
return this.getEntry(filename).version;
1469+
var file = this.getEntry(filename);
1470+
return file && file.version;
14581471
}
14591472

14601473
public getScriptSnapshot(filename: string): IScriptSnapshot {
14611474
var file = this.getEntry(filename);
1462-
if (!file.sourceText) {
1463-
file.sourceText = this.host.getScriptSnapshot(file.filename);
1464-
}
1465-
return file.sourceText;
1475+
return file && file.scriptSnapshot;
14661476
}
14671477

14681478
public getChangeRange(filename: string, lastKnownVersion: string, oldScriptSnapshot: IScriptSnapshot): TextChangeRange {
@@ -1918,14 +1928,12 @@ module ts {
19181928
export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry): LanguageService {
19191929
var syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
19201930
var ruleProvider: formatting.RulesProvider;
1921-
var hostCache: HostCache; // A cache of all the information about the files on the host side.
19221931
var program: Program;
19231932

19241933
// this checker is used to answer all LS questions except errors
19251934
var typeInfoResolver: TypeChecker;
19261935

19271936
var useCaseSensitivefilenames = false;
1928-
var sourceFilesByName: Map<SourceFile> = {};
19291937
var documentRegistry = documentRegistry;
19301938
var cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken());
19311939
var activeCompletionSession: CompletionSession; // The current active completion session, used to get the completion entry details
@@ -1946,7 +1954,7 @@ module ts {
19461954
}
19471955

19481956
function getSourceFile(filename: string): SourceFile {
1949-
return lookUp(sourceFilesByName, getCanonicalFileName(filename));
1957+
return program.getSourceFile(getCanonicalFileName(filename));
19501958
}
19511959

19521960
function getDiagnosticsProducingTypeChecker() {
@@ -1963,112 +1971,108 @@ module ts {
19631971
return ruleProvider;
19641972
}
19651973

1966-
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
1967-
return sourceFile && sourceFile.version === hostCache.getVersion(sourceFile.filename);
1968-
}
1969-
1970-
function programUpToDate(): boolean {
1971-
// If we haven't create a program yet, then it is not up-to-date
1972-
if (!program) {
1973-
return false;
1974-
}
1975-
1976-
// If number of files in the program do not match, it is not up-to-date
1977-
var hostFilenames = hostCache.getFilenames();
1978-
if (program.getSourceFiles().length !== hostFilenames.length) {
1979-
return false;
1980-
}
1981-
1982-
// If any file is not up-to-date, then the whole program is not up-to-date
1983-
for (var i = 0, n = hostFilenames.length; i < n; i++) {
1984-
if (!sourceFileUpToDate(program.getSourceFile(hostFilenames[i]))) {
1985-
return false;
1986-
}
1987-
}
1988-
1989-
// If the compilation settings do no match, then the program is not up-to-date
1990-
return compareDataObjects(program.getCompilerOptions(), hostCache.compilationSettings());
1991-
}
1992-
19931974
function synchronizeHostData(): void {
1994-
// Reset the cache at start of every refresh
1995-
hostCache = new HostCache(host);
1975+
// Get a fresh cache of the host information
1976+
var hostCache = new HostCache(host);
19961977

19971978
// If the program is already up-to-date, we can reuse it
19981979
if (programUpToDate()) {
19991980
return;
20001981
}
20011982

2002-
var compilationSettings = hostCache.compilationSettings();
1983+
var oldSettings = program && program.getCompilerOptions();
1984+
var newSettings = hostCache.compilationSettings();
1985+
var changesInCompilationSettingsAffectSyntax = oldSettings && oldSettings.target !== newSettings.target;
20031986

2004-
// Now, remove any files from the compiler that are no longer in the host.
2005-
var oldProgram = program;
2006-
if (oldProgram) {
2007-
var oldSettings = program.getCompilerOptions();
2008-
// If the language version changed, then that affects what types of things we parse. So
2009-
// we have to dump all syntax trees.
2010-
// TODO: handle propagateEnumConstants
2011-
// TODO: is module still needed
2012-
var settingsChangeAffectsSyntax = oldSettings.target !== compilationSettings.target || oldSettings.module !== compilationSettings.module;
1987+
// Now create a new compiler
1988+
var newProgram = createProgram(hostCache.getRootFilenames(), newSettings, {
1989+
getSourceFile: getOrCreateSourceFile,
1990+
getCancellationToken: () => cancellationToken,
1991+
getCanonicalFileName: (filename) => useCaseSensitivefilenames ? filename : filename.toLowerCase(),
1992+
useCaseSensitiveFileNames: () => useCaseSensitivefilenames,
1993+
getNewLine: () => host.getNewLine ? host.getNewLine() : "\r\n",
1994+
getDefaultLibFilename: (options) => host.getDefaultLibFilename(options),
1995+
writeFile: (filename, data, writeByteOrderMark) => { },
1996+
getCurrentDirectory: () => host.getCurrentDirectory()
1997+
});
20131998

2014-
var changesInCompilationSettingsAffectSyntax =
2015-
oldSettings && compilationSettings && !compareDataObjects(oldSettings, compilationSettings) && settingsChangeAffectsSyntax;
1999+
// Release any files we have acquired in the old program but are
2000+
// not part of the new program.
2001+
if (program) {
20162002
var oldSourceFiles = program.getSourceFiles();
2017-
20182003
for (var i = 0, n = oldSourceFiles.length; i < n; i++) {
2019-
cancellationToken.throwIfCancellationRequested();
20202004
var filename = oldSourceFiles[i].filename;
2021-
if (!hostCache.contains(filename) || changesInCompilationSettingsAffectSyntax) {
2005+
if (!newProgram.getSourceFile(filename) || changesInCompilationSettingsAffectSyntax) {
20222006
documentRegistry.releaseDocument(filename, oldSettings);
2023-
delete sourceFilesByName[getCanonicalFileName(filename)];
20242007
}
20252008
}
20262009
}
20272010

2028-
// Now, for every file the host knows about, either add the file (if the compiler
2029-
// doesn't know about it.). Or notify the compiler about any changes (if it does
2030-
// know about it.)
2031-
var hostfilenames = hostCache.getFilenames();
2032-
for (var i = 0, n = hostfilenames.length; i < n; i++) {
2033-
var filename = hostfilenames[i];
2011+
program = newProgram;
2012+
typeInfoResolver = program.getTypeChecker(/*produceDiagnostics*/ false);
20342013

2035-
var version = hostCache.getVersion(filename);
2036-
var scriptSnapshot = hostCache.getScriptSnapshot(filename);
2014+
return;
20372015

2038-
var sourceFile: SourceFile = getSourceFile(filename);
2039-
if (sourceFile) {
2040-
//
2041-
// If the sourceFile is the same, assume no update
2042-
//
2043-
if (sourceFileUpToDate(sourceFile)) {
2044-
continue;
2016+
function getOrCreateSourceFile(filename: string): SourceFile {
2017+
cancellationToken.throwIfCancellationRequested();
2018+
2019+
// The program is asking for this file, check first if the host can locate it.
2020+
// If the host can not locate the file, then it does not exist. return undefined
2021+
// to the program to allow reporting of errors for missing files.
2022+
var hostFileInformation = hostCache.getOrCreateEntry(filename);
2023+
if (!hostFileInformation) {
2024+
return undefined;
2025+
}
2026+
2027+
// Check if the language version has changed since we last created a program; if they are the same,
2028+
// it is safe to reuse the souceFiles; if not, then the shape of the AST can change, and the oldSourceFile
2029+
// can not be reused. we have to dump all syntax trees and create new ones.
2030+
if (!changesInCompilationSettingsAffectSyntax) {
2031+
2032+
// Check if the old program had this file already
2033+
var oldSourceFile = program && program.getSourceFile(filename);
2034+
if (oldSourceFile) {
2035+
// This SourceFile is safe to reuse, return it
2036+
if (sourceFileUpToDate(oldSourceFile)) {
2037+
return oldSourceFile;
2038+
}
2039+
2040+
// We have an older version of the sourceFile, incrementally parse the changes
2041+
var textChangeRange = hostCache.getChangeRange(filename, oldSourceFile.version, oldSourceFile.scriptSnapshot);
2042+
return documentRegistry.updateDocument(oldSourceFile, filename, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version, textChangeRange);
20452043
}
2044+
}
2045+
2046+
// Could not find this file in the old program, create a new SourceFile for it.
2047+
return documentRegistry.acquireDocument(filename, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version);
2048+
}
20462049

2047-
var textChangeRange: TextChangeRange = null;
2048-
textChangeRange = hostCache.getChangeRange(filename, sourceFile.version, sourceFile.scriptSnapshot);
2050+
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
2051+
return sourceFile && sourceFile.version === hostCache.getVersion(sourceFile.filename);
2052+
}
20492053

2050-
sourceFile = documentRegistry.updateDocument(sourceFile, filename, compilationSettings, scriptSnapshot, version, textChangeRange);
2054+
function programUpToDate(): boolean {
2055+
// If we haven't create a program yet, then it is not up-to-date
2056+
if (!program) {
2057+
return false;
20512058
}
2052-
else {
2053-
sourceFile = documentRegistry.acquireDocument(filename, compilationSettings, scriptSnapshot, version);
2059+
2060+
// If number of files in the program do not match, it is not up-to-date
2061+
var rootFilenames = hostCache.getRootFilenames();
2062+
if (program.getSourceFiles().length !== rootFilenames.length) {
2063+
return false;
20542064
}
20552065

2056-
// Remember the new sourceFile
2057-
sourceFilesByName[getCanonicalFileName(filename)] = sourceFile;
2058-
}
2066+
// If any file is not up-to-date, then the whole program is not up-to-date
2067+
for (var i = 0, n = rootFilenames.length; i < n; i++) {
2068+
if (!sourceFileUpToDate(program.getSourceFile(rootFilenames[i]))) {
2069+
return false;
2070+
}
2071+
}
20592072

2060-
// Now create a new compiler
2061-
program = createProgram(hostfilenames, compilationSettings, {
2062-
getSourceFile,
2063-
getCancellationToken: () => cancellationToken,
2064-
getCanonicalFileName: filename => useCaseSensitivefilenames ? filename : filename.toLowerCase(),
2065-
useCaseSensitiveFileNames: () => useCaseSensitivefilenames,
2066-
getNewLine: () => host.getNewLine ? host.getNewLine() : "\r\n",
2067-
getDefaultLibFilename,
2068-
writeFile: (filename, data, writeByteOrderMark) => { },
2069-
getCurrentDirectory: () => host.getCurrentDirectory()
2070-
});
2071-
typeInfoResolver = program.getTypeChecker(/*produceDiagnostics*/ false);
2073+
// If the compilation settings do no match, then the program is not up-to-date
2074+
return compareDataObjects(program.getCompilerOptions(), hostCache.compilationSettings());
2075+
}
20722076
}
20732077

20742078
/**

0 commit comments

Comments
 (0)