Skip to content

Commit 90cfbae

Browse files
authored
Make build info tolerant to json errors (microsoft#50265)
* Make build info tolerant to json errors Fixes microsoft#49754 * Fix incorrect code
1 parent 8a24fe7 commit 90cfbae

File tree

17 files changed

+521
-40
lines changed

17 files changed

+521
-40
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5398,6 +5398,10 @@
53985398
"category": "Message",
53995399
"code": 6400
54005400
},
5401+
"Project '{0}' is out of date because there was error reading file '{1}'": {
5402+
"category": "Message",
5403+
"code": 6401
5404+
},
54015405

54025406
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
54035407
"category": "Message",

src/compiler/emitter.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -652,8 +652,8 @@ namespace ts {
652652
}
653653

654654
/*@internal*/
655-
export function getBuildInfo(buildInfoText: string) {
656-
return JSON.parse(buildInfoText) as BuildInfo;
655+
export function getBuildInfo(buildInfoFile: string, buildInfoText: string) {
656+
return readJsonOrUndefined(buildInfoFile, buildInfoText) as BuildInfo | undefined;
657657
}
658658

659659
/*@internal*/
@@ -751,18 +751,17 @@ namespace ts {
751751
): EmitUsingBuildInfoResult {
752752
const createHash = maybeBind(host, host.createHash);
753753
const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false);
754-
let buildInfo: BuildInfo;
754+
let buildInfo: BuildInfo | undefined;
755755
if (host.getBuildInfo) {
756756
// If host directly provides buildinfo we can get it directly. This allows host to cache the buildinfo
757-
const hostBuildInfo = host.getBuildInfo(buildInfoPath!, config.options.configFilePath);
758-
if (!hostBuildInfo) return buildInfoPath!;
759-
buildInfo = hostBuildInfo;
757+
buildInfo = host.getBuildInfo(buildInfoPath!, config.options.configFilePath);
760758
}
761759
else {
762760
const buildInfoText = host.readFile(buildInfoPath!);
763761
if (!buildInfoText) return buildInfoPath!;
764-
buildInfo = getBuildInfo(buildInfoText);
762+
buildInfo = getBuildInfo(buildInfoPath!, buildInfoText);
765763
}
764+
if (!buildInfo) return buildInfoPath!;
766765
if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationFilePath && !buildInfo.bundle.dts)) return buildInfoPath!;
767766

