From 83a58b0229120cbe40fff3ed41637678e09e9521 Mon Sep 17 00:00:00 2001
From: Sheetal Nandi <shkamat@microsoft.com>
Date: Fri, 2 Sep 2022 12:40:14 -0700
Subject: [PATCH] Check if its same buildinfo only for directly referenced
 projects and not recursively Fixes #50545

---
 src/compiler/tsbuildPublic.ts                 |  22 +-
 .../tsbuild/containerOnlyReferenced.ts        |  31 ++
 .../when-solution-is-referenced-indirectly.js | 303 ++++++++++++++++++
 3 files changed, 337 insertions(+), 19 deletions(-)
 create mode 100644 tests/baselines/reference/tsbuild/containerOnlyReferenced/when-solution-is-referenced-indirectly.js

diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts
index 2b9d97f324ee2..0849c5921f29a 100644
--- a/src/compiler/tsbuildPublic.ts
+++ b/src/compiler/tsbuildPublic.ts
@@ -1706,10 +1706,7 @@ namespace ts {
             }
         }
 
-        const seenRefs = buildInfoPath ? new Set<ResolvedConfigFilePath>() : undefined;
         const buildInfoCacheEntry = state.buildInfoCache.get(resolvedPath);
-        seenRefs?.add(resolvedPath);
-
         /** Inputs are up-to-date, just need either timestamp update or bundle prepend manipulation to make it look up-to-date */
         let pseudoUpToDate = false;
         let usesPrepend = false;
@@ -1724,7 +1721,7 @@ namespace ts {
                 }
 
                 // Check if tsbuildinfo path is shared, then we need to rebuild
