///
namespace ts {
interface File {
name: string;
content: string;
}
function createDefaultServerHost(fileMap: Map): server.ServerHost {
const existingDirectories: Map = {};
forEachValue(fileMap, v => {
let dir = getDirectoryPath(v.name);
let previous: string;
do {
existingDirectories[dir] = true;
previous = dir;
dir = getDirectoryPath(dir);
} while (dir !== previous);
});
return {
args: [],
newLine: "\r\n",
useCaseSensitiveFileNames: false,
write: (s: string) => {
},
readFile: (path: string, encoding?: string): string => {
return hasProperty(fileMap, path) && fileMap[path].content;
},
writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => {
throw new Error("NYI");
},
resolvePath: (path: string): string => {
throw new Error("NYI");
},
fileExists: (path: string): boolean => {
return hasProperty(fileMap, path);
},
directoryExists: (path: string): boolean => {
return hasProperty(existingDirectories, path);
},
createDirectory: (path: string) => {
},
getExecutingFilePath: (): string => {
return "";
},
getCurrentDirectory: (): string => {
return "";
},
readDirectory: (path: string, extension?: string, exclude?: string[]): string[] => {
throw new Error("NYI");
},
exit: (exitCode?: number) => {
},
watchFile: (path, callback) => {
return {
close: () => { }
};
},
watchDirectory: (path, callback, recursive?) => {
return {
close: () => { }
};
},
setTimeout,
clearTimeout
};
}
function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } {
const logger: server.Logger = {
close() { },
isVerbose: () => false,
loggingEnabled: () => false,
perftrc: (s: string) => { },
info: (s: string) => { },
startGroup: () => { },
endGroup: () => { },
msg: (s: string, type?: string) => { }
};
const projectService = new server.ProjectService(serverHost, logger);
const rootScriptInfo = projectService.openFile(rootFile, /* openedByClient */true);
const project = projectService.createInferredProject(rootScriptInfo);
project.setProjectOptions({ files: [rootScriptInfo.fileName], compilerOptions: { module: ts.ModuleKind.AMD } });
return {
project,
rootScriptInfo
};
}
describe("Caching in LSHost", () => {
it("works using legacy resolution logic", () => {
const root: File = {
name: "c:/d/f0.ts",
content: `import {x} from "f1"`
};
const imported: File = {
name: "c:/f1.ts",
content: `foo()`
};
const serverHost = createDefaultServerHost({ [root.name]: root, [imported.name]: imported });
const { project, rootScriptInfo } = createProject(root.name, serverHost);
// ensure that imported file was found
let diags = project.compilerService.languageService.getSemanticDiagnostics(imported.name);
assert.equal(diags.length, 1);
const originalFileExists = serverHost.fileExists;
{
// patch fileExists to make sure that disk is not touched
serverHost.fileExists = (fileName): boolean => {
assert.isTrue(false, "fileExists should not be called");
return false;
};
const newContent = `import {x} from "f1"
var x: string = 1;`;
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
// trigger synchronization to make sure that import will be fetched from the cache
diags = project.compilerService.languageService.getSemanticDiagnostics(imported.name);
// ensure file has correct number of errors after edit
assert.equal(diags.length, 1);
}
{
let fileExistsIsCalled = false;
serverHost.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsIsCalled = true;
assert.isTrue(fileName.indexOf("/f2.") !== -1);
return originalFileExists.call(serverHost, fileName);
};
const newContent = `import {x} from "f2"`;
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
try {
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
assert.isTrue(false, `should not find file '${imported.name}'`);
}
catch (e) {
assert.isTrue(e.message.indexOf(`Could not find file: '${imported.name}'.`) === 0);
}
assert.isTrue(fileExistsIsCalled);
}
{
let fileExistsCalled = false;
serverHost.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsCalled = true;
assert.isTrue(fileName.indexOf("/f1.") !== -1);
return originalFileExists.call(serverHost, fileName);
};
const newContent = `import {x} from "f1"`;
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
assert.isTrue(fileExistsCalled);
// setting compiler options discards module resolution cache
fileExistsCalled = false;
const opts = ts.clone(project.projectOptions);
opts.compilerOptions = ts.clone(opts.compilerOptions);
opts.compilerOptions.target = ts.ScriptTarget.ES5;
project.setProjectOptions(opts);
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
assert.isTrue(fileExistsCalled);
}
});
it("loads missing files from disk", () => {
const root: File = {
name: `c:/foo.ts`,
content: `import {x} from "bar"`
};
const imported: File = {
name: `c:/bar.d.ts`,
content: `export var y = 1`
};
const fileMap: Map = { [root.name]: root };
const serverHost = createDefaultServerHost(fileMap);
const originalFileExists = serverHost.fileExists;
let fileExistsCalledForBar = false;
serverHost.fileExists = fileName => {
if (fileName === "lib.d.ts") {
return false;
}
if (!fileExistsCalledForBar) {
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
}
return originalFileExists.call(serverHost, fileName);
};
const { project, rootScriptInfo } = createProject(root.name, serverHost);
let diags = project.compilerService.languageService.getSemanticDiagnostics(root.name);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
assert.isTrue(diags.length === 1, "one diagnostic expected");
assert.isTrue(typeof diags[0].messageText === "string" && ((diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message");
// assert that import will success once file appear on disk
fileMap[imported.name] = imported;
fileExistsCalledForBar = false;
rootScriptInfo.editContent(0, rootScriptInfo.content.length, `import {y} from "bar"`);
diags = project.compilerService.languageService.getSemanticDiagnostics(root.name);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
assert.isTrue(diags.length === 0);
});
});
}