768767
const jsFileText = host.readFile(Debug.checkDefined(jsFilePath));
@@ -805,7 +804,7 @@ namespace ts {
805804
const emitHost: EmitHost = {
806805
getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]),
807806
getCanonicalFileName: host.getCanonicalFileName,
808-
getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory),
807+
getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo!.bundle!.commonSourceDirectory, buildInfoDirectory),
809808
getCompilerOptions: () => config.options,
810809
getCurrentDirectory: () => host.getCurrentDirectory(),
811810
getNewLine: () => host.getNewLine(),
@@ -827,13 +826,13 @@ namespace ts {
827826
break;
828827
case buildInfoPath:
829828
const newBuildInfo = data!.buildInfo!;
830-
newBuildInfo.program = buildInfo.program;
829+
newBuildInfo.program = buildInfo!.program;
831830
if (newBuildInfo.program && changedDtsText !== undefined && config.options.composite) {
832831
// Update the output signature
833832
(newBuildInfo.program as ProgramBundleEmitBuildInfo).outSignature = computeSignature(changedDtsText, createHash, changedDtsData);
834833
}
835834
// Update sourceFileInfo
836-
const { js, dts, sourceFiles } = buildInfo.bundle!;
835+
const { js, dts, sourceFiles } = buildInfo!.bundle!;
837836
newBuildInfo.bundle!.js!.sources = js!.sources;
838837
if (dts) {
839838
newBuildInfo.bundle!.dts!.sources = dts.sources;

src/compiler/factory/nodeFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6734,7 +6734,7 @@ namespace ts {
67346734
const getAndCacheBuildInfo = (getText: () => string | undefined) => {
67356735
if (buildInfo === undefined) {
67366736
const result = getText();
6737-
buildInfo = result !== undefined ? getBuildInfo(result) : false;
6737+
buildInfo = result !== undefined ? getBuildInfo(node.buildInfoPath!, result) ?? false : false;
67386738
}
67396739
return buildInfo || undefined;
67406740
};

src/compiler/tsbuild.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace ts {
1616
*/
1717
OutOfDateWithPrepend,
1818
OutputMissing,
19+
ErrorReadingFile,
1920
OutOfDateWithSelf,
2021
OutOfDateWithUpstream,
2122
OutOfDateBuildInfo,
@@ -37,6 +38,7 @@ namespace ts {
3738
| Status.UpToDate
3839
| Status.OutOfDateWithPrepend
3940
| Status.OutputMissing
41+
| Status.ErrorReadingFile
4042
| Status.OutOfDateWithSelf
4143
| Status.OutOfDateWithUpstream
4244
| Status.OutOfDateBuildInfo
@@ -95,6 +97,12 @@ namespace ts {
9597
missingOutputFileName: string;
9698
}
9799

100+
/** Error reading file */
101+
export interface ErrorReadingFile {
102+
type: UpToDateStatusType.ErrorReadingFile;
103+
fileName: string;
104+
}
105+
98106
/**
99107
* One or more of the project's outputs is older than its newest input.
100108
*/

src/compiler/tsbuildPublic.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,11 +1110,11 @@ namespace ts {
11101110
const emitterDiagnostics = createDiagnosticCollection();
11111111
const emittedOutputs = new Map<Path, string>();
11121112
let resultFlags = BuildResultFlags.DeclarationOutputUnchanged;
1113-
const existingBuildInfo = state.buildInfoCache.get(projectPath)!.buildInfo as BuildInfo;
1113+
const existingBuildInfo = state.buildInfoCache.get(projectPath)!.buildInfo || undefined;
11141114
outputFiles.forEach(({ name, text, writeByteOrderMark, buildInfo }) => {
11151115
emittedOutputs.set(toPath(state, name), name);
11161116
if (buildInfo) {
1117-
if ((buildInfo.program as ProgramBundleEmitBuildInfo)?.outSignature !== (existingBuildInfo.program as ProgramBundleEmitBuildInfo)?.outSignature) {
1117+
if ((buildInfo.program as ProgramBundleEmitBuildInfo)?.outSignature !== (existingBuildInfo?.program as ProgramBundleEmitBuildInfo)?.outSignature) {
11181118
resultFlags &= ~BuildResultFlags.DeclarationOutputUnchanged;
11191119
}
11201120
setBuildInfo(state, buildInfo, projectPath, config.options, resultFlags);
@@ -1496,8 +1496,7 @@ namespace ts {
14961496
return existing.buildInfo || undefined;
14971497
}
14981498
const value = state.readFileWithCache(buildInfoPath);
1499-
const buildInfo = value ? ts.getBuildInfo(value) : undefined;
1500-
Debug.assert(modifiedTime || !buildInfo);
1499+
const buildInfo = value ? ts.getBuildInfo(buildInfoPath, value) : undefined;
15011500
state.buildInfoCache.set(resolvedConfigPath, { path, buildInfo: buildInfo || false, modifiedTime: modifiedTime || missingFileModifiedTime });
15021501
return buildInfo;
15031502
}
@@ -1587,7 +1586,14 @@ namespace ts {
15871586
};
15881587
}
15891588

1590-
const buildInfo = Debug.checkDefined(getBuildInfo(state, buildInfoPath, resolvedPath, buildInfoTime));
1589+
const buildInfo = getBuildInfo(state, buildInfoPath, resolvedPath, buildInfoTime);
1590+
if (!buildInfo) {
1591+
// Error reading buildInfo
1592+
return {
1593+
type: UpToDateStatusType.ErrorReadingFile,
1594+
fileName: buildInfoPath
1595+
};
1596+
}
15911597
if ((buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) {
15921598
return {
15931599
type: UpToDateStatusType.TsVersionOutputOfDate,
@@ -2344,6 +2350,13 @@ namespace ts {
23442350
relName(state, configFileName),
23452351
relName(state, status.missingOutputFileName)
23462352
);
2353+
case UpToDateStatusType.ErrorReadingFile:
2354+
return reportStatus(
2355+
state,
2356+
Diagnostics.Project_0_is_out_of_date_because_there_was_error_reading_file_1,
2357+
relName(state, configFileName),
2358+
relName(state, status.fileName)
2359+
);
23472360
case UpToDateStatusType.OutOfDateBuildInfo:
23482361
return reportStatus(
23492362
state,

src/compiler/utilities.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5360,20 +5360,16 @@ namespace ts {
53605360
return getStringFromExpandedCharCodes(expandedCharCodes);
53615361
}
53625362

5363+
export function readJsonOrUndefined(path: string, hostOrText: { readFile(fileName: string): string | undefined } | string): object | undefined {
5364+
const jsonText = isString(hostOrText) ? hostOrText : hostOrText.readFile(path);
5365+
if (!jsonText) return undefined;
5366+
// gracefully handle if readFile fails or returns not JSON
5367+
const result = parseConfigFileTextToJson(path, jsonText);
5368+
return !result.error ? result.config : undefined;
5369+
}
5370+
53635371
export function readJson(path: string, host: { readFile(fileName: string): string | undefined }): object {
5364-
try {
5365-
const jsonText = host.readFile(path);
5366-
if (!jsonText) return {};
5367-
const result = parseConfigFileTextToJson(path, jsonText);
5368-
if (result.error) {
5369-
return {};
5370-
}
5371-
return result.config;
5372-
}
5373-
catch (e) {
5374-
// gracefully handle if readFile fails or returns not JSON
5375-
return {};
5376-
}
5372+
return readJsonOrUndefined(path, host) || {};
53775373
}
53785374

53795375
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {

src/compiler/watchPublic.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@ namespace ts {
1313
if (host.getBuildInfo) {
1414
// host provides buildinfo, get it from there. This allows host to cache it
1515
buildInfo = host.getBuildInfo(buildInfoPath, compilerOptions.configFilePath);
16-
if (!buildInfo) return undefined;
1716
}
1817
else {
1918
const content = host.readFile(buildInfoPath);
2019
if (!content) return undefined;
21-
buildInfo = getBuildInfo(content);
20+
buildInfo = getBuildInfo(buildInfoPath, content);
2221
}
23-
if (buildInfo.version !== version) return undefined;
24-
if (!buildInfo.program) return undefined;
22+
if (!buildInfo || buildInfo.version !== version || !buildInfo.program) return undefined;
2523
return createBuilderProgramUsingProgramBuildInfo(buildInfo.program, buildInfoPath, host);
2624
}
2725

src/harness/fakesHosts.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,8 @@ ${indentText}${text}`;
506506
sys.readFile = (path, encoding) => {
507507
const value = originalReadFile.call(sys, path, encoding);
508508
if (!value || !ts.isBuildInfoFile(path)) return value;
509-
const buildInfo = ts.getBuildInfo(value);
509+
const buildInfo = ts.getBuildInfo(path, value);
510+
if (!buildInfo) return value;
510511
ts.Debug.assert(buildInfo.version === version);
511512
buildInfo.version = ts.version;
512513
return ts.getBuildInfoText(buildInfo);
@@ -519,10 +520,14 @@ ${indentText}${text}`;
519520
sys.write = msg => originalWrite.call(sys, msg.replace(ts.version, version));
520521
const originalWriteFile = sys.writeFile;
521522
sys.writeFile = (fileName: string, content: string, writeByteOrderMark: boolean) => {
522-
if (!ts.isBuildInfoFile(fileName)) return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
523-
const buildInfo = ts.getBuildInfo(content);
524-
buildInfo.version = version;
525-
originalWriteFile.call(sys, fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
523+
if (ts.isBuildInfoFile(fileName)) {
524+
const buildInfo = ts.getBuildInfo(fileName, content);
525+
if (buildInfo) {
526+
buildInfo.version = version;
527+
return originalWriteFile.call(sys, fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
528+
}
529+
}
530+
return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
526531
};
527532
return sys;
528533
}

src/testRunner/unittests/tsbuild/helpers.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace ts {
1212
host.readFile = path => {
1313
const value = originalReadFile.call(host, path);
1414
if (!value || !isBuildInfoFile(path)) return value;
15-
const buildInfo = getBuildInfo(value);
15+
const buildInfo = getBuildInfo(path, value);
16+
if (!buildInfo) return value;
1617
buildInfo.version = fakes.version;
1718
return getBuildInfoText(buildInfo);
1819
};
@@ -330,7 +331,8 @@ interface Symbol {
330331
if (!buildInfoPath || !sys.writtenFiles!.has(toPathWithSystem(sys, buildInfoPath))) return;
331332
if (!sys.fileExists(buildInfoPath)) return;
332333

333-
const buildInfo = getBuildInfo((originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!);
334+
const buildInfo = getBuildInfo(buildInfoPath, (originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!);
335+
if (!buildInfo) return sys.writeFile(`${buildInfoPath}.baseline.txt`, "Error reading valid buildinfo file");
334336
generateBuildInfoProgramBaseline(sys, buildInfoPath, buildInfo);
335337

336338
if (!outFile(options)) return;

src/testRunner/unittests/tsbuild/sample.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,21 @@ namespace ts {
168168
commandLineArgs: ["--b", "/src/tests", "--verbose", "--force"],
169169
});
170170

171+
verifyTscWithEdits({
172+
scenario: "sample1",
173+
subScenario: "tsbuildinfo has error",
174+
fs: () => loadProjectFromFiles({
175+
"/src/project/main.ts": "export const x = 10;",
176+
"/src/project/tsconfig.json": "{}",
177+
"/src/project/tsconfig.tsbuildinfo": "Some random string",
178+
}),
179+
commandLineArgs: ["--b", "src/project", "-i", "-v"],
180+
edits: [{
181+
subScenario: "tsbuildinfo written has error",
182+
modifyFs: fs => prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"),
183+
}]
184+
});
185+
171186
verifyTscCompileLike(testTscCompileLike, {
172187
scenario: "sample1",
173188
subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version",

src/testRunner/unittests/tsbuildWatch/programUpdates.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,5 +729,18 @@ export function someFn() { }`),
729729
}
730730
]
731731
});
732+
733+
verifyTscWatch({
734+
scenario: "programUpdates",
735+
subScenario: "tsbuildinfo has error",
736+
sys: () => createWatchedSystem({
737+
"/src/project/main.ts": "export const x = 10;",
738+
"/src/project/tsconfig.json": "{}",
739+
"/src/project/tsconfig.tsbuildinfo": "Some random string",
740+
[libFile.path]: libFile.content,
741+
}),
742+
commandLineArgs: ["--b", "src/project", "-i", "-w"],
743+
changes: emptyArray
744+
});
732745
});
733746
}

src/testRunner/unittests/tsc/incremental.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ namespace ts {
7373
edits: noChangeOnlyRuns
7474
});
7575

76+
verifyTscWithEdits({
77+
scenario: "incremental",
78+
subScenario: "tsbuildinfo has error",
79+
fs: () => loadProjectFromFiles({
80+
"/src/project/main.ts": "export const x = 10;",
81+
"/src/project/tsconfig.json": "{}",
82+
"/src/project/tsconfig.tsbuildinfo": "Some random string",
83+
}),
84+
commandLineArgs: ["--p", "src/project", "-i"],
85+
edits: [{
86+
subScenario: "tsbuildinfo written has error",
87+
modifyFs: fs => prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"),
88+
}]
89+
});
90+
7691
describe("with noEmitOnError", () => {
7792
let projFs: vfs.FileSystem;
7893
before(() => {

src/testRunner/unittests/tscWatch/incremental.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,5 +374,18 @@ export const Fragment: unique symbol;
374374
},
375375
});
376376
});
377+
378+
verifyTscWatch({
379+
scenario: "incremental",
380+
subScenario: "tsbuildinfo has error",
381+
sys: () => createWatchedSystem({
382+
"/src/project/main.ts": "export const x = 10;",
383+
"/src/project/tsconfig.json": "{}",
384+
"/src/project/tsconfig.tsbuildinfo": "Some random string",
385+
[libFile.path]: libFile.content,
386+
}),
387+
commandLineArgs: ["--p", "src/project", "-i", "-w"],
388+
changes: emptyArray
389+
});
377390
});
378391
}

0 commit comments

Comments
 (0)