Skip to content

Commit

Permalink
Cherry-pick PR #40880 into release-4.0 (#41107)
Browse files Browse the repository at this point in the history
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

Co-authored-by: Sheetal Nandi <[email protected]>
  • Loading branch information
TypeScript Bot and sheetalkamat authored Oct 15, 2020
1 parent 91628ac commit 60ad73d
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 23 deletions.
73 changes: 50 additions & 23 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];

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
};
}
const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken);
if (result) return result;

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

Expand Down
7 changes: 7 additions & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,7 @@ namespace ts {
getOptionsDiagnostics,
getGlobalDiagnostics,
getSemanticDiagnostics,
getCachedSemanticDiagnostics,
getSuggestionDiagnostics,
getDeclarationDiagnostics,
getBindAndCheckDiagnostics,
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
115 changes: 115 additions & 0 deletions src/testRunner/unittests/tscWatch/watchApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
});
}

0 comments on commit 60ad73d

Please sign in to comment.