diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index eedac508368..9b1137a3405 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -395,7 +395,7 @@ namespace ts { name: "lib", type: libMap }, - affectsModuleResolution: true, + affectsProgramStructure: true, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation, @@ -755,7 +755,7 @@ namespace ts { name: "types", type: "string" }, - affectsModuleResolution: true, + affectsProgramStructure: true, showInSimplifiedHelpView: true, category: Diagnostics.Module_Resolution_Options, description: Diagnostics.Type_declaration_files_to_be_included_in_compilation, @@ -928,7 +928,7 @@ namespace ts { { name: "noLib", type: "boolean", - affectsModuleResolution: true, + affectsProgramStructure: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Do_not_include_the_default_library_file_lib_d_ts, // We are not returning a sourceFile for lib file when asked by the program, @@ -955,7 +955,7 @@ namespace ts { { name: "disableSizeLimit", type: "boolean", - affectsSourceFile: true, + affectsProgramStructure: true, category: Diagnostics.Advanced_Options, description: Diagnostics.Disable_size_limitations_on_JavaScript_projects }, @@ -1126,6 +1126,10 @@ namespace ts { export const sourceFileAffectingCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => !!option.affectsSourceFile || !!option.affectsModuleResolution || !!option.affectsBindDiagnostics); + /* @internal */ + export const optionsAffectingProgramStructure: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsProgramStructure); + /* @internal */ export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => hasProperty(option, "transpileOptionValue")); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 102517c4ce6..f6e3b4e5399 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -745,9 +745,7 @@ namespace ts { if (!program) return false; // If any compiler options change, we can't reuse old source file even if version match // The change in options like these could result in change in syntax tree or `sourceFile.bindDiagnostics`. - const oldOptions = program.getCompilerOptions(); - return !!sourceFileAffectingCompilerOptions.some(option => - !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); + return optionsHaveChanges(program.getCompilerOptions(), newOptions, sourceFileAffectingCompilerOptions); } function createCreateProgramOptions(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): CreateProgramOptions { @@ -1421,10 +1419,6 @@ namespace ts { return StructureIsReused.Not; } - if (!arrayIsEqualTo(options.types, oldOptions.types)) { - return StructureIsReused.Not; - } - // Check if any referenced project tsconfig files are different if (!canReuseProjectReferences()) { return StructureIsReused.Not; @@ -1505,7 +1499,7 @@ namespace ts { if (!arrayIsEqualTo(oldSourceFile.libReferenceDirectives, newSourceFile.libReferenceDirectives, fileReferenceIsEqualTo)) { // 'lib' references has changed. Matches behavior in changesAffectModuleResolution - return StructureIsReused.Not; + structureIsReused = StructureIsReused.SafeModules; } if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { @@ -1598,7 +1592,7 @@ namespace ts { return structureIsReused; } - if (host.hasChangedAutomaticTypeDirectiveNames?.()) { + if (changesAffectingProgramStructure(oldOptions, options) || host.hasChangedAutomaticTypeDirectiveNames?.()) { return StructureIsReused.SafeModules; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9a5cf354125..576ca26f985 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6230,6 +6230,7 @@ namespace ts { 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 + affectsProgramStructure?: true; // true if program should be reconstructed from root files if option changes and does not affect module resolution as affectsModuleResolution indirectly means program needs to reconstructed transpileOptionValue?: boolean | undefined; // If set this means that the option should be set to this value when transpiling extraValidation?: (value: CompilerOptionsValue) => [DiagnosticMessage, ...string[]] | undefined; // Additional validation to be performed for the value to be valid } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7a012114524..b04530ebfa4 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -96,7 +96,15 @@ namespace ts { } export function optionsHaveModuleResolutionChanges(oldOptions: CompilerOptions, newOptions: CompilerOptions) { - return moduleResolutionOptionDeclarations.some(o => + return optionsHaveChanges(oldOptions, newOptions, moduleResolutionOptionDeclarations); + } + + export function changesAffectingProgramStructure(oldOptions: CompilerOptions, newOptions: CompilerOptions) { + return optionsHaveChanges(oldOptions, newOptions, optionsAffectingProgramStructure); + } + + export function optionsHaveChanges(oldOptions: CompilerOptions, newOptions: CompilerOptions, optionDeclarations: readonly CommandLineOption[]) { + return oldOptions !== newOptions && optionDeclarations.some(o => !isJsonEqual(getCompilerOptionValue(oldOptions, o), getCompilerOptionValue(newOptions, o))); } @@ -6125,13 +6133,11 @@ namespace ts { } export function compilerOptionsAffectSemanticDiagnostics(newOptions: CompilerOptions, oldOptions: CompilerOptions): boolean { - return oldOptions !== newOptions && - semanticDiagnosticsOptionDeclarations.some(option => !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); + return optionsHaveChanges(oldOptions, newOptions, semanticDiagnosticsOptionDeclarations); } export function compilerOptionsAffectEmit(newOptions: CompilerOptions, oldOptions: CompilerOptions): boolean { - return oldOptions !== newOptions && - affectsEmitOptionDeclarations.some(option => !isJsonEqual(getCompilerOptionValue(oldOptions, option), getCompilerOptionValue(newOptions, option))); + return optionsHaveChanges(oldOptions, newOptions, affectsEmitOptionDeclarations); } export function getCompilerOptionValue(options: CompilerOptions, option: CommandLineOption): unknown { diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 57fe93fed14..41192ac9623 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -262,7 +262,7 @@ namespace ts { it("fails if change affects type references", () => { const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); const program2 = updateProgram(program1, ["a.ts"], { types: ["b"] }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Not); + assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); }); it("succeeds if change doesn't affect type references", () => { diff --git a/tests/baselines/reference/tscWatch/programUpdates/correctly-handles-changes-in-lib-section-of-config-file.js b/tests/baselines/reference/tscWatch/programUpdates/correctly-handles-changes-in-lib-section-of-config-file.js index 31f927caa32..9603efb891f 100644 --- a/tests/baselines/reference/tscWatch/programUpdates/correctly-handles-changes-in-lib-section-of-config-file.js +++ b/tests/baselines/reference/tscWatch/programUpdates/correctly-handles-changes-in-lib-section-of-config-file.js @@ -88,7 +88,7 @@ Output:: Program root files: ["/src/app.ts"] Program options: {"module":1,"target":1,"noImplicitAny":true,"sourceMap":false,"lib":["lib.es5.d.ts","lib.es2015.promise.d.ts"],"watch":true,"project":"/src/tsconfig.json","configFilePath":"/src/tsconfig.json"} -Program structureReused: Not +Program structureReused: SafeModules Program files:: /compiler/lib.es5.d.ts /compiler/lib.es2015.promise.d.ts @@ -112,10 +112,10 @@ WatchedFiles:: FsWatches:: FsWatchesRecursive:: -/src: - {"directoryName":"/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} /src/node_modules/@types: {"directoryName":"/src/node_modules/@types","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} +/src: + {"directoryName":"/src","fallbackPollingInterval":500,"fallbackOptions":{"watchFile":"PriorityPollingInterval"}} exitCode:: ExitStatus.undefined