From 707cb938915aba6937c154e53fc4ba3c121a960f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 5 Apr 2019 14:33:33 -0700 Subject: [PATCH 1/7] Write test case that baselines the incremental build result Testcase for #30780 --- src/testRunner/tsconfig.json | 1 + src/testRunner/unittests/tsbuild/helpers.ts | 2 +- .../inferredTypeFromTransitiveModule.ts | 49 +++++++ .../inferred-type-from-transitive-module.js | 93 ++++++++++++ .../inferred-type-from-transitive-module.js | 136 ++++++++++++++++++ .../inferredTypeFromTransitiveModule/bar.ts | 9 ++ .../bundling.ts | 11 ++ .../inferredTypeFromTransitiveModule/index.ts | 5 + .../lazyIndex.ts | 1 + .../tsconfig.json | 9 ++ 10 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts create mode 100644 tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js create mode 100644 tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/initial-Build/inferred-type-from-transitive-module.js create mode 100644 tests/projects/inferredTypeFromTransitiveModule/bar.ts create mode 100644 tests/projects/inferredTypeFromTransitiveModule/bundling.ts create mode 100644 tests/projects/inferredTypeFromTransitiveModule/index.ts create mode 100644 tests/projects/inferredTypeFromTransitiveModule/lazyIndex.ts create mode 100644 tests/projects/inferredTypeFromTransitiveModule/tsconfig.json diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index d3ea4744e8a6f..52131bd6c2841 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -92,6 +92,7 @@ "unittests/tsbuild/amdModulesWithOut.ts", "unittests/tsbuild/emptyFiles.ts", "unittests/tsbuild/graphOrdering.ts", + "unittests/tsbuild//inferredTypeFromTransitiveModule.ts", "unittests/tsbuild/missingExtendedFile.ts", "unittests/tsbuild/outFile.ts", "unittests/tsbuild/referencesWithRootDirInParent.ts", diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 6915dd5d3b75b..538ebd75cdb8a 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/inferredTypeFromTransitiveModule.ts b/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts new file mode 100644 index 0000000000000..792fefb7ba7df --- /dev/null +++ b/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts @@ -0,0 +1,49 @@ +namespace ts { + describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => { + let projFs: vfs.FileSystem; + const { time, tick } = getTime(); + before(() => { + projFs = loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule", time); + }); + after(() => { + projFs = undefined!; + }); + + verifyTsbuildOutput({ + scenario: "inferred type from transitive module", + projFs: () => projFs, + time, + tick, + proj: "inferredTypeFromTransitiveModule", + rootNames: ["/src"], + expectedMapFileNames: emptyArray, + lastProjectOutputJs: `/src/obj/index.js`, + outputFiles: [ + "/src/obj/bar.js", "/src/obj/bar.d.ts", + "/src/obj/bundling.js", "/src/obj/bundling.d.ts", + "/src/obj/lazyIndex.js", "/src/obj/lazyIndex.d.ts", + "/src/obj/index.js", "/src/obj/index.d.ts", + "/src/obj/tsconfig.tsbuildinfo" + ], + initialBuild: { + modifyFs: noop, + expectedDiagnostics: [ + getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tsconfig.json", "src/obj/bar.js"], + [Diagnostics.Building_project_0, "/src/tsconfig.json"] + ] + }, + incrementalDtsChangedBuild: { + modifyFs: fs => replaceText(fs, "/src/bar.ts", "param: string", ""), + expectedDiagnostics: [ + getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tsconfig.json", "src/obj/bar.js", "src/bar.ts"], + [Diagnostics.Building_project_0, "/src/tsconfig.json"], + [Diagnostics.Updating_unchanged_output_timestamps_of_project_0, "/src/tsconfig.json"] + ] + }, + baselineOnly: true, + verifyDiagnostics: true + }); + }); +} diff --git a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js new file mode 100644 index 0000000000000..ea6e9ff01c669 --- /dev/null +++ b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js @@ -0,0 +1,93 @@ +//// [/src/bar.ts] +interface RawAction { + (...args: any[]): Promise | void; +} +interface ActionFactory { + (target: T): T; +} +declare function foo(): ActionFactory; +export default foo()(function foobar(): void { +}); + +//// [/src/obj/bar.d.ts] +declare const _default: () => void; +export default _default; + + +//// [/src/obj/bar.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = foo()(function foobar() { +}); + + +//// [/src/obj/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "/lib/lib.es5.d.ts": { + "version": "/lib/lib.es5.d.ts", + "signature": "/lib/lib.es5.d.ts" + }, + "/lib/lib.es2015.promise.d.ts": { + "version": "/lib/lib.es2015.promise.d.ts", + "signature": "/lib/lib.es2015.promise.d.ts" + }, + "/src/bar.ts": { + "version": "747071916", + "signature": "-9232740537" + }, + "/src/bundling.ts": { + "version": "-21659820217", + "signature": "-40032907372" + }, + "/src/lazyindex.ts": { + "version": "-6956449754", + "signature": "-6224542381" + }, + "/src/index.ts": { + "version": "-11602502901", + "signature": "18468008756" + } + }, + "options": { + "target": 1, + "declaration": true, + "outDir": "/src/obj", + "incremental": true, + "lib": [ + "lib.es5.d.ts", + "lib.es2015.promise.d.ts" + ], + "configFilePath": "/src/tsconfig.json" + }, + "referencedMap": { + "/src/index.ts": [ + "/src/bundling.ts", + "/src/lazyindex.ts" + ], + "/src/lazyindex.ts": [ + "/src/bar.ts" + ] + }, + "exportedModulesMap": { + "/src/index.ts": [ + "/src/bundling.ts", + "/src/lazyindex.ts" + ], + "/src/lazyindex.ts": [ + "/src/bar.ts" + ] + }, + "semanticDiagnosticsPerFile": [ + "/lib/lib.es2015.promise.d.ts", + "/lib/lib.es5.d.ts", + "/src/bar.ts", + "/src/bundling.ts", + "/src/index.ts", + "/src/lazyindex.ts" + ] + }, + "version": "FakeTSVersion" +} + diff --git a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/initial-Build/inferred-type-from-transitive-module.js b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/initial-Build/inferred-type-from-transitive-module.js new file mode 100644 index 0000000000000..d18a805edf43f --- /dev/null +++ b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/initial-Build/inferred-type-from-transitive-module.js @@ -0,0 +1,136 @@ +//// [/src/obj/bar.d.ts] +declare const _default: (param: string) => void; +export default _default; + + +//// [/src/obj/bar.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = foo()(function foobar(param) { +}); + + +//// [/src/obj/bundling.d.ts] +export declare class LazyModule { + private importCallback; + constructor(importCallback: () => Promise); +} +export declare class LazyAction any, TModule> { + constructor(_lazyModule: LazyModule, _getter: (module: TModule) => TAction); +} + + +//// [/src/obj/bundling.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var LazyModule = /** @class */ (function () { + function LazyModule(importCallback) { + this.importCallback = importCallback; + } + return LazyModule; +}()); +exports.LazyModule = LazyModule; +var LazyAction = /** @class */ (function () { + function LazyAction(_lazyModule, _getter) { + } + return LazyAction; +}()); +exports.LazyAction = LazyAction; + + +//// [/src/obj/index.d.ts] +import { LazyAction } from './bundling'; +export declare const lazyBar: LazyAction<(param: string) => void, typeof import("./lazyIndex")>; + + +//// [/src/obj/index.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var bundling_1 = require("./bundling"); +var lazyModule = new bundling_1.LazyModule(function () { + return Promise.resolve().then(function () { return require('./lazyIndex'); }); +}); +exports.lazyBar = new bundling_1.LazyAction(lazyModule, function (m) { return m.bar; }); + + +//// [/src/obj/lazyIndex.d.ts] +export { default as bar } from './bar'; + + +//// [/src/obj/lazyIndex.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var bar_1 = require("./bar"); +exports.bar = bar_1.default; + + +//// [/src/obj/tsconfig.tsbuildinfo] +{ + "program": { + "fileInfos": { + "/lib/lib.es5.d.ts": { + "version": "/lib/lib.es5.d.ts", + "signature": "/lib/lib.es5.d.ts" + }, + "/lib/lib.es2015.promise.d.ts": { + "version": "/lib/lib.es2015.promise.d.ts", + "signature": "/lib/lib.es2015.promise.d.ts" + }, + "/src/bar.ts": { + "version": "5936740878", + "signature": "11191036521" + }, + "/src/bundling.ts": { + "version": "-21659820217", + "signature": "-40032907372" + }, + "/src/lazyindex.ts": { + "version": "-6956449754", + "signature": "-6224542381" + }, + "/src/index.ts": { + "version": "-11602502901", + "signature": "18468008756" + } + }, + "options": { + "target": 1, + "declaration": true, + "outDir": "/src/obj", + "incremental": true, + "lib": [ + "lib.es5.d.ts", + "lib.es2015.promise.d.ts" + ], + "configFilePath": "/src/tsconfig.json" + }, + "referencedMap": { + "/src/index.ts": [ + "/src/bundling.ts", + "/src/lazyindex.ts" + ], + "/src/lazyindex.ts": [ + "/src/bar.ts" + ] + }, + "exportedModulesMap": { + "/src/index.ts": [ + "/src/bundling.ts", + "/src/lazyindex.ts" + ], + "/src/lazyindex.ts": [ + "/src/bar.ts" + ] + }, + "semanticDiagnosticsPerFile": [ + "/lib/lib.es2015.promise.d.ts", + "/lib/lib.es5.d.ts", + "/src/bar.ts", + "/src/bundling.ts", + "/src/index.ts", + "/src/lazyindex.ts" + ] + }, + "version": "FakeTSVersion" +} + diff --git a/tests/projects/inferredTypeFromTransitiveModule/bar.ts b/tests/projects/inferredTypeFromTransitiveModule/bar.ts new file mode 100644 index 0000000000000..84efa817b01df --- /dev/null +++ b/tests/projects/inferredTypeFromTransitiveModule/bar.ts @@ -0,0 +1,9 @@ +interface RawAction { + (...args: any[]): Promise | void; +} +interface ActionFactory { + (target: T): T; +} +declare function foo(): ActionFactory; +export default foo()(function foobar(param: string): void { +}); \ No newline at end of file diff --git a/tests/projects/inferredTypeFromTransitiveModule/bundling.ts b/tests/projects/inferredTypeFromTransitiveModule/bundling.ts new file mode 100644 index 0000000000000..dc7fd0d1864e0 --- /dev/null +++ b/tests/projects/inferredTypeFromTransitiveModule/bundling.ts @@ -0,0 +1,11 @@ +export class LazyModule { + constructor(private importCallback: () => Promise) {} +} + +export class LazyAction< + TAction extends (...args: any[]) => any, + TModule +> { + constructor(_lazyModule: LazyModule, _getter: (module: TModule) => TAction) { + } +} diff --git a/tests/projects/inferredTypeFromTransitiveModule/index.ts b/tests/projects/inferredTypeFromTransitiveModule/index.ts new file mode 100644 index 0000000000000..f5adaa5eb2a32 --- /dev/null +++ b/tests/projects/inferredTypeFromTransitiveModule/index.ts @@ -0,0 +1,5 @@ +import { LazyAction, LazyModule } from './bundling'; +const lazyModule = new LazyModule(() => + import('./lazyIndex') +); +export const lazyBar = new LazyAction(lazyModule, m => m.bar); \ No newline at end of file diff --git a/tests/projects/inferredTypeFromTransitiveModule/lazyIndex.ts b/tests/projects/inferredTypeFromTransitiveModule/lazyIndex.ts new file mode 100644 index 0000000000000..1b1a7743ed76d --- /dev/null +++ b/tests/projects/inferredTypeFromTransitiveModule/lazyIndex.ts @@ -0,0 +1 @@ +export { default as bar } from './bar'; diff --git a/tests/projects/inferredTypeFromTransitiveModule/tsconfig.json b/tests/projects/inferredTypeFromTransitiveModule/tsconfig.json new file mode 100644 index 0000000000000..4663dd37883ea --- /dev/null +++ b/tests/projects/inferredTypeFromTransitiveModule/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "es5", + "declaration": true, + "outDir": "obj", + "incremental": true, + "lib": ["es5", "es2015.promise"] + } +} \ No newline at end of file From 0be9a22dbfd5a4545526b43f928127f7b4267d26 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 16 Apr 2019 12:53:45 -0700 Subject: [PATCH 2/7] Remove unnecessary check since seenEmittedFiles is set when getting pending affected files --- src/compiler/builder.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index f72f30b226cab..df00eca8b0ad9 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -811,11 +811,6 @@ namespace ts { } } - // Mark seen emitted files if there are pending files to be emitted - if (state.affectedFilesPendingEmit && state.program !== affected) { - (state.seenEmittedFiles || (state.seenEmittedFiles = createMap())).set((affected as SourceFile).path, true); - } - return toAffectedFileResult( state, // When whole program is affected, do emit only once (eg when --out or --outFile is specified) From db8c6ee67aca9bb42917b328f1860c779b4231ca Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 Apr 2019 12:42:56 -0700 Subject: [PATCH 3/7] When exported types from module change, the modules exporting these types indirectly mean d.ts change too (not just semantic diagnostics) --- src/compiler/builder.ts | 109 ++++++++++++------ .../inferred-type-from-transitive-module.js | 5 + 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index df00eca8b0ad9..aaba45901ff05 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -241,10 +241,7 @@ namespace ts { oldCompilerOptions.declarationDir !== compilerOptions.declarationDir || (oldCompilerOptions.outFile || oldCompilerOptions.out) !== (compilerOptions.outFile || compilerOptions.out))) { // Add all files to affectedFilesPendingEmit since emit changed - state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, newProgram.getSourceFiles().map(f => f.path)); - if (state.affectedFilesPendingEmitIndex === undefined) { - state.affectedFilesPendingEmitIndex = 0; - } + addToAffectedFilesPendingEmit(state, newProgram.getSourceFiles().map(f => f.path)); Debug.assert(state.seenAffectedFiles === undefined); state.seenAffectedFiles = createMap(); } @@ -343,6 +340,7 @@ namespace ts { // Set the next affected file as seen and remove the cached semantic diagnostics state.affectedFilesIndex = affectedFilesIndex; cleanSemanticDiagnosticsOfAffectedFile(state, affectedFile); + handleDtsMayChangeOfAffectedFile(state, affectedFile) return affectedFile; } seenAffectedFiles.set(affectedFile.path, true); @@ -433,7 +431,55 @@ namespace ts { // If there was change in signature for the changed file, // then delete the semantic diagnostics for files that are affected by using exports of this module + forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, removeSemanticDiagnosticsOf); + } + /** + * Removes semantic diagnostics for path and + * returns true if there are no more semantic diagnostics from the old state + */ + function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { + if (!state.semanticDiagnosticsFromOldState) { + return true; + } + state.semanticDiagnosticsFromOldState.delete(path); + state.semanticDiagnosticsPerFile!.delete(path); + return !state.semanticDiagnosticsFromOldState.size; + } + + /** + * Add files, that are referencing modules that export entities from affected file as pending emit since dts may change + * Similar to cleanSemanticDiagnosticsOfAffectedFile + */ + function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile) { + // If not dts emit, nothing more to do + if (!getEmitDeclarations(state.compilerOptions)) { + return; + } + + // If affected files is everything except default librarry, then nothing more to do + if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { + return; + } + + // If there was change in signature (dts output) for the changed file, + // then only we need to handle pending file emit + if (!state.exportedModulesMap || state.affectedFiles!.length === 1 || !state.changedFilesSet.has(affectedFile.path)) { + return; + } + + forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, filePath) => { + addToAffectedFilesPendingEmit(state, [filePath]); + return false; + }); + } + + /** + * Iterate on referencing modules that export entities from affected file + */ + function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => boolean) { + // If there was change in signature (dts output) for the changed file, + // then only we need to handle pending file emit if (!state.exportedModulesMap || state.affectedFiles!.length === 1 || !state.changedFilesSet.has(affectedFile.path)) { return; } @@ -445,7 +491,7 @@ namespace ts { if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => exportedModules && exportedModules.has(affectedFile.path) && - removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile) + forEachFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) )) { return; } @@ -454,29 +500,28 @@ namespace ts { forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) => !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it exportedModules.has(affectedFile.path) && - removeSemanticDiagnosticsOfFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile) + forEachFilesReferencingPath(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) ); } /** - * removes the semantic diagnostics of files referencing referencedPath and - * returns true if there are no more semantic diagnostics from old state + * Iterate on files referencing referencedPath */ - function removeSemanticDiagnosticsOfFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Map) { + function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Map, fn: (state: BuilderProgramState, filePath: Path) => boolean) { return forEachEntry(state.referencedMap!, (referencesInFile, filePath) => - referencesInFile.has(referencedPath) && removeSemanticDiagnosticsOfFileAndExportsOfFile(state, filePath as Path, seenFileAndExportsOfFile) + referencesInFile.has(referencedPath) && forEachFileAndExportsOfFile(state, filePath as Path, seenFileAndExportsOfFile, fn) ); } /** - * Removes semantic diagnostics of file and anything that exports this file + * fn on file and iterate on anything that exports this file */ - function removeSemanticDiagnosticsOfFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Map): boolean { + function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Map, fn: (state: BuilderProgramState, filePath: Path) => boolean): boolean { if (!addToSeen(seenFileAndExportsOfFile, filePath)) { return false; } - if (removeSemanticDiagnosticsOf(state, filePath)) { + if (fn(state, filePath)) { // If there are no more diagnostics from old cache, done return true; } @@ -487,7 +532,7 @@ namespace ts { if (forEachEntry(state.currentAffectedFilesExportedModulesMap!, (exportedModules, exportedFromPath) => exportedModules && exportedModules.has(filePath) && - removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile) + forEachFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) )) { return true; } @@ -496,7 +541,7 @@ namespace ts { if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) => !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it exportedModules.has(filePath) && - removeSemanticDiagnosticsOfFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile) + forEachFileAndExportsOfFile(state, exportedFromPath as Path, seenFileAndExportsOfFile, fn) )) { return true; } @@ -505,22 +550,10 @@ namespace ts { return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) => referencesInFile.has(filePath) && !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file - removeSemanticDiagnosticsOf(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal + fn(state, referencingFilePath as Path) // Dont add to seen since this is not yet done with the export removal ); } - /** - * Removes semantic diagnostics for path and - * returns true if there are no more semantic diagnostics from the old state - */ - function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { - if (!state.semanticDiagnosticsFromOldState) { - return true; - } - state.semanticDiagnosticsFromOldState.delete(path); - state.semanticDiagnosticsPerFile!.delete(path); - return !state.semanticDiagnosticsFromOldState.size; - } /** * This is called after completing operation on the next affected file. @@ -929,14 +962,7 @@ namespace ts { // In case of emit builder, cache the files to be emitted if (affectedFilesPendingEmit) { - state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit); - // affectedFilesPendingEmitIndex === undefined - // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files - // so start from 0 as array would be affectedFilesPendingEmit - // else, continue to iterate from existing index, the current set is appended to existing files - if (state.affectedFilesPendingEmitIndex === undefined) { - state.affectedFilesPendingEmitIndex = 0; - } + addToAffectedFilesPendingEmit(state, affectedFilesPendingEmit); } let diagnostics: Diagnostic[] | undefined; @@ -947,6 +973,17 @@ namespace ts { } } + function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilesPendingEmit: readonly Path[]) { + state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit); + // affectedFilesPendingEmitIndex === undefined + // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files + // so start from 0 as array would be affectedFilesPendingEmit + // else, continue to iterate from existing index, the current set is appended to existing files + if (state.affectedFilesPendingEmitIndex === undefined) { + state.affectedFilesPendingEmitIndex = 0; + } + } + function getMapOfReferencedSet(mapLike: MapLike> | undefined): ReadonlyMap | undefined { if (!mapLike) return undefined; const map = createMap(); diff --git a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js index ea6e9ff01c669..0dca3e32654df 100644 --- a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js +++ b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js @@ -21,6 +21,11 @@ exports.default = foo()(function foobar() { }); +//// [/src/obj/index.d.ts] +import { LazyAction } from './bundling'; +export declare const lazyBar: LazyAction<() => void, typeof import("./lazyIndex")>; + + //// [/src/obj/tsconfig.tsbuildinfo] { "program": { From 17d2c8bcfb92872f8015427bab59ebb60ef1bab6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 16 Apr 2019 13:25:54 -0700 Subject: [PATCH 4/7] Handle dts change as well as signature update when exported module affects dts but not js file --- src/compiler/builder.ts | 32 +++++++++++++------ src/compiler/builderState.ts | 2 +- .../inferred-type-from-transitive-module.js | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index aaba45901ff05..2d6551cb37752 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -340,7 +340,7 @@ namespace ts { // Set the next affected file as seen and remove the cached semantic diagnostics state.affectedFilesIndex = affectedFilesIndex; cleanSemanticDiagnosticsOfAffectedFile(state, affectedFile); - handleDtsMayChangeOfAffectedFile(state, affectedFile) + handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash); return affectedFile; } seenAffectedFiles.set(affectedFile.path, true); @@ -451,11 +451,7 @@ namespace ts { * Add files, that are referencing modules that export entities from affected file as pending emit since dts may change * Similar to cleanSemanticDiagnosticsOfAffectedFile */ - function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile) { - // If not dts emit, nothing more to do - if (!getEmitDeclarations(state.compilerOptions)) { - return; - } + function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { // If affected files is everything except default librarry, then nothing more to do if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { @@ -468,10 +464,26 @@ namespace ts { return; } - forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, filePath) => { - addToAffectedFilesPendingEmit(state, [filePath]); - return false; - }); + forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, path) => handleDtsMayChangeOf(state, path, cancellationToken, computeHash)); + } + + /** + * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, + * Also we need to make sure signature is updated for these files + */ + function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { + if (!state.changedFilesSet.has(path)) { + const program = Debug.assertDefined(state.program); + const sourceFile = program.getSourceFileByPath(path); + if (sourceFile) { + BuilderState.updateShapeSignature(state, program, sourceFile, Debug.assertDefined(state.currentAffectedFilesSignatures), cancellationToken, computeHash, state.currentAffectedFilesExportedModulesMap); + // If not dts emit, nothing more to do + if (getEmitDeclarations(state.compilerOptions)) { + addToAffectedFilesPendingEmit(state, [path]); + } + } + } + return false; } /** diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index b7f01b838fdbf..58b189d9e81d4 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -321,7 +321,7 @@ namespace ts.BuilderState { /** * Returns if the shape of the signature has changed since last emit */ - function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) { + export function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) { Debug.assert(!!sourceFile); Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state"); diff --git a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js index 0dca3e32654df..89e21796e61d7 100644 --- a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js +++ b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js @@ -52,7 +52,7 @@ export declare const lazyBar: LazyAction<() => void, typeof import("./lazyIndex" }, "/src/index.ts": { "version": "-11602502901", - "signature": "18468008756" + "signature": "6256067474" } }, "options": { From ae7e00e74119a9009f73ea2a1ca615a6bc8b7653 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 16 Apr 2019 14:18:03 -0700 Subject: [PATCH 5/7] Combine semantic diagnostics of files using exported entities from modules and their dts emit --- src/compiler/builder.ts | 78 +++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 2d6551cb37752..219a2c0cbc92b 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -339,7 +339,6 @@ namespace ts { if (!seenAffectedFiles.has(affectedFile.path)) { // Set the next affected file as seen and remove the cached semantic diagnostics state.affectedFilesIndex = affectedFilesIndex; - cleanSemanticDiagnosticsOfAffectedFile(state, affectedFile); handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash); return affectedFile; } @@ -407,60 +406,25 @@ namespace ts { } /** - * Remove the semantic diagnostics cached from old state for affected File and the files that are referencing modules that export entities from affected file - */ - function cleanSemanticDiagnosticsOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile) { - if (removeSemanticDiagnosticsOf(state, affectedFile.path)) { - // If there are no more diagnostics from old cache, done - return; - } - - // Clean lib file diagnostics if its all files excluding default files to emit - if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles && !state.cleanedDiagnosticsOfLibFiles) { - state.cleanedDiagnosticsOfLibFiles = true; - const program = Debug.assertDefined(state.program); - const options = program.getCompilerOptions(); - if (forEach(program.getSourceFiles(), f => - program.isSourceFileDefaultLibrary(f) && - !skipTypeChecking(f, options) && - removeSemanticDiagnosticsOf(state, f.path) - )) { - return; - } - } - - // If there was change in signature for the changed file, - // then delete the semantic diagnostics for files that are affected by using exports of this module - forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, removeSemanticDiagnosticsOf); - } - - /** - * Removes semantic diagnostics for path and - * returns true if there are no more semantic diagnostics from the old state - */ - function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { - if (!state.semanticDiagnosticsFromOldState) { - return true; - } - state.semanticDiagnosticsFromOldState.delete(path); - state.semanticDiagnosticsPerFile!.delete(path); - return !state.semanticDiagnosticsFromOldState.size; - } - - /** - * Add files, that are referencing modules that export entities from affected file as pending emit since dts may change + * Handles sematic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file + * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change * Similar to cleanSemanticDiagnosticsOfAffectedFile */ function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { + removeSemanticDiagnosticsOf(state, affectedFile.path); // If affected files is everything except default librarry, then nothing more to do if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { - return; - } - - // If there was change in signature (dts output) for the changed file, - // then only we need to handle pending file emit - if (!state.exportedModulesMap || state.affectedFiles!.length === 1 || !state.changedFilesSet.has(affectedFile.path)) { + if (!state.cleanedDiagnosticsOfLibFiles) { + state.cleanedDiagnosticsOfLibFiles = true; + const program = Debug.assertDefined(state.program); + const options = program.getCompilerOptions(); + forEach(program.getSourceFiles(), f => + program.isSourceFileDefaultLibrary(f) && + !skipTypeChecking(f, options) && + removeSemanticDiagnosticsOf(state, f.path) + ); + } return; } @@ -472,6 +436,8 @@ namespace ts { * Also we need to make sure signature is updated for these files */ function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { + removeSemanticDiagnosticsOf(state, path); + if (!state.changedFilesSet.has(path)) { const program = Debug.assertDefined(state.program); const sourceFile = program.getSourceFileByPath(path); @@ -483,9 +449,23 @@ namespace ts { } } } + return false; } + /** + * Removes semantic diagnostics for path and + * returns true if there are no more semantic diagnostics from the old state + */ + function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { + if (!state.semanticDiagnosticsFromOldState) { + return true; + } + state.semanticDiagnosticsFromOldState.delete(path); + state.semanticDiagnosticsPerFile!.delete(path); + return !state.semanticDiagnosticsFromOldState.size; + } + /** * Iterate on referencing modules that export entities from affected file */ From 4a55025f2043a5c7febc8453af44f622b488ee99 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 17 Apr 2019 16:32:33 -0700 Subject: [PATCH 6/7] Code review feedback --- src/compiler/builder.ts | 19 +++++++++++++++---- src/testRunner/tsconfig.json | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 219a2c0cbc92b..8ee04b26e1ddd 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -406,14 +406,13 @@ namespace ts { } /** - * Handles sematic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file + * Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change - * Similar to cleanSemanticDiagnosticsOfAffectedFile */ function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { removeSemanticDiagnosticsOf(state, affectedFile.path); - // If affected files is everything except default librarry, then nothing more to do + // If affected files is everything except default library, then nothing more to do if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { if (!state.cleanedDiagnosticsOfLibFiles) { state.cleanedDiagnosticsOfLibFiles = true; @@ -442,7 +441,19 @@ namespace ts { const program = Debug.assertDefined(state.program); const sourceFile = program.getSourceFileByPath(path); if (sourceFile) { - BuilderState.updateShapeSignature(state, program, sourceFile, Debug.assertDefined(state.currentAffectedFilesSignatures), cancellationToken, computeHash, state.currentAffectedFilesExportedModulesMap); + // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics + // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file + // This ensures that we dont later during incremental builds considering wrong signature. + // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build + BuilderState.updateShapeSignature( + state, + program, + sourceFile, + Debug.assertDefined(state.currentAffectedFilesSignatures), + cancellationToken, + computeHash, + state.currentAffectedFilesExportedModulesMap + ); // If not dts emit, nothing more to do if (getEmitDeclarations(state.compilerOptions)) { addToAffectedFilesPendingEmit(state, [path]); diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 52131bd6c2841..9210aed9ab7f9 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -92,7 +92,7 @@ "unittests/tsbuild/amdModulesWithOut.ts", "unittests/tsbuild/emptyFiles.ts", "unittests/tsbuild/graphOrdering.ts", - "unittests/tsbuild//inferredTypeFromTransitiveModule.ts", + "unittests/tsbuild/inferredTypeFromTransitiveModule.ts", "unittests/tsbuild/missingExtendedFile.ts", "unittests/tsbuild/outFile.ts", "unittests/tsbuild/referencesWithRootDirInParent.ts", From 2a29880c615856356149c29bd12c89168fcc3354 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 17 Apr 2019 16:43:10 -0700 Subject: [PATCH 7/7] Handle simple lib file in the test case --- .../inferred-type-from-transitive-module.js | 22 ++++++++----------- .../inferred-type-from-transitive-module.js | 22 ++++++++----------- .../global.d.ts | 6 +++++ .../tsconfig.json | 3 +-- 4 files changed, 25 insertions(+), 28 deletions(-) create mode 100644 tests/projects/inferredTypeFromTransitiveModule/global.d.ts diff --git a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js index 89e21796e61d7..f7ffde933fd36 100644 --- a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js +++ b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/incremental-declaration-changes/inferred-type-from-transitive-module.js @@ -30,13 +30,9 @@ export declare const lazyBar: LazyAction<() => void, typeof import("./lazyIndex" { "program": { "fileInfos": { - "/lib/lib.es5.d.ts": { - "version": "/lib/lib.es5.d.ts", - "signature": "/lib/lib.es5.d.ts" - }, - "/lib/lib.es2015.promise.d.ts": { - "version": "/lib/lib.es2015.promise.d.ts", - "signature": "/lib/lib.es2015.promise.d.ts" + "/lib/lib.d.ts": { + "version": "-15964756381", + "signature": "-15964756381" }, "/src/bar.ts": { "version": "747071916", @@ -46,6 +42,10 @@ export declare const lazyBar: LazyAction<() => void, typeof import("./lazyIndex" "version": "-21659820217", "signature": "-40032907372" }, + "/src/global.d.ts": { + "version": "-9780226215", + "signature": "-9780226215" + }, "/src/lazyindex.ts": { "version": "-6956449754", "signature": "-6224542381" @@ -60,10 +60,6 @@ export declare const lazyBar: LazyAction<() => void, typeof import("./lazyIndex" "declaration": true, "outDir": "/src/obj", "incremental": true, - "lib": [ - "lib.es5.d.ts", - "lib.es2015.promise.d.ts" - ], "configFilePath": "/src/tsconfig.json" }, "referencedMap": { @@ -85,10 +81,10 @@ export declare const lazyBar: LazyAction<() => void, typeof import("./lazyIndex" ] }, "semanticDiagnosticsPerFile": [ - "/lib/lib.es2015.promise.d.ts", - "/lib/lib.es5.d.ts", + "/lib/lib.d.ts", "/src/bar.ts", "/src/bundling.ts", + "/src/global.d.ts", "/src/index.ts", "/src/lazyindex.ts" ] diff --git a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/initial-Build/inferred-type-from-transitive-module.js b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/initial-Build/inferred-type-from-transitive-module.js index d18a805edf43f..e587db59b21c2 100644 --- a/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/initial-Build/inferred-type-from-transitive-module.js +++ b/tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/initial-Build/inferred-type-from-transitive-module.js @@ -68,13 +68,9 @@ exports.bar = bar_1.default; { "program": { "fileInfos": { - "/lib/lib.es5.d.ts": { - "version": "/lib/lib.es5.d.ts", - "signature": "/lib/lib.es5.d.ts" - }, - "/lib/lib.es2015.promise.d.ts": { - "version": "/lib/lib.es2015.promise.d.ts", - "signature": "/lib/lib.es2015.promise.d.ts" + "/lib/lib.d.ts": { + "version": "-15964756381", + "signature": "-15964756381" }, "/src/bar.ts": { "version": "5936740878", @@ -84,6 +80,10 @@ exports.bar = bar_1.default; "version": "-21659820217", "signature": "-40032907372" }, + "/src/global.d.ts": { + "version": "-9780226215", + "signature": "-9780226215" + }, "/src/lazyindex.ts": { "version": "-6956449754", "signature": "-6224542381" @@ -98,10 +98,6 @@ exports.bar = bar_1.default; "declaration": true, "outDir": "/src/obj", "incremental": true, - "lib": [ - "lib.es5.d.ts", - "lib.es2015.promise.d.ts" - ], "configFilePath": "/src/tsconfig.json" }, "referencedMap": { @@ -123,10 +119,10 @@ exports.bar = bar_1.default; ] }, "semanticDiagnosticsPerFile": [ - "/lib/lib.es2015.promise.d.ts", - "/lib/lib.es5.d.ts", + "/lib/lib.d.ts", "/src/bar.ts", "/src/bundling.ts", + "/src/global.d.ts", "/src/index.ts", "/src/lazyindex.ts" ] diff --git a/tests/projects/inferredTypeFromTransitiveModule/global.d.ts b/tests/projects/inferredTypeFromTransitiveModule/global.d.ts new file mode 100644 index 0000000000000..0386ed66f7071 --- /dev/null +++ b/tests/projects/inferredTypeFromTransitiveModule/global.d.ts @@ -0,0 +1,6 @@ +interface PromiseConstructor { + new (): Promise; +} +declare var Promise: PromiseConstructor; +interface Promise { +} \ No newline at end of file diff --git a/tests/projects/inferredTypeFromTransitiveModule/tsconfig.json b/tests/projects/inferredTypeFromTransitiveModule/tsconfig.json index 4663dd37883ea..f29cb38d3fb2c 100644 --- a/tests/projects/inferredTypeFromTransitiveModule/tsconfig.json +++ b/tests/projects/inferredTypeFromTransitiveModule/tsconfig.json @@ -3,7 +3,6 @@ "target": "es5", "declaration": true, "outDir": "obj", - "incremental": true, - "lib": ["es5", "es2015.promise"] + "incremental": true } } \ No newline at end of file