diff --git a/Jakefile.js b/Jakefile.js
index e425a8767c4..58779a3d8dc 100644
--- a/Jakefile.js
+++ b/Jakefile.js
@@ -138,6 +138,7 @@ var harnessSources = harnessCoreSources.concat([
"telemetry.ts",
"transform.ts",
"customTransforms.ts",
+ "programMissingFiles.ts",
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json
index eee6473f77f..8d52791f539 100644
--- a/src/harness/tsconfig.json
+++ b/src/harness/tsconfig.json
@@ -129,6 +129,7 @@
"./unittests/transform.ts",
"./unittests/customTransforms.ts",
"./unittests/textChanges.ts",
- "./unittests/telemetry.ts"
+ "./unittests/telemetry.ts",
+ "./unittests/programMissingFiles.ts"
]
}
diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts
index 46d9aa462ce..eb2907e89de 100644
--- a/src/harness/unittests/cachingInServerLSHost.ts
+++ b/src/harness/unittests/cachingInServerLSHost.ts
@@ -75,7 +75,7 @@ namespace ts {
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined);
const project = projectService.createInferredProjectWithRootFileIfNecessary(rootScriptInfo);
- project.setCompilerOptions({ module: ts.ModuleKind.AMD } );
+ project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } );
return {
project,
rootScriptInfo
diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts
new file mode 100644
index 00000000000..26cc63590bd
--- /dev/null
+++ b/src/harness/unittests/programMissingFiles.ts
@@ -0,0 +1,109 @@
+///
+
+namespace ts {
+ describe("Program.getMissingFilePaths", () => {
+
+ const options: CompilerOptions = {
+ noLib: true,
+ };
+
+ const emptyFileName = "empty.ts";
+ const emptyFileRelativePath = "./" + emptyFileName;
+
+ const emptyFile: Harness.Compiler.TestFile = {
+ unitName: emptyFileName,
+ content: ""
+ };
+
+ const referenceFileName = "reference.ts";
+ const referenceFileRelativePath = "./" + referenceFileName;
+
+ const referenceFile: Harness.Compiler.TestFile = {
+ unitName: referenceFileName,
+ content:
+ "/// \n" + // Absolute
+ "/// \n" + // Relative
+ "/// \n" + // Unqualified
+ "/// \n" // No extension
+ };
+
+ const testCompilerHost = Harness.Compiler.createCompilerHost(
+ /*inputFiles*/ [emptyFile, referenceFile],
+ /*writeFile*/ undefined,
+ /*scriptTarget*/ undefined,
+ /*useCaseSensitiveFileNames*/ false,
+ /*currentDirectory*/ "d:\\pretend\\",
+ /*newLineKind*/ NewLineKind.LineFeed,
+ /*libFiles*/ undefined
+ );
+
+ it("handles no missing root files", () => {
+ const program = createProgram([emptyFileRelativePath], options, testCompilerHost);
+ const missing = program.getMissingFilePaths();
+ assert.isDefined(missing);
+ assert.equal(missing.length, 0);
+ });
+
+ it("handles missing root file", () => {
+ const program = createProgram(["./nonexistent.ts"], options, testCompilerHost);
+ const missing = program.getMissingFilePaths();
+ assert.isDefined(missing);
+ assert.equal(missing.length, 1);
+ assert.equal(missing[0].toString(), "d:/pretend/nonexistent.ts"); // Absolute path
+ });
+
+ it("handles multiple missing root files", () => {
+ const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost);
+ const missing = program.getMissingFilePaths().sort();
+ assert.equal(missing.length, 2);
+ assert.equal(missing[0].toString(), "d:/pretend/nonexistent0.ts");
+ assert.equal(missing[1].toString(), "d:/pretend/nonexistent1.ts");
+ });
+
+ it("handles a mix of present and missing root files", () => {
+ const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost);
+ const missing = program.getMissingFilePaths().sort();
+ assert.equal(missing.length, 2);
+ assert.equal(missing[0].toString(), "d:/pretend/nonexistent0.ts");
+ assert.equal(missing[1].toString(), "d:/pretend/nonexistent1.ts");
+ });
+
+ it("handles repeatedly specified root files", () => {
+ const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost);
+ const missing = program.getMissingFilePaths();
+ assert.isDefined(missing);
+ assert.equal(missing.length, 1);
+ assert.equal(missing[0].toString(), "d:/pretend/nonexistent.ts");
+ });
+
+ it("normalizes file paths", () => {
+ const program0 = createProgram(["./nonexistent.ts", "./NONEXISTENT.ts"], options, testCompilerHost);
+ const program1 = createProgram(["./NONEXISTENT.ts", "./nonexistent.ts"], options, testCompilerHost);
+ const missing0 = program0.getMissingFilePaths();
+ const missing1 = program1.getMissingFilePaths();
+ assert.equal(missing0.length, 1);
+ assert.deepEqual(missing0, missing1);
+ });
+
+ it("handles missing triple slash references", () => {
+ const program = createProgram([referenceFileRelativePath], options, testCompilerHost);
+ const missing = program.getMissingFilePaths().sort();
+ assert.isDefined(missing);
+ assert.equal(missing.length, 6);
+
+ // From absolute reference
+ assert.equal(missing[0].toString(), "d:/imaginary/nonexistent1.ts");
+
+ // From relative reference
+ assert.equal(missing[1].toString(), "d:/pretend/nonexistent2.ts");
+
+ // From unqualified reference
+ assert.equal(missing[2].toString(), "d:/pretend/nonexistent3.ts");
+
+ // From no-extension reference
+ assert.equal(missing[3].toString(), "d:/pretend/nonexistent4.d.ts");
+ assert.equal(missing[4].toString(), "d:/pretend/nonexistent4.ts");
+ assert.equal(missing[5].toString(), "d:/pretend/nonexistent4.tsx");
+ });
+ });
+}
\ No newline at end of file
diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts
index 2eb3d1f0890..de5f5a756d8 100644
--- a/src/harness/unittests/reuseProgramStructure.ts
+++ b/src/harness/unittests/reuseProgramStructure.ts
@@ -316,6 +316,31 @@ namespace ts {
assert.isTrue(program_1.structureIsReused === StructureIsReused.Not);
});
+ it("succeeds if missing files remain missing", () => {
+ const options: CompilerOptions = { target, noLib: true };
+
+ const program_1 = newProgram(files, ["a.ts"], options);
+ assert.notDeepEqual(emptyArray, program_1.getMissingFilePaths());
+
+ const program_2 = updateProgram(program_1, ["a.ts"], options, noop);
+ assert.deepEqual(program_1.getMissingFilePaths(), program_2.getMissingFilePaths());
+
+ assert.equal(StructureIsReused.Completely, program_1.structureIsReused);
+ });
+
+ it("fails if missing file is created", () => {
+ const options: CompilerOptions = { target, noLib: true };
+
+ const program_1 = newProgram(files, ["a.ts"], options);
+ assert.notDeepEqual(emptyArray, program_1.getMissingFilePaths());
+
+ const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]);
+ const program_2 = updateProgram(program_1, ["a.ts"], options, noop, newTexts);
+ assert.deepEqual(emptyArray, program_2.getMissingFilePaths());
+
+ assert.equal(StructureIsReused.SafeModules, program_1.structureIsReused);
+ });
+
it("resolution cache follows imports", () => {
(Error).stackTraceLimit = Infinity;