diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index f72f30b226c..ef22ec250b3 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -236,10 +236,7 @@ namespace ts { } }); - if (oldCompilerOptions && - (oldCompilerOptions.outDir !== compilerOptions.outDir || - oldCompilerOptions.declarationDir !== compilerOptions.declarationDir || - (oldCompilerOptions.outFile || oldCompilerOptions.out) !== (compilerOptions.outFile || compilerOptions.out))) { + if (oldCompilerOptions && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) { // Add all files to affectedFilesPendingEmit since emit changed state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, newProgram.getSourceFiles().map(f => f.path)); if (state.affectedFilesPendingEmitIndex === undefined) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 12a20e0df42..edb25e9cb71 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -281,6 +281,7 @@ namespace ts { name: "declaration", shortName: "d", type: "boolean", + affectsEmit: true, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, description: Diagnostics.Generates_corresponding_d_ts_file, @@ -288,6 +289,7 @@ namespace ts { { name: "declarationMap", type: "boolean", + affectsEmit: true, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, description: Diagnostics.Generates_a_sourcemap_for_each_corresponding_d_ts_file, @@ -295,12 +297,14 @@ namespace ts { { name: "emitDeclarationOnly", type: "boolean", + affectsEmit: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Only_emit_d_ts_declaration_files, }, { name: "sourceMap", type: "boolean", + affectsEmit: true, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, description: Diagnostics.Generates_corresponding_map_file, @@ -308,6 +312,7 @@ namespace ts { { name: "outFile", type: "string", + affectsEmit: true, isFilePath: true, paramType: Diagnostics.FILE, showInSimplifiedHelpView: true, @@ -317,6 +322,7 @@ namespace ts { { name: "outDir", type: "string", + affectsEmit: true, isFilePath: true, paramType: Diagnostics.DIRECTORY, showInSimplifiedHelpView: true, @@ -326,6 +332,7 @@ namespace ts { { name: "rootDir", type: "string", + affectsEmit: true, isFilePath: true, paramType: Diagnostics.LOCATION, category: Diagnostics.Basic_Options, @@ -334,6 +341,7 @@ namespace ts { { name: "composite", type: "boolean", + affectsEmit: true, isTSConfigOnly: true, category: Diagnostics.Basic_Options, description: Diagnostics.Enable_project_compilation, @@ -341,6 +349,7 @@ namespace ts { { name: "tsBuildInfoFile", type: "string", + affectsEmit: true, isFilePath: true, paramType: Diagnostics.FILE, category: Diagnostics.Basic_Options, @@ -349,6 +358,7 @@ namespace ts { { name: "removeComments", type: "boolean", + affectsEmit: true, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, description: Diagnostics.Do_not_emit_comments_to_output, @@ -356,6 +366,7 @@ namespace ts { { name: "noEmit", type: "boolean", + affectsEmit: true, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, description: Diagnostics.Do_not_emit_outputs, @@ -363,12 +374,14 @@ namespace ts { { name: "importHelpers", type: "boolean", + affectsEmit: true, category: Diagnostics.Basic_Options, description: Diagnostics.Import_emit_helpers_from_tslib }, { name: "downlevelIteration", type: "boolean", + affectsEmit: true, category: Diagnostics.Basic_Options, description: Diagnostics.Provide_full_support_for_iterables_in_for_of_spread_and_destructuring_when_targeting_ES5_or_ES3 }, @@ -580,6 +593,7 @@ namespace ts { { name: "sourceRoot", type: "string", + affectsEmit: true, paramType: Diagnostics.LOCATION, category: Diagnostics.Source_Map_Options, description: Diagnostics.Specify_the_location_where_debugger_should_locate_TypeScript_files_instead_of_source_locations, @@ -587,6 +601,7 @@ namespace ts { { name: "mapRoot", type: "string", + affectsEmit: true, paramType: Diagnostics.LOCATION, category: Diagnostics.Source_Map_Options, description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, @@ -594,12 +609,14 @@ namespace ts { { name: "inlineSourceMap", type: "boolean", + affectsEmit: true, category: Diagnostics.Source_Map_Options, description: Diagnostics.Emit_a_single_file_with_source_maps_instead_of_having_a_separate_file }, { name: "inlineSources", type: "boolean", + affectsEmit: true, category: Diagnostics.Source_Map_Options, description: Diagnostics.Emit_the_source_alongside_the_sourcemaps_within_a_single_file_requires_inlineSourceMap_or_sourceMap_to_be_set }, @@ -635,6 +652,7 @@ namespace ts { { name: "out", type: "string", + affectsEmit: true, isFilePath: false, // This is intentionally broken to support compatability with existing tsconfig files // for correct behaviour, please use outFile category: Diagnostics.Advanced_Options, @@ -644,6 +662,7 @@ namespace ts { { name: "reactNamespace", type: "string", + affectsEmit: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Deprecated_Use_jsxFactory_instead_Specify_the_object_invoked_for_createElement_when_targeting_react_JSX_emit }, @@ -662,6 +681,7 @@ namespace ts { { name: "emitBOM", type: "boolean", + affectsEmit: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files }, @@ -677,6 +697,7 @@ namespace ts { crlf: NewLineKind.CarriageReturnLineFeed, lf: NewLineKind.LineFeed }), + affectsEmit: true, paramType: Diagnostics.NEWLINE, category: Diagnostics.Advanced_Options, description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, @@ -704,6 +725,7 @@ namespace ts { { name: "stripInternal", type: "boolean", + affectsEmit: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Do_not_emit_declarations_for_code_that_has_an_internal_annotation, }, @@ -724,24 +746,28 @@ namespace ts { { name: "noEmitHelpers", type: "boolean", + affectsEmit: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Do_not_generate_custom_helper_functions_like_extends_in_compiled_output }, { name: "noEmitOnError", type: "boolean", + affectsEmit: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Do_not_emit_outputs_if_any_errors_were_reported, }, { name: "preserveConstEnums", type: "boolean", + affectsEmit: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Do_not_erase_const_enum_declarations_in_generated_code }, { name: "declarationDir", type: "string", + affectsEmit: true, isFilePath: true, paramType: Diagnostics.DIRECTORY, category: Diagnostics.Advanced_Options, @@ -826,6 +852,10 @@ namespace ts { export const semanticDiagnosticsOptionDeclarations: ReadonlyArray = optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics); + /* @internal */ + export const affectsEmitOptionDeclarations: ReadonlyArray = + optionDeclarations.filter(option => !!option.affectsEmit); + /* @internal */ export const moduleResolutionOptionDeclarations: ReadonlyArray = optionDeclarations.filter(option => !!option.affectsModuleResolution); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 23d72875e4f..3f0d72d4ad1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4823,6 +4823,7 @@ namespace ts { affectsModuleResolution?: true; // currently same effect as `affectsSourceFile` affectsBindDiagnostics?: true; // true if this affects binding (currently same effect as `affectsSourceFile`) affectsSemanticDiagnostics?: true; // true if option affects semantic diagnostics + affectsEmit?: true; // true if the options affects emit } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index eca7dc7aee9..2e6c3ae4f86 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7320,6 +7320,11 @@ namespace ts { semanticDiagnosticsOptionDeclarations.some(option => !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); } + export function compilerOptionsAffectEmit(newOptions: CompilerOptions, oldOptions: CompilerOptions): boolean { + return oldOptions !== newOptions && + affectsEmitOptionDeclarations.some(option => !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); + } + export function getCompilerOptionValue(options: CompilerOptions, option: CommandLineOption): unknown { return option.strictFlag ? getStrictOptionValue(options, option.name as StrictOptionName) : options[option.name]; } diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 6915dd5d3b7..538ebd75cdb 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -305,7 +305,7 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt actualReadFileMap = undefined!; host = undefined!; }); - if (!baselineOnly) { + if (!baselineOnly || verifyDiagnostics) { it(`verify diagnostics`, () => { host.assertDiagnosticMessages(...(incrementalExpectedDiagnostics || emptyArray)); }); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 588eb256eda..ab9210451aa 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -720,6 +720,47 @@ class someClass { }`), "/src/tests/tsconfig.tsbuildinfo", ] }); + + verifyTsbuildOutput({ + scenario: "when declaration option changes", + projFs: () => projFs, + time, + tick, + proj: "sample1", + rootNames: ["/src/core"], + expectedMapFileNames: emptyArray, + lastProjectOutputJs: "/src/core/index.js", + initialBuild: { + modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ + "compilerOptions": { + "incremental": true, + "skipDefaultLibCheck": true + } +}`), + expectedDiagnostics: [ + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], + ] + }, + incrementalDtsChangedBuild: { + modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`), + expectedDiagnostics: [ + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.d.ts"], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"] + ] + }, + outputFiles: [ + "/src/core/anotherModule.js", + "/src/core/anotherModule.d.ts", + "/src/core/index.js", + "/src/core/index.d.ts", + "/src/core/tsconfig.tsbuildinfo", + ], + baselineOnly: true, + verifyDiagnostics: true + }); }); }); } diff --git a/tests/baselines/reference/tsbuild/sample1/incremental-declaration-changes/when-declaration-option-changes.js b/tests/baselines/reference/tsbuild/sample1/incremental-declaration-changes/when-declaration-option-changes.js new file mode 100644 index 00000000000..ad16296e10c --- /dev/null +++ b/tests/baselines/reference/tsbuild/sample1/incremental-declaration-changes/when-declaration-option-changes.js @@ -0,0 +1,77 @@ +//// [/src/core/anotherModule.d.ts] +export declare const World = "hello"; + + +//// [/src/core/index.d.ts] +export declare const someString: string; +export declare function leftPad(s: string, n: number): string; +export declare function multiply(a: number, b: number): number; + + +//// [/src/core/tsconfig.json] +{ + "compilerOptions": { + "incremental": true, "declaration": true, + "skipDefaultLibCheck": true + } +} + +//// [/src/core/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "/lib/lib.d.ts": { + "version": "/lib/lib.d.ts", + "signature": "/lib/lib.d.ts" + }, + "/lib/lib.es5.d.ts": { + "version": "/lib/lib.es5.d.ts", + "signature": "/lib/lib.es5.d.ts" + }, + "/lib/lib.dom.d.ts": { + "version": "/lib/lib.dom.d.ts", + "signature": "/lib/lib.dom.d.ts" + }, + "/lib/lib.webworker.importscripts.d.ts": { + "version": "/lib/lib.webworker.importscripts.d.ts", + "signature": "/lib/lib.webworker.importscripts.d.ts" + }, + "/lib/lib.scripthost.d.ts": { + "version": "/lib/lib.scripthost.d.ts", + "signature": "/lib/lib.scripthost.d.ts" + }, + "/src/core/anothermodule.ts": { + "version": "-2676574883", + "signature": "-8396256275" + }, + "/src/core/index.ts": { + "version": "-18749805970", + "signature": "1874987148" + }, + "/src/core/some_decl.d.ts": { + "version": "-9253692965", + "signature": "-9253692965" + } + }, + "options": { + "incremental": true, + "declaration": true, + "skipDefaultLibCheck": true, + "configFilePath": "/src/core/tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "/lib/lib.d.ts", + "/lib/lib.dom.d.ts", + "/lib/lib.es5.d.ts", + "/lib/lib.scripthost.d.ts", + "/lib/lib.webworker.importscripts.d.ts", + "/src/core/anothermodule.ts", + "/src/core/index.ts", + "/src/core/some_decl.d.ts" + ] + }, + "version": "FakeTSVersion" +} + diff --git a/tests/baselines/reference/tsbuild/sample1/initial-Build/when-declaration-option-changes.js b/tests/baselines/reference/tsbuild/sample1/initial-Build/when-declaration-option-changes.js new file mode 100644 index 00000000000..341b277de0d --- /dev/null +++ b/tests/baselines/reference/tsbuild/sample1/initial-Build/when-declaration-option-changes.js @@ -0,0 +1,82 @@ +//// [/src/core/anotherModule.js] +"use strict"; +exports.__esModule = true; +exports.World = "hello"; + + +//// [/src/core/index.js] +"use strict"; +exports.__esModule = true; +exports.someString = "HELLO WORLD"; +function leftPad(s, n) { return s + n; } +exports.leftPad = leftPad; +function multiply(a, b) { return a * b; } +exports.multiply = multiply; + + +//// [/src/core/tsconfig.json] +{ + "compilerOptions": { + "incremental": true, + "skipDefaultLibCheck": true + } +} + +//// [/src/core/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "/lib/lib.d.ts": { + "version": "/lib/lib.d.ts", + "signature": "/lib/lib.d.ts" + }, + "/lib/lib.es5.d.ts": { + "version": "/lib/lib.es5.d.ts", + "signature": "/lib/lib.es5.d.ts" + }, + "/lib/lib.dom.d.ts": { + "version": "/lib/lib.dom.d.ts", + "signature": "/lib/lib.dom.d.ts" + }, + "/lib/lib.webworker.importscripts.d.ts": { + "version": "/lib/lib.webworker.importscripts.d.ts", + "signature": "/lib/lib.webworker.importscripts.d.ts" + }, + "/lib/lib.scripthost.d.ts": { + "version": "/lib/lib.scripthost.d.ts", + "signature": "/lib/lib.scripthost.d.ts" + }, + "/src/core/anothermodule.ts": { + "version": "-2676574883", + "signature": "-8396256275" + }, + "/src/core/index.ts": { + "version": "-18749805970", + "signature": "1874987148" + }, + "/src/core/some_decl.d.ts": { + "version": "-9253692965", + "signature": "-9253692965" + } + }, + "options": { + "incremental": true, + "skipDefaultLibCheck": true, + "configFilePath": "/src/core/tsconfig.json" + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "/lib/lib.d.ts", + "/lib/lib.dom.d.ts", + "/lib/lib.es5.d.ts", + "/lib/lib.scripthost.d.ts", + "/lib/lib.webworker.importscripts.d.ts", + "/src/core/anothermodule.ts", + "/src/core/index.ts", + "/src/core/some_decl.d.ts" + ] + }, + "version": "FakeTSVersion" +} +