Skip to content

Commit e18dd33

Browse files
committed
Allow noEmit with incremental and composite
Fixes #38440
1 parent 9a741bc commit e18dd33

19 files changed

+2058
-372
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
@@ -1013,6 +1013,12 @@ namespace ts {
10131013
}
10141014
}
10151015

1016+
function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
1017+
const diagnostic = error(location, message, arg0, arg1, arg2, arg3);
1018+
diagnostic.skippedOn = key;
1019+
return diagnostic;
1020+
}
1021+
10161022
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
10171023
const diagnostic = location
10181024
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
@@ -32030,13 +32036,13 @@ namespace ts {
3203032036

3203132037
function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) {
3203232038
// no rest parameters \ declaration context \ overload - no codegen impact
32033-
if (languageVersion >= ScriptTarget.ES2015 || compilerOptions.noEmit || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((<FunctionLikeDeclaration>node).body)) {
32039+
if (languageVersion >= ScriptTarget.ES2015 || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((<FunctionLikeDeclaration>node).body)) {
3203432040
return;
3203532041
}
3203632042

3203732043
forEach(node.parameters, p => {
3203832044
if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) {
32039-
error(p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters);
32045+
errorSkippedOn("noEmit", p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters);
3204032046
}
3204132047
});
3204232048
}
@@ -32106,13 +32112,13 @@ namespace ts {
3210632112
function checkWeakMapCollision(node: Node) {
3210732113
const enclosingBlockScope = getEnclosingBlockScopeContainer(node);
3210832114
if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) {
32109-
error(node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, "WeakMap");
32115+
errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, "WeakMap");
3211032116
}
3211132117
}
3211232118

3211332119
function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier) {
3211432120
// No need to check for require or exports for ES6 modules and later
32115-
if (moduleKind >= ModuleKind.ES2015 || compilerOptions.noEmit) {
32121+
if (moduleKind >= ModuleKind.ES2015) {
3211632122
return;
3211732123
}
3211832124

@@ -32129,13 +32135,13 @@ namespace ts {
3212932135
const parent = getDeclarationContainer(node);
3213032136
if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>parent)) {
3213132137
// If the declaration happens to be in external module, report error that require and exports are reserved keywords
32132-
error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module,
32138+
errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module,
3213332139
declarationNameToString(name), declarationNameToString(name));
3213432140
}
3213532141
}
3213632142

