1namespace ts.tscWatch { 2 describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => { 3 const configFileJson: any = { 4 compilerOptions: { module: "commonjs", resolveJsonModule: true }, 5 files: ["index.ts"] 6 }; 7 const mainFile: File = { 8 path: `${projectRoot}/index.ts`, 9 content: "import settings from './settings.json';" 10 }; 11 const config: File = { 12 path: `${projectRoot}/tsconfig.json`, 13 content: JSON.stringify(configFileJson) 14 }; 15 const settingsJson: File = { 16 path: `${projectRoot}/settings.json`, 17 content: JSON.stringify({ content: "Print this" }) 18 }; 19 20 it("verify that module resolution with json extension works when returned without extension", () => { 21 const files = [libFile, mainFile, config, settingsJson]; 22 const host = createWatchedSystem(files, { currentDirectory: projectRoot }); 23 const compilerHost = createWatchCompilerHostOfConfigFile({ 24 configFileName: config.path, 25 system: host 26 }); 27 const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path); 28 compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => { 29 const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost); 30 const resolvedModule = result.resolvedModule!; 31 return { 32 resolvedFileName: resolvedModule.resolvedFileName, 33 isExternalLibraryImport: resolvedModule.isExternalLibraryImport, 34 originalFileName: resolvedModule.originalPath, 35 }; 36 }); 37 const watch = createWatchProgram(compilerHost); 38 const program = watch.getCurrentProgram().getProgram(); 39 checkProgramActualFiles(program, [mainFile.path, libFile.path, settingsJson.path]); 40 }); 41 }); 42 43 describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to watch status reporter", () => { 44 const configFileJson: any = { 45 compilerOptions: { module: "commonjs" }, 46 files: ["index.ts"] 47 }; 48 const config: File = { 49 path: `${projectRoot}/tsconfig.json`, 50 content: JSON.stringify(configFileJson) 51 }; 52 const mainFile: File = { 53 path: `${projectRoot}/index.ts`, 54 content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}" 55 }; 56 57 it("verify that the error count is correctly passed down to the watch status reporter", () => { 58 const files = [libFile, mainFile, config]; 59 const host = createWatchedSystem(files, { currentDirectory: projectRoot }); 60 let watchedErrorCount; 61 const reportWatchStatus: WatchStatusReporter = (_, __, ___, errorCount) => { 62 watchedErrorCount = errorCount; 63 }; 64 const compilerHost = createWatchCompilerHostOfConfigFile({ 65 configFileName: config.path, 66 system: host, 67 reportWatchStatus 68 }); 69 createWatchProgram(compilerHost); 70 assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change"); 71 }); 72 }); 73 74 describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement setTimeout or clearTimeout", () => { 75 it("verifies that getProgram gets updated program if new file is added to the program", () => { 76 const config: File = { 77 path: `${projectRoot}/tsconfig.json`, 78 content: "{}" 79 }; 80 const mainFile: File = { 81 path: `${projectRoot}/main.ts`, 82 content: "const x = 10;" 83 }; 84 const sys = createWatchedSystem([config, mainFile, libFile]); 85 const watchCompilerHost = createWatchCompilerHost(config.path, {}, sys); 86 watchCompilerHost.setTimeout = undefined; 87 watchCompilerHost.clearTimeout = undefined; 88 const watch = createWatchProgram(watchCompilerHost); 89 checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, libFile.path]); 90 // Write new file 91 const barPath = `${projectRoot}/bar.ts`; 92 sys.writeFile(barPath, "const y =10;"); 93 checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, barPath, libFile.path]); 94 }); 95 }); 96 97 describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExtensions to process", () => { 98 it("verifies that extraFileExtensions are supported to get the program with other extensions", () => { 99 const config: File = { 100 path: `${projectRoot}/tsconfig.json`, 101 content: "{}" 102 }; 103 const mainFile: File = { 104 path: `${projectRoot}/main.ts`, 105 content: "const x = 10;" 106 }; 107 const otherFile: File = { 108 path: `${projectRoot}/other.vue`, 109 content: "" 110 }; 111 const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); 112 const watchCompilerHost = createWatchCompilerHost( 113 config.path, 114 { allowNonTsExtensions: true }, 115 sys, 116 /*createProgram*/ undefined, 117 /*reportDiagnostics*/ undefined, 118 /*reportWatchStatus*/ undefined, 119 /*watchOptionsToExtend*/ undefined, 120 [{ extension: ".vue", isMixedContent: true, scriptKind: ScriptKind.Deferred }] 121 ); 122 const watch = createWatchProgram(watchCompilerHost); 123 checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]); 124 125 const other2 = `${projectRoot}/other2.vue`; 126 sys.writeFile(other2, otherFile.content); 127 checkSingleTimeoutQueueLengthAndRun(sys); 128 checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path, other2]); 129 }); 130 }); 131 132 describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => { 133 function getWatch<T extends BuilderProgram>(config: File, optionsToExtend: CompilerOptions | undefined, sys: System, createProgram: CreateProgram<T>) { 134 const watchCompilerHost = createWatchCompilerHost(config.path, optionsToExtend, sys, createProgram); 135 return createWatchProgram(watchCompilerHost); 136 } 137 138 function setup<T extends BuilderProgram>(createProgram: CreateProgram<T>, configText: string) { 139 const config: File = { 140 path: `${projectRoot}/tsconfig.json`, 141 content: configText 142 }; 143 const mainFile: File = { 144 path: `${projectRoot}/main.ts`, 145 content: "export const x = 10;" 146 }; 147 const otherFile: File = { 148 path: `${projectRoot}/other.ts`, 149 content: "export const y = 10;" 150 }; 151 const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); 152 const watch = getWatch(config, { noEmit: true }, sys, createProgram); 153 return { sys, watch, mainFile, otherFile, config }; 154 } 155 156 function verifyOutputs(sys: System, emitSys: System) { 157 for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) { 158 assert.strictEqual(sys.readFile(output), emitSys.readFile(output), `Output file text for ${output}`); 159 } 160 } 161 162 function verifyBuilder<T extends BuilderProgram, U extends BuilderProgram>(config: File, sys: System, emitSys: System, createProgram: CreateProgram<T>, createEmitProgram: CreateProgram<U>, optionsToExtend?: CompilerOptions) { 163 const watch = getWatch(config, /*optionsToExtend*/ optionsToExtend, sys, createProgram); 164 const emitWatch = getWatch(config, /*optionsToExtend*/ optionsToExtend, emitSys, createEmitProgram); 165 verifyOutputs(sys, emitSys); 166 watch.close(); 167 emitWatch.close(); 168 } 169 170 it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => { 171 const { sys, watch, mainFile, otherFile } = setup(createSemanticDiagnosticsBuilderProgram, "{}"); 172 checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]); 173 sys.appendFile(mainFile.path, "\n// SomeComment"); 174 sys.runQueuedTimeoutCallbacks(); 175 const program = watch.getProgram().getProgram(); 176 assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(mainFile.path)), []); 177 // Should not retrieve diagnostics for other file thats not changed 178 assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(otherFile.path)), /*expected*/ undefined); 179 }); 180 181 it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { 182 const configText = JSON.stringify({ compilerOptions: { composite: true } }); 183 const { sys, watch, config, mainFile } = setup(createSemanticDiagnosticsBuilderProgram, configText); 184 const { sys: emitSys, watch: emitWatch } = setup(createEmitAndSemanticDiagnosticsBuilderProgram, configText); 185 verifyOutputs(sys, emitSys); 186 187 watch.close(); 188 emitWatch.close(); 189 190 // Emit on both sys should result in same output 191 verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); 192 193 // Change file 194 sys.appendFile(mainFile.path, "\n// SomeComment"); 195 emitSys.appendFile(mainFile.path, "\n// SomeComment"); 196 197 // Verify noEmit results in same output 198 verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true }); 199 200 // Emit on both sys should result in same output 201 verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); 202 203 // Change file 204 sys.appendFile(mainFile.path, "\n// SomeComment"); 205 emitSys.appendFile(mainFile.path, "\n// SomeComment"); 206 207 // Emit on both the builders should result in same files 208 verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram); 209 }); 210 211 it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { 212 const config: File = { 213 path: `${projectRoot}/tsconfig.json`, 214 content: JSON.stringify({ compilerOptions: { composite: true } }) 215 }; 216 const mainFile: File = { 217 path: `${projectRoot}/main.ts`, 218 content: "export const x: string = 10;" 219 }; 220 const otherFile: File = { 221 path: `${projectRoot}/other.ts`, 222 content: "export const y = 10;" 223 }; 224 const sys = createWatchedSystem([config, mainFile, otherFile, libFile]); 225 const emitSys = createWatchedSystem([config, mainFile, otherFile, libFile]); 226 227 // Verify noEmit results in same output 228 verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); 229 230 // Change file 231 sys.appendFile(mainFile.path, "\n// SomeComment"); 232 emitSys.appendFile(mainFile.path, "\n// SomeComment"); 233 234 // Verify noEmit results in same output 235 verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); 236 237 // Fix error 238 const fixed = "export const x = 10;"; 239 sys.appendFile(mainFile.path, fixed); 240 emitSys.appendFile(mainFile.path, fixed); 241 242 // Emit on both the builders should result in same files 243 verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true }); 244 }); 245 }); 246} 247