Skip to content

Commit 372f8ee

Browse files
sheetalkamatsandersn
authored andcommitted
Cherry-pick PR microsoft#40880 into release-4.0
Component commits: ca35546 Add test that fails c7b5005 Handle noEmit on semantic builder's emit as well 6a05abd Add test for tsbuildinfo text verification 8bae521 Fix noEmit handling for tsbuildinfo emit with SemanticDiagnosticBuilder 0fd4a08 Add test for noEmitOnError with SemanticDiagnosticsBuilder 4fa1d78 Fix tsbuildinfo emit with SemanticDiagnosticsBuilder on noEmitOnError a3968e7 Update src/compiler/builder.ts Co-authored-by: Nathan Shively-Sanders <[email protected]> 174b00a Update src/compiler/builder.ts
1 parent 91628ac commit 372f8ee

File tree

4 files changed

+174
-23
lines changed

4 files changed

+174
-23
lines changed

src/compiler/builder.ts

+50-23
Original file line numberDiff line numberDiff line change
@@ -1019,31 +1019,57 @@ namespace ts {
10191019
* in that order would be used to write the files
10201020
*/
10211021
function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult {
1022+
let restorePendingEmitOnHandlingNoEmitSuccess = false;
1023+
let savedAffectedFilesPendingEmit;
1024+
let savedAffectedFilesPendingEmitKind;
1025+
let savedAffectedFilesPendingEmitIndex;
1026+
// Backup and restore affected pendings emit state for non emit Builder if noEmitOnError is enabled and emitBuildInfo could be written in case there are errors
1027+
// This ensures pending files to emit is updated in tsbuildinfo
1028+
// Note that when there are no errors, emit proceeds as if everything is emitted as it is callers reponsibility to write the files to disk if at all (because its builder that doesnt track files to emit)
1029+
if (kind !== BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram &&
1030+
!targetSourceFile &&
1031+
!outFile(state.compilerOptions) &&
1032+
!state.compilerOptions.noEmit &&
1033+
state.compilerOptions.noEmitOnError) {
1034+
restorePendingEmitOnHandlingNoEmitSuccess = true;
1035+
savedAffectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice();
1036+
savedAffectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind);
1037+
savedAffectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex;
1038+
}
1039+
10221040
if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
10231041
assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile);
1024-
const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken);
1025-
if (result) return result;
1026-
if (!targetSourceFile) {
1027-
// Emit and report any errors we ran into.
1028-
let sourceMaps: SourceMapEmitResult[] = [];
1029-
let emitSkipped = false;
1030-
let diagnostics: Diagnostic[] | undefined;
1031-
let emittedFiles: string[] = [];
1032-
1033-
let affectedEmitResult: AffectedFileResult<EmitResult>;
1034-
while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) {
1035-
emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped;
1036-
diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics);
1037-
emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles);
1038-
sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps);
1039-
}
1040-
return {
1041-
emitSkipped,
1042-
diagnostics: diagnostics || emptyArray,
1043-
emittedFiles,
1044-
sourceMaps
1045-
};
1042+
}
1043+
const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken);
1044+
if (result) return result;
1045+
1046+
if (restorePendingEmitOnHandlingNoEmitSuccess) {
1047+
state.affectedFilesPendingEmit = savedAffectedFilesPendingEmit;
1048+
state.affectedFilesPendingEmitKind = savedAffectedFilesPendingEmitKind;
1049+
state.affectedFilesPendingEmitIndex = savedAffectedFilesPendingEmitIndex;
1050+
}
1051+
1052+
// Emit only affected files if using builder for emit
1053+
if (!targetSourceFile && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
1054+
// Emit and report any errors we ran into.
1055+
let sourceMaps: SourceMapEmitResult[] = [];
1056+
let emitSkipped = false;
1057+
let diagnostics: Diagnostic[] | undefined;
1058+
let emittedFiles: string[] = [];
1059+
1060+
let affectedEmitResult: AffectedFileResult<EmitResult>;
1061+
while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) {
1062+
emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped;
1063+
diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics);
1064+
emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles);
1065+
sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps);
10461066
}
1067+
return {
1068+
emitSkipped,
1069+
diagnostics: diagnostics || emptyArray,
1070+
emittedFiles,
1071+
sourceMaps
1072+
};
10471073
}
10481074
return Debug.checkDefined(state.program).emit(targetSourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers);
10491075
}
@@ -1069,7 +1095,8 @@ namespace ts {
10691095
}
10701096

10711097
// Add file to affected file pending emit to handle for later emit time
1072-
if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
1098+
// Apart for emit builder do this for tsbuildinfo, do this for non emit builder when noEmit is set as tsbuildinfo is written and reused between emitters
1099+
if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram || state.compilerOptions.noEmit || state.compilerOptions.noEmitOnError) {
10731100
addToAffectedFilesPendingEmit(state, (affected as SourceFile).resolvedPath, BuilderFileEmit.Full);
10741101
}
10751102

src/compiler/program.ts

+7
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,7 @@ namespace ts {
935935
getOptionsDiagnostics,
936936
getGlobalDiagnostics,
937937
getSemanticDiagnostics,
938+
getCachedSemanticDiagnostics,
938939
getSuggestionDiagnostics,
939940
getDeclarationDiagnostics,
940941
getBindAndCheckDiagnostics,
@@ -1648,6 +1649,12 @@ namespace ts {
16481649
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken);
16491650
}
16501651