-                if (buildInfoCacheEntry && hasSameBuildInfo(state, buildInfoCacheEntry, seenRefs!, resolvedConfig, resolvedRefPath)) {
+                if (buildInfoCacheEntry && hasSameBuildInfo(state, buildInfoCacheEntry, resolvedRefPath)) {
                     return {
                         type: UpToDateStatusType.OutOfDateWithUpstream,
                         outOfDateOutputFileName: buildInfoPath!,
@@ -1787,22 +1784,9 @@ namespace ts {
         };
     }
 
-    function hasSameBuildInfo(state: SolutionBuilderState, buildInfoCacheEntry: BuildInfoCacheEntry, seenRefs: Set<ResolvedConfigFilePath>, resolvedConfig: ParsedCommandLine, resolvedRefPath: ResolvedConfigFilePath) {
-        if (seenRefs.has(resolvedRefPath)) return false;
-        seenRefs.add(resolvedRefPath);
+    function hasSameBuildInfo(state: SolutionBuilderState, buildInfoCacheEntry: BuildInfoCacheEntry, resolvedRefPath: ResolvedConfigFilePath) {
         const refBuildInfo = state.buildInfoCache.get(resolvedRefPath)!;
-        if (refBuildInfo.path === buildInfoCacheEntry.path) return true;
-
-        if (resolvedConfig.projectReferences) {
-            // Check references
-            for (const ref of resolvedConfig.projectReferences) {
-                const resolvedRef = resolveProjectReferencePath(ref);
-                const resolvedRefPath = toResolvedConfigFilePath(state, resolvedRef);
-                const resolvedConfig = parseConfigFile(state, resolvedRef, resolvedRefPath)!;
-                if (hasSameBuildInfo(state, buildInfoCacheEntry, seenRefs, resolvedConfig, resolvedRefPath)) return true;
-            }
-        }
-        return false;
+        return refBuildInfo.path === buildInfoCacheEntry.path;
     }
 
     function getUpToDateStatus(state: SolutionBuilderState, project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus {
diff --git a/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts b/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts
index e9eacf1fa657c..595bc509744d5 100644
--- a/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts
+++ b/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts
@@ -7,5 +7,36 @@ namespace ts {
             commandLineArgs: ["--b", "/src", "--verbose"],
             edits: noChangeOnlyRuns
         });
+
+        verifyTscWithEdits({
+            scenario: "containerOnlyReferenced",
+            subScenario: "when solution is referenced indirectly",
+            fs: () => loadProjectFromFiles({
+                "/src/project1/tsconfig.json": JSON.stringify({
+                    compilerOptions: { composite: true },
+                    references: [],
+                }),
+                "/src/project2/tsconfig.json": JSON.stringify({
+                    compilerOptions: { composite: true },
+                    references: [],
+                }),
+                "/src/project2/src/b.ts": "export const b = 10;",
+                "/src/project3/tsconfig.json": JSON.stringify({
+                    compilerOptions: { composite: true },
+                    references: [{ path: "../project1", }, { path: "../project2" }],
+                }),
+                "/src/project3/src/c.ts": "export const c = 10;",
+                "/src/project4/tsconfig.json": JSON.stringify({
+                    compilerOptions: { composite: true },
+                    references: [{ path: "../project3" }]
+                }),
+                "/src/project4/src/d.ts": "export const d = 10;",
+            }),
+            commandLineArgs: ["--b", "/src/project4", "--verbose", "--explainFiles"],
+            edits: [{
+                subScenario: "modify project3 file",
+                modifyFs: fs => replaceText(fs, "/src/project3/src/c.ts", "c = ", "cc = "),
+            }],
+        });
     });
 }
diff --git a/tests/baselines/reference/tsbuild/containerOnlyReferenced/when-solution-is-referenced-indirectly.js b/tests/baselines/reference/tsbuild/containerOnlyReferenced/when-solution-is-referenced-indirectly.js
new file mode 100644
index 0000000000000..7cc61e7f319ee
--- /dev/null
+++ b/tests/baselines/reference/tsbuild/containerOnlyReferenced/when-solution-is-referenced-indirectly.js
@@ -0,0 +1,303 @@
+Input::
+//// [/lib/lib.d.ts]
+/// <reference no-default-lib="true"/>
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array<T> { length: number; [n: number]: T; }
+interface ReadonlyArray<T> {}
+declare const console: { log(msg: any): void; };
+
+//// [/src/project1/tsconfig.json]
+{"compilerOptions":{"composite":true},"references":[]}
+
+//// [/src/project2/src/b.ts]
+export const b = 10;
+
+//// [/src/project2/tsconfig.json]
+{"compilerOptions":{"composite":true},"references":[]}
+
+//// [/src/project3/src/c.ts]
+export const c = 10;
+
+//// [/src/project3/tsconfig.json]
+{"compilerOptions":{"composite":true},"references":[{"path":"../project1"},{"path":"../project2"}]}
+
+//// [/src/project4/src/d.ts]
+export const d = 10;
+
+//// [/src/project4/tsconfig.json]
+{"compilerOptions":{"composite":true},"references":[{"path":"../project3"}]}
+
+
+
+Output::
+/lib/tsc --b /src/project4 --verbose --explainFiles
+[12:00:20 AM] Projects in this build: 
+    * src/project1/tsconfig.json
+    * src/project2/tsconfig.json
+    * src/project3/tsconfig.json
+    * src/project4/tsconfig.json
+
+[12:00:21 AM] Project 'src/project2/tsconfig.json' is out of date because output file 'src/project2/tsconfig.tsbuildinfo' does not exist
+
+[12:00:22 AM] Building project '/src/project2/tsconfig.json'...
+
+lib/lib.d.ts
+  Default library for target 'es3'
+src/project2/src/b.ts
+  Matched by default include pattern '**/*'
+[12:00:28 AM] Project 'src/project3/tsconfig.json' is out of date because output file 'src/project3/tsconfig.tsbuildinfo' does not exist
+
+[12:00:29 AM] Building project '/src/project3/tsconfig.json'...
+
+lib/lib.d.ts
+  Default library for target 'es3'
+src/project3/src/c.ts
+  Matched by default include pattern '**/*'
+[12:00:35 AM] Project 'src/project4/tsconfig.json' is out of date because output file 'src/project4/tsconfig.tsbuildinfo' does not exist
+
+[12:00:36 AM] Building project '/src/project4/tsconfig.json'...
+
+lib/lib.d.ts
+  Default library for target 'es3'
+src/project4/src/d.ts
+  Matched by default include pattern '**/*'
+exitCode:: ExitStatus.Success
+
+
+//// [/src/project2/src/b.d.ts]
+export declare const b = 10;
+
+
+//// [/src/project2/src/b.js]
+"use strict";
+exports.__esModule = true;
+exports.b = void 0;
+exports.b = 10;
+
+
+//// [/src/project2/tsconfig.tsbuildinfo]
+{"program":{"fileNames":["../../lib/lib.d.ts","./src/b.ts"],"fileInfos":[{"version":"3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true},{"version":"-13368947479-export const b = 10;","signature":"-1807916688-export declare const b = 10;\r\n"}],"options":{"composite":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,2],"latestChangedDtsFile":"./src/b.d.ts"},"version":"FakeTSVersion"}
+
+//// [/src/project2/tsconfig.tsbuildinfo.readable.baseline.txt]
+{
+  "program": {
+    "fileNames": [
+      "../../lib/lib.d.ts",
+      "./src/b.ts"
+    ],
+    "fileInfos": {
+      "../../lib/lib.d.ts": {
+        "version": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
+        "signature": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
+        "affectsGlobalScope": true
+      },
+      "./src/b.ts": {
+        "version": "-13368947479-export const b = 10;",
+        "signature": "-1807916688-export declare const b = 10;\r\n"
+      }
+    },
+    "options": {
+      "composite": true
+    },
+    "referencedMap": {},
+    "exportedModulesMap": {},
+    "semanticDiagnosticsPerFile": [
+      "../../lib/lib.d.ts",
+      "./src/b.ts"
+    ],
+    "latestChangedDtsFile": "./src/b.d.ts"
+  },
+  "version": "FakeTSVersion",
+  "size": 832
+}
+
+//// [/src/project3/src/c.d.ts]
+export declare const c = 10;
+
+
+//// [/src/project3/src/c.js]
+"use strict";
+exports.__esModule = true;
+exports.c = void 0;
+exports.c = 10;
+
+
+//// [/src/project3/tsconfig.tsbuildinfo]
+{"program":{"fileNames":["../../lib/lib.d.ts","./src/c.ts"],"fileInfos":[{"version":"3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true},{"version":"-12077479510-export const c = 10;","signature":"-4148571535-export declare const c = 10;\r\n"}],"options":{"composite":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,2],"latestChangedDtsFile":"./src/c.d.ts"},"version":"FakeTSVersion"}
+
+//// [/src/project3/tsconfig.tsbuildinfo.readable.baseline.txt]
+{
+  "program": {
+    "fileNames": [
+      "../../lib/lib.d.ts",
+      "./src/c.ts"
+    ],
+    "fileInfos": {
+      "../../lib/lib.d.ts": {
+        "version": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
+        "signature": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
+        "affectsGlobalScope": true
+      },
+      "./src/c.ts": {
+        "version": "-12077479510-export const c = 10;",
+        "signature": "-4148571535-export declare const c = 10;\r\n"
+      }
+    },
+    "options": {
+      "composite": true
+    },
+    "referencedMap": {},
+    "exportedModulesMap": {},
+    "semanticDiagnosticsPerFile": [
+      "../../lib/lib.d.ts",
+      "./src/c.ts"
+    ],
+    "latestChangedDtsFile": "./src/c.d.ts"
+  },
+  "version": "FakeTSVersion",
+  "size": 832
+}
+
+//// [/src/project4/src/d.d.ts]
+export declare const d = 10;
+
+
+//// [/src/project4/src/d.js]
+"use strict";
+exports.__esModule = true;
+exports.d = void 0;
+exports.d = 10;
+
+
+//// [/src/project4/tsconfig.tsbuildinfo]
+{"program":{"fileNames":["../../lib/lib.d.ts","./src/d.ts"],"fileInfos":[{"version":"3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true},{"version":"-10786011541-export const d = 10;","signature":"-6489226382-export declare const d = 10;\r\n"}],"options":{"composite":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,2],"latestChangedDtsFile":"./src/d.d.ts"},"version":"FakeTSVersion"}
+
+//// [/src/project4/tsconfig.tsbuildinfo.readable.baseline.txt]
+{
+  "program": {
+    "fileNames": [
+      "../../lib/lib.d.ts",
+      "./src/d.ts"
+    ],
+    "fileInfos": {
+      "../../lib/lib.d.ts": {
+        "version": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
+        "signature": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
+        "affectsGlobalScope": true
+      },
+      "./src/d.ts": {
+        "version": "-10786011541-export const d = 10;",
+        "signature": "-6489226382-export declare const d = 10;\r\n"
+      }
+    },
+    "options": {
+      "composite": true
+    },
+    "referencedMap": {},
+    "exportedModulesMap": {},
+    "semanticDiagnosticsPerFile": [
+      "../../lib/lib.d.ts",
+      "./src/d.ts"
+    ],
+    "latestChangedDtsFile": "./src/d.d.ts"
+  },
+  "version": "FakeTSVersion",
+  "size": 832
+}
+
+
+
+Change:: modify project3 file
+Input::
+//// [/src/project3/src/c.ts]
+export const cc = 10;
+
+
+
+Output::
+/lib/tsc --b /src/project4 --verbose --explainFiles
+[12:00:43 AM] Projects in this build: 
+    * src/project1/tsconfig.json
+    * src/project2/tsconfig.json
+    * src/project3/tsconfig.json
+    * src/project4/tsconfig.json
+
+[12:00:44 AM] Project 'src/project2/tsconfig.json' is up to date because newest input 'src/project2/src/b.ts' is older than output 'src/project2/tsconfig.tsbuildinfo'
+
+[12:00:45 AM] Project 'src/project3/tsconfig.json' is out of date because output 'src/project3/tsconfig.tsbuildinfo' is older than input 'src/project3/src/c.ts'
+
+[12:00:46 AM] Building project '/src/project3/tsconfig.json'...
+
+lib/lib.d.ts
+  Default library for target 'es3'
+src/project3/src/c.ts
+  Matched by default include pattern '**/*'
+[12:00:52 AM] Project 'src/project4/tsconfig.json' is out of date because output 'src/project4/tsconfig.tsbuildinfo' is older than input 'src/project3'
+
+[12:00:53 AM] Building project '/src/project4/tsconfig.json'...
+
+[12:00:54 AM] Updating unchanged output timestamps of project '/src/project4/tsconfig.json'...
+
+lib/lib.d.ts
+  Default library for target 'es3'
+src/project4/src/d.ts
+  Matched by default include pattern '**/*'
+exitCode:: ExitStatus.Success
+
+
+//// [/src/project3/src/c.d.ts]
+export declare const cc = 10;
+
+
+//// [/src/project3/src/c.js]
+"use strict";
+exports.__esModule = true;
+exports.cc = void 0;
+exports.cc = 10;
+
+
+//// [/src/project3/tsconfig.tsbuildinfo]
+{"program":{"fileNames":["../../lib/lib.d.ts","./src/c.ts"],"fileInfos":[{"version":"3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true},{"version":"-12481904019-export const cc = 10;","signature":"-2519819788-export declare const cc = 10;\r\n"}],"options":{"composite":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,2],"latestChangedDtsFile":"./src/c.d.ts"},"version":"FakeTSVersion"}
+
+//// [/src/project3/tsconfig.tsbuildinfo.readable.baseline.txt]
+{
+  "program": {
+    "fileNames": [
+      "../../lib/lib.d.ts",
+      "./src/c.ts"
+    ],
+    "fileInfos": {
+      "../../lib/lib.d.ts": {
+        "version": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
+        "signature": "3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };",
+        "affectsGlobalScope": true
+      },
+      "./src/c.ts": {
+        "version": "-12481904019-export const cc = 10;",
+        "signature": "-2519819788-export declare const cc = 10;\r\n"
+      }
+    },
+    "options": {
+      "composite": true
+    },
+    "referencedMap": {},
+    "exportedModulesMap": {},
+    "semanticDiagnosticsPerFile": [
+      "../../lib/lib.d.ts",
+      "./src/c.ts"
+    ],
+    "latestChangedDtsFile": "./src/c.d.ts"
+  },
+  "version": "FakeTSVersion",
+  "size": 834
+}
+
+//// [/src/project4/tsconfig.tsbuildinfo] file changed its modified time