diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 71dcc43482d..53094bf1888 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4044,6 +4044,10 @@ "category": "Message", "code": 6380 }, + "Project '{0}' is out of date because output for it was generated with version '{1}' that differs with current version '{2}'": { + "category": "Message", + "code": 6381 + }, "The expected type comes from property '{0}' which is declared here on type '{1}'": { "category": "Message", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 4acbfe6bfe9..e55e768bab3 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -466,6 +466,11 @@ namespace ts { return JSON.stringify(buildInfo, undefined, 2); } + /*@internal*/ + export function getBuildInfo(buildInfoText: string) { + return JSON.parse(buildInfoText) as BuildInfo; + } + /*@internal*/ export const notImplementedResolver: EmitResolver = { hasGlobalName: notImplemented, @@ -560,7 +565,7 @@ namespace ts { // error if no source map or for now if inline sourcemap if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) return declarationMapPath || "inline sourcemap decoding"; - const buildInfo = JSON.parse(buildInfoText) as BuildInfo; + const buildInfo = getBuildInfo(buildInfoText); if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationText && !buildInfo.bundle.dts)) return buildInfoPath!; const ownPrependInput = createInputFiles( jsFileText, @@ -599,7 +604,7 @@ namespace ts { if (sourceMapText === text) return; break; case buildInfoPath: - const newBuildInfo = JSON.parse(text) as BuildInfo; + const newBuildInfo = getBuildInfo(text); newBuildInfo.program = buildInfo.program; // Update sourceFileInfo const { js, dts, sourceFiles } = buildInfo.bundle!; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 44dce146ab4..a3bffb785a8 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2891,10 +2891,10 @@ namespace ts { return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; }; let buildInfo: BuildInfo | false; - const getBuildInfo = (getText: () => string | undefined) => { + const getAndCacheBuildInfo = (getText: () => string | undefined) => { if (buildInfo === undefined) { const result = getText(); - buildInfo = result !== undefined ? JSON.parse(result) as BuildInfo : false; + buildInfo = result !== undefined ? getBuildInfo(result) : false; } return buildInfo || undefined; }; @@ -2908,7 +2908,7 @@ namespace ts { javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that declarationText: { get() { return definedTextGetter(Debug.assertDefined(javascriptMapTextOrDeclarationPath)); } }, declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that - buildInfo: { get() { return getBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } + buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } }); } else { diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index ffbe7ef114e..1ab990355b8 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -79,6 +79,7 @@ namespace ts { UpstreamOutOfDate, UpstreamBlocked, ComputingUpstream, + TsVersionOutputOfDate, /** * Projects with no outputs (i.e. "solution" files) @@ -96,6 +97,7 @@ namespace ts { | Status.UpstreamOutOfDate | Status.UpstreamBlocked | Status.ComputingUpstream + | Status.TsVersionOutOfDate | Status.ContainerOnly; export namespace Status { @@ -181,6 +183,11 @@ namespace ts { type: UpToDateStatusType.ComputingUpstream; } + export interface TsVersionOutOfDate { + type: UpToDateStatusType.TsVersionOutputOfDate; + version: string; + } + /** * One or more of the project's outputs is older than the newest output of * an upstream project. @@ -454,6 +461,8 @@ namespace ts { return result; }; + const buildInfoChecked = createFileMap(toPath); + // Watch state const builderPrograms = createFileMap(toPath); const diagnostics = createFileMap>(toPath); @@ -499,6 +508,7 @@ namespace ts { projectStatus.clear(); missingRoots.clear(); globalDependencyGraph = undefined; + buildInfoChecked.clear(); diagnostics.clear(); projectPendingBuild.clear(); @@ -844,6 +854,21 @@ namespace ts { }; } + if (!buildInfoChecked.hasKey(project.options.configFilePath as ResolvedConfigFileName)) { + buildInfoChecked.setValue(project.options.configFilePath as ResolvedConfigFileName, true); + const buildInfoPath = getOutputPathForBuildInfo(project.options); + if (buildInfoPath) { + const value = readFileWithCache(buildInfoPath); + const buildInfo = value && getBuildInfo(value); + if (buildInfo && buildInfo.version !== version) { + return { + type: UpToDateStatusType.TsVersionOutputOfDate, + version: buildInfo.version + }; + } + } + } + if (usesPrepend && pseudoUpToDate) { return { type: UpToDateStatusType.OutOfDateWithPrepend, @@ -1220,7 +1245,8 @@ namespace ts { if (!buildInfoPath) return undefined; const content = readFileWithCache(buildInfoPath); if (!content) return undefined; - const buildInfo = JSON.parse(content) as BuildInfo; + const buildInfo = getBuildInfo(content); + if (buildInfo.version !== version) return undefined; return buildInfo.program && createBuildProgramUsingProgramBuildInfo(buildInfo.program) as any as T; } @@ -1545,6 +1571,11 @@ namespace ts { return formatMessage(Diagnostics.Failed_to_parse_file_0_Colon_1, relName(configFileName), status.reason); + case UpToDateStatusType.TsVersionOutputOfDate: + return formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, + relName(configFileName), + status.version, + version); case UpToDateStatusType.ContainerOnly: // Don't report status on "solution" projects case UpToDateStatusType.ComputingUpstream: diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index 0bf628d67b7..31c96ac6e73 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -396,7 +396,7 @@ namespace fakes { readFile(path: string) { const value = super.readFile(path); if (!value || !ts.isBuildInfoFile(path)) return value; - const buildInfo = JSON.parse(value) as ts.BuildInfo; + const buildInfo = ts.getBuildInfo(value); if (buildInfo.program) { // Fix lib signatures for (const path of ts.getOwnKeys(buildInfo.program.fileInfos)) { @@ -417,7 +417,7 @@ namespace fakes { public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) { if (!ts.isBuildInfoFile(fileName)) return super.writeFile(fileName, content, writeByteOrderMark); - const buildInfo = JSON.parse(content) as ts.BuildInfo; + const buildInfo = ts.getBuildInfo(content); if (buildInfo.program) { // Fix lib signatures for (const path of ts.getOwnKeys(buildInfo.program.fileInfos)) { diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 496350323fc..9672da1ff79 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -8,7 +8,7 @@ namespace ts { host.readFile = path => { const value = originalReadFile.call(host, path); if (!value || !isBuildInfoFile(path)) return value; - const buildInfo = JSON.parse(value) as BuildInfo; + const buildInfo = getBuildInfo(value); buildInfo.version = fakes.version; return getBuildInfoText(buildInfo); }; @@ -101,7 +101,7 @@ namespace ts { for (const [file, jsFile, dtsFile] of buildInfoFileNames) { if (!fs.existsSync(file)) continue; - const buildInfo = JSON.parse(fs.readFileSync(file, "utf8")) as BuildInfo; + const buildInfo = getBuildInfo(fs.readFileSync(file, "utf8")); const bundle = buildInfo.bundle; if (!bundle || (!length(bundle.js && bundle.js.sections) && !length(bundle.dts && bundle.dts.sections))) continue; diff --git a/src/testRunner/unittests/tsbuild/outFile.ts b/src/testRunner/unittests/tsbuild/outFile.ts index 154e42a5be8..e9b7cdae326 100644 --- a/src/testRunner/unittests/tsbuild/outFile.ts +++ b/src/testRunner/unittests/tsbuild/outFile.ts @@ -451,9 +451,12 @@ namespace ts { host.assertDiagnosticMessages( // TODO:: This should build all instead getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]), - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[project.first][source.config], relSources[project.first][source.ts][part.one], relOutputFiles[project.first][ext.js]], - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[project.second][source.config], relSources[project.second][source.ts][part.one], relOutputFiles[project.second][ext.js]], - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[project.third][source.config], relSources[project.third][source.ts][part.one], relOutputFiles[project.third][ext.js]], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[project.first][source.config], fakes.version, version], + [Diagnostics.Building_project_0, sources[project.first][source.config]], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[project.second][source.config], fakes.version, version], + [Diagnostics.Building_project_0, sources[project.second][source.config]], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[project.third][source.config], fakes.version, version], + [Diagnostics.Building_project_0, sources[project.third][source.config]], ); }); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index c827f4499c2..c225840a564 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -245,11 +245,13 @@ namespace ts { changeCompilerVersion(host); builder.buildAllProjects(); host.assertDiagnosticMessages( - // TODO:: This should build all instead getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"], - [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/tests/tsconfig.json", "src/tests/index.ts", "src/tests/index.js"] + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/core/tsconfig.json", fakes.version, version], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/logic/tsconfig.json", fakes.version, version], + [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/tests/tsconfig.json", fakes.version, version], + [Diagnostics.Building_project_0, "/src/tests/tsconfig.json"], ); }); }); @@ -508,6 +510,8 @@ class someClass { }`), // build info "/src/core/tsconfig.tsbuildinfo", + "/src/logic/tsconfig.tsbuildinfo", + "/src/tests/tsconfig.tsbuildinfo", ], ) }, @@ -571,6 +575,7 @@ class someClass { }`), "/src/logic/decls/index.d.ts", // build info + "/src/core/tsconfig.tsbuildinfo", "/src/logic/tsconfig.tsbuildinfo", "/src/tests/tsconfig.tsbuildinfo",