1652+
function getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined {
1653+
return sourceFile
1654+
? cachedBindAndCheckDiagnosticsForFile.perFile?.get(sourceFile.path)
1655+
: cachedBindAndCheckDiagnosticsForFile.allDiagnostics;
1656+
}
1657+
16511658
function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
16521659
return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken);
16531660
}

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3727,6 +3727,8 @@ namespace ts {
37273727
/* @internal */ getDiagnosticsProducingTypeChecker(): TypeChecker;
37283728
/* @internal */ dropDiagnosticsProducingTypeChecker(): void;
37293729

3730+
/* @internal */ getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined;
3731+
37303732
/* @internal */ getClassifiableNames(): Set<__String>;
37313733

37323734
getNodeCount(): number;

src/testRunner/unittests/tscWatch/watchApi.ts

+115
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,119 @@ namespace ts.tscWatch {
123123
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]);
124124
});
125125
});
126+
127+
describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => {
128+
function getWatch<T extends BuilderProgram>(config: File, optionsToExtend: CompilerOptions | undefined, sys: System, createProgram: CreateProgram<T>) {
129+
const watchCompilerHost = createWatchCompilerHost(config.path, optionsToExtend, sys, createProgram);
130+
return createWatchProgram(watchCompilerHost);
131+
}
132+
133+
function setup<T extends BuilderProgram>(createProgram: CreateProgram<T>, configText: string) {
134+
const config: File = {
135+
path: `${projectRoot}/tsconfig.json`,
136+
content: configText
137+
};
138+
const mainFile: File = {
139+
path: `${projectRoot}/main.ts`,
140+
content: "export const x = 10;"
141+
};
142+
const otherFile: File = {
143+
path: `${projectRoot}/other.ts`,
144+
content: "export const y = 10;"
145+
};
146+
const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
147+
const watch = getWatch(config, { noEmit: true }, sys, createProgram);
148+
return { sys, watch, mainFile, otherFile, config };
149+
}
150+
151+
function verifyOutputs(sys: System, emitSys: System) {
152+
for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) {
153+
assert.strictEqual(sys.readFile(output), emitSys.readFile(output), `Output file text for ${output}`);
154+
}
155+
}
156+
157+
function verifyBuilder<T extends BuilderProgram, U extends BuilderProgram>(config: File, sys: System, emitSys: System, createProgram: CreateProgram<T>, createEmitProgram: CreateProgram<U>, optionsToExtend?: CompilerOptions) {
158+
const watch = getWatch(config, /*optionsToExtend*/ optionsToExtend, sys, createProgram);
159+
const emitWatch = getWatch(config, /*optionsToExtend*/ optionsToExtend, emitSys, createEmitProgram);
160+
verifyOutputs(sys, emitSys);
161+
watch.close();
162+
emitWatch.close();
163+
}
164+
165+
it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => {
166+
const { sys, watch, mainFile, otherFile } = setup(createSemanticDiagnosticsBuilderProgram, "{}");
167+
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]);
168+
sys.appendFile(mainFile.path, "\n// SomeComment");
169+
sys.runQueuedTimeoutCallbacks();
170+
const program = watch.getProgram().getProgram();
171+
assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(mainFile.path)), []);
172+
// Should not retrieve diagnostics for other file thats not changed
173+
assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(otherFile.path)), /*expected*/ undefined);
174+
});
175+
176+
it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => {
177+
const configText = JSON.stringify({ compilerOptions: { composite: true } });
178+
const { sys, watch, config, mainFile } = setup(createSemanticDiagnosticsBuilderProgram, configText);
179+
const { sys: emitSys, watch: emitWatch } = setup(createEmitAndSemanticDiagnosticsBuilderProgram, configText);
180+
verifyOutputs(sys, emitSys);
181+
182+
watch.close();
183+
emitWatch.close();
184+
185+
// Emit on both sys should result in same output
186+
verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
187+
188+
// Change file
189+
sys.appendFile(mainFile.path, "\n// SomeComment");
190+
emitSys.appendFile(mainFile.path, "\n// SomeComment");
191+
192+
// Verify noEmit results in same output
193+
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true });
194+
195+
// Emit on both sys should result in same output
196+
verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
197+
198+
// Change file
199+
sys.appendFile(mainFile.path, "\n// SomeComment");
200+
emitSys.appendFile(mainFile.path, "\n// SomeComment");
201+
202+
// Emit on both the builders should result in same files
203+
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
204+
});
205+
206+
it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => {
207+
const config: File = {
208+
path: `${projectRoot}/tsconfig.json`,
209+
content: JSON.stringify({ compilerOptions: { composite: true } })
210+
};
211+
const mainFile: File = {
212+
path: `${projectRoot}/main.ts`,
213+
content: "export const x: string = 10;"
214+
};
215+
const otherFile: File = {
216+
path: `${projectRoot}/other.ts`,
217+
content: "export const y = 10;"
218+
};
219+
const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
220+
const emitSys = createWatchedSystem([config, mainFile, otherFile, libFile]);
221+
222+
// Verify noEmit results in same output
223+
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
224+
225+
// Change file
226+
sys.appendFile(mainFile.path, "\n// SomeComment");
227+
emitSys.appendFile(mainFile.path, "\n// SomeComment");
228+
229+
// Verify noEmit results in same output
230+
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
231+
232+
// Fix error
233+
const fixed = "export const x = 10;";
234+
sys.appendFile(mainFile.path, fixed);
235+
emitSys.appendFile(mainFile.path, fixed);
236+
237+
// Emit on both the builders should result in same files
238+
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
239+
});
240+
});
126241
}

0 commit comments

Comments
 (0)