Cherry-pick PR #40880 into release-4.0 (#41107)

Component commits:
ca35546663 Add test that fails

c7b5005aca Handle noEmit on semantic builder's emit as well

6a05abdff8 Add test for tsbuildinfo text verification

8bae5210ee Fix noEmit handling for tsbuildinfo emit with SemanticDiagnosticBuilder

0fd4a08b75 Add test for noEmitOnError with SemanticDiagnosticsBuilder

4fa1d78a6c Fix tsbuildinfo emit with SemanticDiagnosticsBuilder on noEmitOnError

a3968e7574 Update src/compiler/builder.ts
Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
174b00a9a6 Update src/compiler/builder.ts

Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
This commit is contained in:
TypeScript Bot
2020-10-14 21:56:04 -04:00
committed by GitHub
parent 91628ac60e
commit 60ad73da1f
4 changed files with 173 additions and 22 deletions

View File

@@ -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);
}

View File

@@ -935,6 +935,7 @@ namespace ts {
getOptionsDiagnostics,
getGlobalDiagnostics,
getSemanticDiagnostics,
getCachedSemanticDiagnostics,
getSuggestionDiagnostics,
getDeclarationDiagnostics,
getBindAndCheckDiagnostics,
@@ -1648,6 +1649,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);
}

View File

@@ -3727,6 +3727,8 @@ namespace ts {
/* @internal */ getDiagnosticsProducingTypeChecker(): TypeChecker;
/* @internal */ dropDiagnosticsProducingTypeChecker(): void;
/* @internal */ getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined;
/* @internal */ getClassifiableNames(): Set<__String>;
getNodeCount(): number;

View File

@@ -123,4 +123,119 @@ namespace ts.tscWatch {
checkProgramActualFiles(watch.getProgram().getProgram(), [mainFile.path, otherFile.path, libFile.path]);
});
});
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 });
});
});
}