Skip to content

Commit b977f86

Browse files
authored
Allows emitting buildInfo when --noEmit is specified (microsoft#39122)
* Some tests * Allow noEmit with incremental and composite Fixes microsoft#38440
1 parent 8683b8a commit b977f86

20 files changed

+6358
-75
lines changed

Diff for: src/compiler/builder.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace ts {
55
reportsUnnecessary?: {};
66
source?: string;
77
relatedInformation?: ReusableDiagnosticRelatedInformation[];
8+
skippedOn?: keyof CompilerOptions;
89
}
910

1011
export interface ReusableDiagnosticRelatedInformation {
@@ -268,6 +269,7 @@ namespace ts {
268269
const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath);
269270
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
270271
result.source = diagnostic.source;
272+
result.skippedOn = diagnostic.skippedOn;
271273
const { relatedInformation } = diagnostic;
272274
result.relatedInformation = relatedInformation ?
273275
relatedInformation.length ?
@@ -676,7 +678,7 @@ namespace ts {
676678
const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path);
677679
// Report the bind and check diagnostics from the cache if we already have those diagnostics present
678680
if (cachedDiagnostics) {
679-
return cachedDiagnostics;
681+
return filterSemanticDiagnotics(cachedDiagnostics, state.compilerOptions);
680682
}
681683
}
682684

@@ -685,7 +687,7 @@ namespace ts {
685687
if (state.semanticDiagnosticsPerFile) {
686688
state.semanticDiagnosticsPerFile.set(path, diagnostics);
687689
}
688-
return diagnostics;
690+
return filterSemanticDiagnotics(diagnostics, state.compilerOptions);
689691
}
690692

691693
export type ProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]];
@@ -816,6 +818,7 @@ namespace ts {
816818
const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo);
817819
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
818820
result.source = diagnostic.source;
821+
result.skippedOn = diagnostic.skippedOn;
819822
const { relatedInformation } = diagnostic;
820823
result.relatedInformation = relatedInformation ?
821824
relatedInformation.length ?

Diff for: src/compiler/checker.ts

+25-10
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,12 @@ namespace ts {
10291029
}
10301030
}
10311031

1032+
function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
1033+
const diagnostic = error(location, message, arg0, arg1, arg2, arg3);
1034+
diagnostic.skippedOn = key;
1035+
return diagnostic;
1036+
}
1037+
10321038
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
10331039
const diagnostic = location
10341040
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
@@ -32057,13 +32063,13 @@ namespace ts {
3205732063

3205832064
function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) {
3205932065
// no rest parameters \ declaration context \ overload - no codegen impact
32060-
if (languageVersion >= ScriptTarget.ES2015 || compilerOptions.noEmit || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((<FunctionLikeDeclaration>node).body)) {
32066+
if (languageVersion >= ScriptTarget.ES2015 || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((<FunctionLikeDeclaration>node).body)) {
3206132067
return;
3206232068
}
3206332069

3206432070
forEach(node.parameters, p => {
3206532071
if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) {
32066-
error(p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters);
32072+
errorSkippedOn("noEmit", p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters);
3206732073
}
3206832074
});
3206932075
}
@@ -32133,13 +32139,13 @@ namespace ts {
3213332139
function checkWeakMapCollision(node: Node) {
3213432140
const enclosingBlockScope = getEnclosingBlockScopeContainer(node);
3213532141
if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) {
32136-
error(node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, "WeakMap");
32142+
errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, "WeakMap");
3213732143
}
3213832144
}
3213932145

3214032146
function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier) {
3214132147
// No need to check for require or exports for ES6 modules and later
32142-
if (moduleKind >= ModuleKind.ES2015 || compilerOptions.noEmit) {
32148+
if (moduleKind >= ModuleKind.ES2015) {
3214332149
return;
3214432150
}
3214532151

@@ -32156,13 +32162,13 @@ namespace ts {
3215632162
const parent = getDeclarationContainer(node);
3215732163
if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>parent)) {
3215832164
// If the declaration happens to be in external module, report error that require and exports are reserved keywords
32159-
error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module,
32165+
errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module,
3216032166
declarationNameToString(name), declarationNameToString(name));
3216132167
}
3216232168
}
3216332169

