diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 33790a1ab35..1aaa3e07f1c 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -483,11 +483,11 @@ namespace ts { function startWatching() { const graph = getGlobalDependencyGraph(); for (const resolved of graph.buildQueue) { + // Watch this file + watchConfigFile(resolved); + const cfg = parseConfigFile(resolved); if (cfg) { - // Watch this file - watchConfigFile(resolved); - // Update watchers for wildcard directories watchWildCardDirectories(resolved, cfg); @@ -879,7 +879,11 @@ namespace ts { // TODO:: handle this in better way later const proj = parseConfigFile(resolved); - if (!proj) return; // ? + if (!proj) { + reportParseConfigFileDiagnostic(resolved); + return; + } + if (reloadLevel === ConfigFileProgramReloadLevel.Full) { watchConfigFile(resolved); watchWildCardDirectories(resolved, proj); @@ -954,6 +958,11 @@ namespace ts { } } + function reportParseConfigFileDiagnostic(proj: ResolvedConfigFileName) { + host.reportDiagnostic(configFileCache.getValue(proj) as Diagnostic); + storeErrorSummary(proj, 1); + } + function buildSingleProject(proj: ResolvedConfigFileName): BuildResultFlags { if (options.dry) { reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj); @@ -969,8 +978,7 @@ namespace ts { if (!configFile) { // Failed to read the config file resultFlags |= BuildResultFlags.ConfigFileErrors; - host.reportDiagnostic(configFileCache.getValue(proj) as Diagnostic); - storeErrorSummary(proj, 1); + reportParseConfigFileDiagnostic(proj); projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" }); return resultFlags; } @@ -995,10 +1003,7 @@ namespace ts { ...program.getSyntacticDiagnostics()]; if (syntaxDiagnostics.length) { resultFlags |= BuildResultFlags.SyntaxErrors; - for (const diag of syntaxDiagnostics) { - host.reportDiagnostic(diag); - } - storeErrors(proj, syntaxDiagnostics); + reportErrors(proj, syntaxDiagnostics); projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Syntactic errors" }); return resultFlags; } @@ -1008,10 +1013,7 @@ namespace ts { const declDiagnostics = program.getDeclarationDiagnostics(); if (declDiagnostics.length) { resultFlags |= BuildResultFlags.DeclarationEmitErrors; - for (const diag of declDiagnostics) { - host.reportDiagnostic(diag); - } - storeErrors(proj, declDiagnostics); + reportErrors(proj, declDiagnostics); projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Declaration file errors" }); return resultFlags; } @@ -1021,10 +1023,7 @@ namespace ts { const semanticDiagnostics = program.getSemanticDiagnostics(); if (semanticDiagnostics.length) { resultFlags |= BuildResultFlags.TypeErrors; - for (const diag of semanticDiagnostics) { - host.reportDiagnostic(diag); - } - storeErrors(proj, semanticDiagnostics); + reportErrors(proj, semanticDiagnostics); projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Semantic errors" }); return resultFlags; } @@ -1091,6 +1090,7 @@ namespace ts { const parsed = parseConfigFile(proj); if (parsed === undefined) { // File has gone missing; fine to ignore here + reportParseConfigFileDiagnostic(proj); continue; } const outputs = getAllProjectOutputs(parsed); @@ -1133,6 +1133,7 @@ namespace ts { for (const next of graph.buildQueue) { const proj = parseConfigFile(next); if (proj === undefined) { + reportParseConfigFileDiagnostic(next); anyFailed = true; break; } @@ -1144,7 +1145,7 @@ namespace ts { const projName = proj.options.configFilePath!; if (status.type === UpToDateStatusType.UpToDate && !options.force) { - reportErrors(errors); + reportErrors(next, errors); // Up to date, skip if (defaultOptions.dry) { // In a dry build, inform the user of this fact @@ -1154,20 +1155,20 @@ namespace ts { } if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !options.force) { - reportErrors(errors); + reportErrors(next, errors); // Fake build updateOutputTimestamps(proj); continue; } if (status.type === UpToDateStatusType.UpstreamBlocked) { - reportErrors(errors); + reportErrors(next, errors); if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, projName, status.upstreamProjectName); continue; } if (status.type === UpToDateStatusType.ContainerOnly) { - reportErrors(errors); + reportErrors(next, errors); // Do nothing continue; } @@ -1179,8 +1180,9 @@ namespace ts { return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success; } - function reportErrors(errors: Diagnostic[]) { - errors.forEach((err) => host.reportDiagnostic(err)); + function reportErrors(proj: ResolvedConfigFileName, errors: ReadonlyArray) { + errors.forEach(err => host.reportDiagnostic(err)); + storeErrors(proj, errors); } /** diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 006facc0a39..e2abd2a0d04 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -98,9 +98,7 @@ namespace ts.tscWatch { function createSolutionInWatchMode() { const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]); - checkWatchedFiles(host, testProjectExpectedWatchedFiles); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectories(host, [projectPath(SubProject.core), projectPath(SubProject.logic)], /*recursive*/ true); + verifyWatches(host); checkOutputErrorsInitial(host, emptyArray); const outputFileStamps = getOutputFileStamps(host); for (const stamp of outputFileStamps) { @@ -108,6 +106,13 @@ namespace ts.tscWatch { } return host; } + + function verifyWatches(host: WatchedSystem) { + checkWatchedFiles(host, testProjectExpectedWatchedFiles); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, [projectPath(SubProject.core), projectPath(SubProject.logic)], /*recursive*/ true); + } + it("creates solution in watch mode", () => { createSolutionInWatchMode(); }); @@ -197,6 +202,47 @@ export class someClass2 { }`); }); + it("watches config files that are not present", () => { + const allFiles = [libFile, ...core, logic[1], ...tests]; + const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); + createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]); + checkWatchedFiles(host, [core[0], core[1], core[2], logic[0], ...tests].map(f => f.path)); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectories(host, [projectPath(SubProject.core)], /*recursive*/ true); + checkOutputErrorsInitial(host, [ + createCompilerDiagnostic(Diagnostics.File_0_not_found, logic[0].path) + ]); + for (const f of [ + ...getOutputFileNames(SubProject.core, "anotherModule"), + ...getOutputFileNames(SubProject.core, "index") + ]) { + assert.isTrue(host.fileExists(f), `${f} expected to be present`); + } + for (const f of [ + ...getOutputFileNames(SubProject.logic, "index"), + ...getOutputFileNames(SubProject.tests, "index") + ]) { + assert.isFalse(host.fileExists(f), `${f} expected to be absent`); + } + + // Create tsconfig file for logic and see that build succeeds + const initial = getOutputFileStamps(host); + host.writeFile(logic[0].path, logic[0].content); + host.checkTimeoutQueueLengthAndRun(1); // Builds logic + const changedLogic = getOutputFileStamps(host); + verifyChangedFiles(changedLogic, initial, [ + ...getOutputFileNames(SubProject.logic, "index") + ]); + host.checkTimeoutQueueLengthAndRun(1); // Builds tests + const changedTests = getOutputFileStamps(host); + verifyChangedFiles(changedTests, changedLogic, [ + ...getOutputFileNames(SubProject.tests, "index") + ]); + host.checkTimeoutQueueLength(0); + checkOutputErrorsIncremental(host, emptyArray); + verifyWatches(host); + }); + // TODO: write tests reporting errors but that will have more involved work since file }); }