///
module ts {
interface File {
name: string;
content: string;
}
function createDefaultServerHost(fileMap: Map): server.ServerHost {
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 => {
throw new Error("NYI");
},
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: () => { }
}
}
};
}
function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } {
let logger: server.Logger = {
close() { },
isVerbose: () => false,
loggingEnabled: () => false,
perftrc: (s: string) => { },
info: (s: string) => { },
startGroup: () => { },
endGroup: () => { },
msg: (s: string, type?: string) => { }
};
let projectService = new server.ProjectService(serverHost, logger);
let rootScriptInfo = projectService.openFile(rootFile, /* openedByClient */true);
let 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", () => {
let root: File = {
name: "c:/d/f0.ts",
content: `import {x} from "f1"`
};
let imported: File = {
name: "c:/f1.ts",
content: `foo()`
};
let serverHost = createDefaultServerHost({ [root.name]: root, [imported.name]: imported });
let { 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);
let 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;
};
let 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(fileName);
};
let 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(fileName);
};
let 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;
let 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", () => {
let root: File = {
name: 'c:/foo.ts',
content: `import {x} from "bar"`
};
let imported: File = {
name: 'c:/bar.d.ts',
content: `export var y = 1`
};
let fileMap: Map = { [root.name]: root };
let serverHost = createDefaultServerHost(fileMap);
let 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(fileName);
};
let { 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);
})
});
}