3216432170
function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void {
32165-
if (languageVersion >= ScriptTarget.ES2017 || compilerOptions.noEmit || !needCollisionCheckForIdentifier(node, name, "Promise")) {
32171+
if (languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) {
3216632172
return;
3216732173
}
3216832174

@@ -32175,7 +32181,7 @@ namespace ts {
3217532181
const parent = getDeclarationContainer(node);
3217632182
if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>parent) && parent.flags & NodeFlags.HasAsyncFunctions) {
3217732183
// If the declaration happens to be in external module, report error that Promise is a reserved identifier.
32178-
error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions,
32184+
errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions,
3217932185
declarationNameToString(name), declarationNameToString(name));
3218032186
}
3218132187
}
@@ -32393,7 +32399,7 @@ namespace ts {
3239332399
}
3239432400
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
3239532401
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
32396-
if (!compilerOptions.noEmit && languageVersion < ScriptTarget.ESNext && needCollisionCheckForIdentifier(node, node.name, "WeakMap")) {
32402+
if (languageVersion < ScriptTarget.ESNext && needCollisionCheckForIdentifier(node, node.name, "WeakMap")) {
3239732403
potentialWeakMapCollisions.push(node);
3239832404
}
3239932405
}
@@ -38316,7 +38322,7 @@ namespace ts {
3831638322

3831738323
const moduleKind = getEmitModuleKind(compilerOptions);
3831838324

38319-
if (moduleKind < ModuleKind.ES2015 && moduleKind !== ModuleKind.System && !compilerOptions.noEmit &&
38325+
if (moduleKind < ModuleKind.ES2015 && moduleKind !== ModuleKind.System &&
3832038326
!(node.parent.parent.flags & NodeFlags.Ambient) && hasSyntacticModifier(node.parent.parent, ModifierFlags.Export)) {
3832138327
checkESModuleMarker(node.name);
3832238328
}
@@ -38336,7 +38342,7 @@ namespace ts {
3833638342
function checkESModuleMarker(name: Identifier | BindingPattern): boolean {
3833738343
if (name.kind === SyntaxKind.Identifier) {
3833838344
if (idText(name) === "__esModule") {
38339-
return grammarErrorOnNode(name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules);
38345+
return grammarErrorOnNodeSkippedOn("noEmit", name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules);
3834038346
}
3834138347
}
3834238348
else {
@@ -38446,6 +38452,15 @@ namespace ts {
3844638452
return false;
3844738453
}
3844838454

38455+
function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
38456+
const sourceFile = getSourceFileOfNode(node);
38457+
if (!hasParseDiagnostics(sourceFile)) {
38458+
errorSkippedOn(key, node, message, arg0, arg1, arg2);
38459+
return true;
38460+
}
38461+
return false;
38462+
}
38463+
3844938464
function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
3845038465
const sourceFile = getSourceFileOfNode(node);
3845138466
if (!hasParseDiagnostics(sourceFile)) {

Diff for: src/compiler/commandLineParser.ts

-1
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,6 @@ namespace ts {
462462
{
463463
name: "noEmit",
464464
type: "boolean",
465-
affectsEmit: true,
466465
showInSimplifiedHelpView: true,
467466
category: Diagnostics.Basic_Options,
468467
description: Diagnostics.Do_not_emit_outputs,

Diff for: src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -4458,6 +4458,10 @@
44584458
"category": "Error",
44594459
"code": 6309
44604460
},
4461+
"Referenced project '{0}' may not disable emit.": {
4462+
"category": "Error",
4463+
"code": 6310
4464+
},
44614465
"Project '{0}' is out of date because oldest output '{1}' is older than newest input '{2}'": {
44624466
"category": "Message",
44634467
"code": 6350

Diff for: src/compiler/emitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ namespace ts {
333333
// Write build information if applicable
334334
if (!buildInfoPath || targetSourceFile || emitSkipped) return;
335335
const program = host.getProgramBuildInfo();
336-
if (host.isEmitBlocked(buildInfoPath) || compilerOptions.noEmit) {
336+
if (host.isEmitBlocked(buildInfoPath)) {
337337
emitSkipped = true;
338338
return;
339339
}

Diff for: src/compiler/program.ts

+17-9
Original file line numberDiff line numberDiff line change
@@ -1719,7 +1719,7 @@ namespace ts {
17191719

17201720
function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] {
17211721
return concatenate(
1722-
getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken),
1722+
filterSemanticDiagnotics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options),
17231723
getProgramDiagnostics(sourceFile)
17241724
);
17251725
}
@@ -3011,10 +3011,6 @@ namespace ts {
30113011
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified));
30123012
}
30133013

3014-
if (!options.listFilesOnly && options.noEmit && isIncrementalCompilation(options)) {
3015-
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmit", options.incremental ? "incremental" : "composite");
3016-
}
3017-
30183014
verifyProjectReferences();
30193015

30203016
// List of collected files is complete; validate exhautiveness if this is a project with a file list
@@ -3278,7 +3274,7 @@ namespace ts {
32783274
}
32793275

32803276
function verifyProjectReferences() {
3281-
const buildInfoPath = !options.noEmit && !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined;
3277+
const buildInfoPath = !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined;
32823278
forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => {
32833279
const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index];
32843280
const parentFile = parent && parent.sourceFile as JsonSourceFile;
@@ -3287,11 +3283,12 @@ namespace ts {
32873283
return;
32883284
}
32893285
const options = resolvedRef.commandLine.options;
3290-
if (!options.composite) {
3286+
if (!options.composite || options.noEmit) {
32913287
// ok to not have composite if the current program is container only
32923288
const inputs = parent ? parent.commandLine.fileNames : rootNames;
32933289
if (inputs.length) {
3294-
createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path);
3290+
if (!options.composite) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path);
3291+
if (options.noEmit) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_may_not_disable_emit, ref.path);
32953292
}
32963293
}
32973294
if (ref.prepend) {
@@ -3657,7 +3654,13 @@ namespace ts {
36573654
cancellationToken: CancellationToken | undefined
36583655
): EmitResult | undefined {
36593656
const options = program.getCompilerOptions();
3660-
if (options.noEmit) return emitSkippedWithNoDiagnostics;
3657+
if (options.noEmit) {
3658+
// Cache the semantic diagnostics
3659+
program.getSemanticDiagnostics(sourceFile, cancellationToken);
3660+
return sourceFile || outFile(options) ?
3661+
emitSkippedWithNoDiagnostics :
3662+
program.emitBuildInfo(writeFile, cancellationToken);
3663+
}
36613664

36623665
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
36633666
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
@@ -3684,6 +3687,11 @@ namespace ts {
36843687
return { diagnostics, sourceMaps: undefined, emittedFiles, emitSkipped: true };
36853688
}
36863689

3690+
/*@internal*/
3691+
export function filterSemanticDiagnotics(diagnostic: readonly Diagnostic[], option: CompilerOptions): readonly Diagnostic[] {
3692+
return filter(diagnostic, d => !d.skippedOn || !option[d.skippedOn]);
3693+
}
3694+
36873695
/*@internal*/
36883696
interface CompilerHostLike {
36893697
useCaseSensitiveFileNames(): boolean;

Diff for: src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5525,6 +5525,7 @@ namespace ts {
55255525
reportsUnnecessary?: {};
55265526
source?: string;
55275527
relatedInformation?: DiagnosticRelatedInformation[];
5528+
/* @internal */ skippedOn?: keyof CompilerOptions;
55285529
}
55295530

55305531
export interface DiagnosticRelatedInformation {

Diff for: src/testRunner/unittests/tsbuild/helpers.ts

+16-12
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,9 @@ interface Symbol {
296296
}
297297
else if (actualText !== expectedText) {
298298
// Verify build info without affectedFilesPendingEmit
299-
const { text: actualBuildInfoText, affectedFilesPendingEmit: actualAffectedFilesPendingEmit } = getBuildInfoWithoutAffectedFilesPendingEmit(actualText);
300-
const { text: expectedBuildInfoText, affectedFilesPendingEmit: expectedAffectedFilesPendingEmit } = getBuildInfoWithoutAffectedFilesPendingEmit(expectedText);
301-
assert.equal(actualBuildInfoText, expectedBuildInfoText, `TsBuild info text without affectedFilesPendingEmit: ${outputFile}::\nIncremental buildInfoText:: ${actualText}\nClean buildInfoText:: ${expectedText}`);
299+
const { buildInfo: actualBuildInfo, affectedFilesPendingEmit: actualAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(actualText);
300+
const { buildInfo: expectedBuildInfo, affectedFilesPendingEmit: expectedAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(expectedText);
301+
assert.deepEqual(actualBuildInfo, expectedBuildInfo, `TsBuild info text without affectedFilesPendingEmit: ${outputFile}::\nIncremental buildInfoText:: ${actualText}\nClean buildInfoText:: ${expectedText}`);
302302
// Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option
303303
if (actualAffectedFilesPendingEmit) {
304304
assert.isDefined(expectedAffectedFilesPendingEmit, `Incremental build contains affectedFilesPendingEmit, clean build should also have it: ${outputFile}::\nIncremental buildInfoText:: ${actualText}\nClean buildInfoText:: ${expectedText}`);
@@ -314,15 +314,19 @@ interface Symbol {
314314
});
315315
}
316316

317-
function getBuildInfoWithoutAffectedFilesPendingEmit(text: string | undefined): { text: string | undefined; affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"]; } {
317+
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { buildInfo: BuildInfo | undefined; affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"]; } {
318318
const buildInfo = text ? getBuildInfo(text) : undefined;
319-
if (!buildInfo?.program?.affectedFilesPendingEmit) return { text };
320-
const { program: { affectedFilesPendingEmit, ...programRest }, ...rest } = buildInfo;
319+
if (!buildInfo?.program) return { buildInfo };
320+
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
321+
const { program: { affectedFilesPendingEmit, options: { noEmit, ...optionsRest}, ...programRest }, ...rest } = buildInfo;
321322
return {
322-
text: getBuildInfoText({
323+
buildInfo: {
323324
...rest,
324-
program: programRest
325-
}),
325+
program: {
326+
options: optionsRest,
327+
...programRest
328+
}
329+
},
326330
affectedFilesPendingEmit
327331
};
328332
}
@@ -423,7 +427,7 @@ interface Symbol {
423427
subScenario,
424428
baseFs,
425429
newSys,
426-
commandLineArgs,
430+
commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
427431
incrementalModifyFs,
428432
modifyFs,
429433
tick
@@ -515,12 +519,12 @@ interface Symbol {
515519
}));
516520
});
517521
describe("incremental correctness", () => {
518-
incrementalScenarios.forEach((_, index) => verifyIncrementalCorrectness(() => ({
522+
incrementalScenarios.forEach(({ commandLineArgs: incrementalCommandLineArgs }, index) => verifyIncrementalCorrectness(() => ({
519523
scenario,
520524
subScenario,
521525
baseFs,
522526
newSys: incrementalSys[index],
523-
commandLineArgs,
527+
commandLineArgs: incrementalCommandLineArgs || commandLineArgs,
524528
incrementalModifyFs: fs => {
525529
for (let i = 0; i <= index; i++) {
526530
incrementalScenarios[i].modifyFs(fs);

0 commit comments

Comments
 (0)