3213732143
function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void {
32138-
if (languageVersion >= ScriptTarget.ES2017 || compilerOptions.noEmit || !needCollisionCheckForIdentifier(node, name, "Promise")) {
32144+
if (languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) {
3213932145
return;
3214032146
}
3214132147

@@ -32148,7 +32154,7 @@ namespace ts {
3214832154
const parent = getDeclarationContainer(node);
3214932155
if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(<SourceFile>parent) && parent.flags & NodeFlags.HasAsyncFunctions) {
3215032156
// If the declaration happens to be in external module, report error that Promise is a reserved identifier.
32151-
error(name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions,
32157+
errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions,
3215232158
declarationNameToString(name), declarationNameToString(name));
3215332159
}
3215432160
}
@@ -32366,7 +32372,7 @@ namespace ts {
3236632372
}
3236732373
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
3236832374
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
32369-
if (!compilerOptions.noEmit && languageVersion < ScriptTarget.ESNext && needCollisionCheckForIdentifier(node, node.name, "WeakMap")) {
32375+
if (languageVersion < ScriptTarget.ESNext && needCollisionCheckForIdentifier(node, node.name, "WeakMap")) {
3237032376
potentialWeakMapCollisions.push(node);
3237132377
}
3237232378
}
@@ -38267,7 +38273,7 @@ namespace ts {
3826738273

3826838274
const moduleKind = getEmitModuleKind(compilerOptions);
3826938275

38270-
if (moduleKind < ModuleKind.ES2015 && moduleKind !== ModuleKind.System && !compilerOptions.noEmit &&
38276+
if (moduleKind < ModuleKind.ES2015 && moduleKind !== ModuleKind.System &&
3827138277
!(node.parent.parent.flags & NodeFlags.Ambient) && hasSyntacticModifier(node.parent.parent, ModifierFlags.Export)) {
3827238278
checkESModuleMarker(node.name);
3827338279
}
@@ -38287,7 +38293,7 @@ namespace ts {
3828738293
function checkESModuleMarker(name: Identifier | BindingPattern): boolean {
3828838294
if (name.kind === SyntaxKind.Identifier) {
3828938295
if (idText(name) === "__esModule") {
38290-
return grammarErrorOnNode(name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules);
38296+
return grammarErrorOnNodeSkippedOn("noEmit", name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules);
3829138297
}
3829238298
}
3829338299
else {
@@ -38397,6 +38403,15 @@ namespace ts {
3839738403
return false;
3839838404
}
3839938405

38406+
function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
38407+
const sourceFile = getSourceFileOfNode(node);
38408+
if (!hasParseDiagnostics(sourceFile)) {
38409+
errorSkippedOn(key, node, message, arg0, arg1, arg2);
38410+
return true;
38411+
}
38412+
return false;
38413+
}
38414+
3840038415
function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean {
3840138416
const sourceFile = getSourceFileOfNode(node);
3840238417
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
@@ -3269,7 +3265,7 @@ namespace ts {
32693265
}
32703266

32713267
function verifyProjectReferences() {
3272-
const buildInfoPath = !options.noEmit && !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined;
3268+
const buildInfoPath = !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined;
32733269
forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => {
32743270
const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index];
32753271
const parentFile = parent && parent.sourceFile as JsonSourceFile;
@@ -3278,11 +3274,12 @@ namespace ts {
32783274
return;
32793275
}
32803276
const options = resolvedRef.commandLine.options;
3281-
if (!options.composite) {
3277+
if (!options.composite || options.noEmit) {
32823278
// ok to not have composite if the current program is container only
32833279
const inputs = parent ? parent.commandLine.fileNames : rootNames;
32843280
if (inputs.length) {
3285-
createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path);
3281+
if (!options.composite) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path);
3282+
if (options.noEmit) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_may_not_disable_emit, ref.path);
32863283
}
32873284
}
32883285
if (ref.prepend) {
@@ -3648,7 +3645,13 @@ namespace ts {
36483645
cancellationToken: CancellationToken | undefined
36493646
): EmitResult | undefined {
36503647
const options = program.getCompilerOptions();
3651-
if (options.noEmit) return emitSkippedWithNoDiagnostics;
3648+
if (options.noEmit) {
3649+
// Cache the semantic diagnostics
3650+
program.getSemanticDiagnostics(sourceFile, cancellationToken);
3651+
return sourceFile || outFile(options) ?
3652+
emitSkippedWithNoDiagnostics :
3653+
program.emitBuildInfo(writeFile, cancellationToken);
3654+
}
36523655

36533656
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
36543657
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
@@ -3675,6 +3678,11 @@ namespace ts {
36753678
return { diagnostics, sourceMaps: undefined, emittedFiles, emitSkipped: true };
36763679
}
36773680

3681+
/*@internal*/
3682+
export function filterSemanticDiagnotics(diagnostic: readonly Diagnostic[], option: CompilerOptions): readonly Diagnostic[] {
3683+
return filter(diagnostic, d => !d.skippedOn || !option[d.skippedOn]);
3684+
}
3685+
36783686
/*@internal*/
36793687
interface CompilerHostLike {
36803688
useCaseSensitiveFileNames(): boolean;

Diff for: src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5522,6 +5522,7 @@ namespace ts {
55225522
reportsUnnecessary?: {};
55235523
source?: string;
55245524
relatedInformation?: DiagnosticRelatedInformation[];
5525+
/* @internal */ skippedOn?: keyof CompilerOptions;
55255526
}
55265527

55275528
export interface DiagnosticRelatedInformation {

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

+13-9
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
}

Diff for: src/testRunner/unittests/tsc/projectReferences.ts

+21
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,26 @@ namespace ts {
1717
}),
1818
commandLineArgs: ["--p", "src/project"],
1919
});
20+
21+
verifyTsc({
22+
scenario: "projectReferences",
23+
subScenario: "when project references composite project with noEmit",
24+
fs: () => loadProjectFromFiles({
25+
"/src/utils/index.ts": "export const x = 10;",
26+
"/src/utils/tsconfig.json": JSON.stringify({
27+
compilerOptions: {
28+
composite: true,
29+
noEmit: true,
30+
}
31+
}),
32+
"/src/project/index.ts": `import { x } from "../utils";`,
33+
"/src/project/tsconfig.json": JSON.stringify({
34+
references: [
35+
{ path: "../utils" }
36+
]
37+
}),
38+
}),
39+
commandLineArgs: ["--p", "src/project"]
40+
});
2041
});
2142
}

Diff for: src/testRunner/unittests/tscWatch/incremental.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ namespace ts.tscWatch {
194194
messageText: "Type 'number' is not assignable to type 'string'.",
195195
relatedInformation: undefined,
196196
reportsUnnecessary: undefined,
197-
source: undefined
197+
source: undefined,
198+
skippedOn: undefined,
198199
}]);
199200
});
200201
});

0 commit comments

Comments
 (0)