mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 02:33:53 -06:00
Handle noEmit and noEmitOnError with SemanticDiagnosticsBuilder (#40880)
* Add test that fails * Handle noEmit on semantic builder's emit as well * Add test for tsbuildinfo text verification * Fix noEmit handling for tsbuildinfo emit with SemanticDiagnosticBuilder * Add test for noEmitOnError with SemanticDiagnosticsBuilder * Fix tsbuildinfo emit with SemanticDiagnosticsBuilder on noEmitOnError * Update src/compiler/builder.ts Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> * Update src/compiler/builder.ts Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
parent
a109b5d5c8
commit
a4b9bbae90
@ -1019,31 +1019,57 @@ namespace ts {
|
||||
* in that order would be used to write the files
|
||||
*/
|
||||
function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult {
|
||||
let restorePendingEmitOnHandlingNoEmitSuccess = false;
|
||||
let savedAffectedFilesPendingEmit;
|
||||
let savedAffectedFilesPendingEmitKind;
|
||||
let savedAffectedFilesPendingEmitIndex;
|
||||
// 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
|
||||
// This ensures pending files to emit is updated in tsbuildinfo
|
||||
// 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)
|
||||
if (kind !== BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram &&
|
||||
!targetSourceFile &&
|
||||
!outFile(state.compilerOptions) &&
|
||||
!state.compilerOptions.noEmit &&
|
||||
state.compilerOptions.noEmitOnError) {
|
||||
restorePendingEmitOnHandlingNoEmitSuccess = true;
|
||||
savedAffectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice();
|
||||
savedAffectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind);
|
||||
savedAffectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex;
|
||||
}
|
||||
|
||||
if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
|
||||
assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile);
|
||||
const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken);
|
||||
if (result) return result;
|
||||
if (!targetSourceFile) {
|
||||
// Emit and report any errors we ran into.
|
||||
let sourceMaps: SourceMapEmitResult[] = [];
|
||||
let emitSkipped = false;
|
||||
let diagnostics: Diagnostic[] | undefined;
|
||||
let emittedFiles: string[] = [];
|
||||
}
|
||||
const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken);
|
||||
if (result) return result;
|
||||
|
||||
let affectedEmitResult: AffectedFileResult<EmitResult>;
|
||||
while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) {
|
||||
emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped;
|
||||
diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics);
|
||||
emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles);
|
||||
sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps);
|
||||
}
|
||||
return {
|
||||
emitSkipped,
|
||||
diagnostics: diagnostics || emptyArray,
|
||||
emittedFiles,
|
||||
sourceMaps
|
||||
};
|
||||
if (restorePendingEmitOnHandlingNoEmitSuccess) {
|
||||
state.affectedFilesPendingEmit = savedAffectedFilesPendingEmit;
|
||||
state.affectedFilesPendingEmitKind = savedAffectedFilesPendingEmitKind;
|
||||
state.affectedFilesPendingEmitIndex = savedAffectedFilesPendingEmitIndex;
|
||||
}
|
||||
|
||||
// Emit only affected files if using builder for emit
|
||||
if (!targetSourceFile && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
|
||||
// Emit and report any errors we ran into.
|
||||
let sourceMaps: SourceMapEmitResult[] = [];
|
||||
let emitSkipped = false;
|
||||
let diagnostics: Diagnostic[] | undefined;
|
||||
let emittedFiles: string[] = [];
|
||||
|
||||
let affectedEmitResult: AffectedFileResult<EmitResult>;
|
||||
while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) {
|
||||
emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped;
|
||||
diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics);
|
||||
emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles);
|
||||
sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps);
|
||||
}
|
||||
return {
|
||||
emitSkipped,
|
||||
diagnostics: diagnostics || emptyArray,
|
||||
emittedFiles,
|
||||
sourceMaps
|
||||
};
|
||||
}
|
||||
return Debug.checkDefined(state.program).emit(targetSourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers);
|
||||
}
|
||||
@ -1069,7 +1095,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
// Add file to affected file pending emit to handle for later emit time
|
||||
if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
|
||||
// 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
|
||||
if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram || state.compilerOptions.noEmit || state.compilerOptions.noEmitOnError) {
|
||||
addToAffectedFilesPendingEmit(state, (affected as SourceFile).resolvedPath, BuilderFileEmit.Full);
|
||||
}
|
||||
|
||||
|
||||
@ -937,6 +937,7 @@ namespace ts {
|
||||
getOptionsDiagnostics,
|
||||
getGlobalDiagnostics,
|
||||
getSemanticDiagnostics,
|
||||
getCachedSemanticDiagnostics,
|
||||
getSuggestionDiagnostics,
|
||||
getDeclarationDiagnostics,
|
||||
getBindAndCheckDiagnostics,
|
||||
@ -1693,6 +1694,12 @@ namespace ts {
|
||||
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken);
|
||||
}
|
||||
|
||||
function getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined {
|
||||
return sourceFile
|
||||
? cachedBindAndCheckDiagnosticsForFile.perFile?.get(sourceFile.path)
|
||||
: cachedBindAndCheckDiagnosticsForFile.allDiagnostics;
|
||||
}
|
||||
|
||||
function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
|
||||
return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken);
|
||||
}
|
||||
|
||||
@ -3762,6 +3762,8 @@ namespace ts {
|
||||
/* @internal */ getDiagnosticsProducingTypeChecker(): TypeChecker;
|
||||
/* @internal */ dropDiagnosticsProducingTypeChecker(): void;
|
||||
|
||||
/* @internal */ getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined;
|
||||
|
||||
/* @internal */ getClassifiableNames(): Set<__String>;
|
||||
|
||||
getTypeCatalog(): readonly Type[];
|
||||
|
||||
@ -128,4 +128,119 @@ namespace ts.tscWatch {
|
||||
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path, other2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => {
|
||||
function getWatch<T extends BuilderProgram>(config: File, optionsToExtend: CompilerOptions | undefined, sys: System, createProgram: CreateProgram<T>) {
|
||||
const watchCompilerHost = createWatchCompilerHost(config.path, optionsToExtend, sys, createProgram);
|
||||
return createWatchProgram(watchCompilerHost);
|
||||
}
|
||||
|
||||
function setup<T extends BuilderProgram>(createProgram: CreateProgram<T>, configText: string) {
|
||||
const config: File = {
|
||||
path: `${projectRoot}/tsconfig.json`,
|
||||
content: configText
|
||||
};
|
||||
const mainFile: File = {
|
||||
path: `${projectRoot}/main.ts`,
|
||||
content: "export const x = 10;"
|
||||
};
|
||||
const otherFile: File = {
|
||||
path: `${projectRoot}/other.ts`,
|
||||
content: "export const y = 10;"
|
||||
};
|
||||
const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
|
||||
const watch = getWatch(config, { noEmit: true }, sys, createProgram);
|
||||
return { sys, watch, mainFile, otherFile, config };
|
||||
}
|
||||
|
||||
function verifyOutputs(sys: System, emitSys: System) {
|
||||
for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) {
|
||||
assert.strictEqual(sys.readFile(output), emitSys.readFile(output), `Output file text for ${output}`);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyBuilder<T extends BuilderProgram, U extends BuilderProgram>(config: File, sys: System, emitSys: System, createProgram: CreateProgram<T>, createEmitProgram: CreateProgram<U>, optionsToExtend?: CompilerOptions) {
|
||||
const watch = getWatch(config, /*optionsToExtend*/ optionsToExtend, sys, createProgram);
|
||||
const emitWatch = getWatch(config, /*optionsToExtend*/ optionsToExtend, emitSys, createEmitProgram);
|
||||
verifyOutputs(sys, emitSys);
|
||||
watch.close();
|
||||
emitWatch.close();
|
||||
}
|
||||
|
||||
it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => {
|
||||
const { sys, watch, mainFile, otherFile } = setup(createSemanticDiagnosticsBuilderProgram, "{}");
|
||||
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]);
|
||||
sys.appendFile(mainFile.path, "\n// SomeComment");
|
||||
sys.runQueuedTimeoutCallbacks();
|
||||
const program = watch.getProgram().getProgram();
|
||||
assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(mainFile.path)), []);
|
||||
// Should not retrieve diagnostics for other file thats not changed
|
||||
assert.deepEqual(program.getCachedSemanticDiagnostics(program.getSourceFile(otherFile.path)), /*expected*/ undefined);
|
||||
});
|
||||
|
||||
it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => {
|
||||
const configText = JSON.stringify({ compilerOptions: { composite: true } });
|
||||
const { sys, watch, config, mainFile } = setup(createSemanticDiagnosticsBuilderProgram, configText);
|
||||
const { sys: emitSys, watch: emitWatch } = setup(createEmitAndSemanticDiagnosticsBuilderProgram, configText);
|
||||
verifyOutputs(sys, emitSys);
|
||||
|
||||
watch.close();
|
||||
emitWatch.close();
|
||||
|
||||
// Emit on both sys should result in same output
|
||||
verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
|
||||
|
||||
// Change file
|
||||
sys.appendFile(mainFile.path, "\n// SomeComment");
|
||||
emitSys.appendFile(mainFile.path, "\n// SomeComment");
|
||||
|
||||
// Verify noEmit results in same output
|
||||
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true });
|
||||
|
||||
// Emit on both sys should result in same output
|
||||
verifyBuilder(config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
|
||||
|
||||
// Change file
|
||||
sys.appendFile(mainFile.path, "\n// SomeComment");
|
||||
emitSys.appendFile(mainFile.path, "\n// SomeComment");
|
||||
|
||||
// Emit on both the builders should result in same files
|
||||
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram);
|
||||
});
|
||||
|
||||
it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => {
|
||||
const config: File = {
|
||||
path: `${projectRoot}/tsconfig.json`,
|
||||
content: JSON.stringify({ compilerOptions: { composite: true } })
|
||||
};
|
||||
const mainFile: File = {
|
||||
path: `${projectRoot}/main.ts`,
|
||||
content: "export const x: string = 10;"
|
||||
};
|
||||
const otherFile: File = {
|
||||
path: `${projectRoot}/other.ts`,
|
||||
content: "export const y = 10;"
|
||||
};
|
||||
const sys = createWatchedSystem([config, mainFile, otherFile, libFile]);
|
||||
const emitSys = createWatchedSystem([config, mainFile, otherFile, libFile]);
|
||||
|
||||
// Verify noEmit results in same output
|
||||
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
|
||||
|
||||
// Change file
|
||||
sys.appendFile(mainFile.path, "\n// SomeComment");
|
||||
emitSys.appendFile(mainFile.path, "\n// SomeComment");
|
||||
|
||||
// Verify noEmit results in same output
|
||||
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
|
||||
|
||||
// Fix error
|
||||
const fixed = "export const x = 10;";
|
||||
sys.appendFile(mainFile.path, fixed);
|
||||
emitSys.appendFile(mainFile.path, fixed);
|
||||
|
||||
// Emit on both the builders should result in same files
|
||||
verifyBuilder(config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmitOnError: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user