From 4d6c59bae008dd27b7417da6c43a0933d46f0d65 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 19:31:21 +0000
Subject: [PATCH 01/10] Initial plan
From 0351c5186c18ac5b441e00de38f0a9affc892e4c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 19:41:52 +0000
Subject: [PATCH 02/10] Implement fix for synthetic default exports with
project references
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
src/compiler/checker.ts | 18 ++++++++++++++++
...heticDefaultExportWithProjectReferences.ts | 21 +++++++++++++++++++
2 files changed, 39 insertions(+)
create mode 100644 tests/cases/fourslash/syntheticDefaultExportWithProjectReferences.ts
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 390c843b0c968..9e313c76ae5fc 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -3812,6 +3812,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// it definitely is a module and does not have a synthetic default
return false;
}
+ // If this is a declaration file from a project reference, check the referenced project's options
+ // to determine if the module format is ESM
+ if (file && file.isDeclarationFile) {
+ const redirect = host.getRedirectFromSourceFile(file.path);
+ if (redirect?.resolvedRef) {
+ const referencedOptions = redirect.resolvedRef.commandLine.options;
+ // If the referenced project has allowSyntheticDefaultImports disabled, respect that
+ if (!getAllowSyntheticDefaultImports(referencedOptions)) {
+ return false;
+ }
+ // If the referenced project's module format is ESM (ES2015 or later),
+ // it cannot have a synthetic default
+ const referencedModuleKind = referencedOptions.module ?? ModuleKind.CommonJS;
+ if (referencedModuleKind >= ModuleKind.ES2015) {
+ return false;
+ }
+ }
+ }
// There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set
// Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member
// as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm
diff --git a/tests/cases/fourslash/syntheticDefaultExportWithProjectReferences.ts b/tests/cases/fourslash/syntheticDefaultExportWithProjectReferences.ts
new file mode 100644
index 0000000000000..1581de77c9a58
--- /dev/null
+++ b/tests/cases/fourslash/syntheticDefaultExportWithProjectReferences.ts
@@ -0,0 +1,21 @@
+///
+
+// Test case for synthetic default imports from declaration files.
+// Without project references, .d.ts files allow synthetic defaults due to ambiguity.
+// With project references (tested manually), the referenced project's options determine
+// if synthetic defaults are allowed.
+
+// @module: esnext
+// @moduleResolution: bundler
+
+// @Filename: /a/utils.d.ts
+//// export declare const test: () => string;
+
+// @Filename: /b/index.ts
+//// import Test from '../a/utils';
+////
+//// console.log(Test.test());
+
+// Without project references, this is allowed (no error) because we can't determine
+// if the original JS was ESM or CJS
+verify.getSemanticDiagnostics([]);
From 071043f0eb5e6d681257bd70cc8dedc89e0a5924 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 19:44:55 +0000
Subject: [PATCH 03/10] Remove redundant condition check in
canHaveSyntheticDefault
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
src/compiler/checker.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 9e313c76ae5fc..9994c0675f18e 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -3814,7 +3814,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// If this is a declaration file from a project reference, check the referenced project's options
// to determine if the module format is ESM
- if (file && file.isDeclarationFile) {
+ if (file) {
const redirect = host.getRedirectFromSourceFile(file.path);
if (redirect?.resolvedRef) {
const referencedOptions = redirect.resolvedRef.commandLine.options;
From bcd143ab1fbad1988a3ad6371b91eff4544a6938 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 19:46:03 +0000
Subject: [PATCH 04/10] Run formatter to fix trailing whitespace
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
src/compiler/checker.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 9994c0675f18e..ae8ec56cd6498 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -3822,7 +3822,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!getAllowSyntheticDefaultImports(referencedOptions)) {
return false;
}
- // If the referenced project's module format is ESM (ES2015 or later),
+ // If the referenced project's module format is ESM (ES2015 or later),
// it cannot have a synthetic default
const referencedModuleKind = referencedOptions.module ?? ModuleKind.CommonJS;
if (referencedModuleKind >= ModuleKind.ES2015) {
From 01c3f081b1085907bf579fdaa5baf356ab7be0ee Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 19:59:00 +0000
Subject: [PATCH 05/10] Accept new baseline for default-import-interop test
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
.../default-import-interop-uses-referenced-project-settings.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tests/baselines/reference/tsc/projectReferences/default-import-interop-uses-referenced-project-settings.js b/tests/baselines/reference/tsc/projectReferences/default-import-interop-uses-referenced-project-settings.js
index bf990179392da..79610a168d1f0 100644
--- a/tests/baselines/reference/tsc/projectReferences/default-import-interop-uses-referenced-project-settings.js
+++ b/tests/baselines/reference/tsc/projectReferences/default-import-interop-uses-referenced-project-settings.js
@@ -86,6 +86,8 @@ declare const console: { log(msg: any): void; };
Output::
app/src/index.ts(2,28): error TS2613: Module '"/home/src/workspaces/project/app/src/local"' has no default export. Did you mean to use 'import { local } from "/home/src/workspaces/project/app/src/local"' instead?
app/src/index.ts(3,28): error TS2613: Module '"/home/src/workspaces/project/node_modules/esm-package/index"' has no default export. Did you mean to use 'import { esm } from "/home/src/workspaces/project/node_modules/esm-package/index"' instead?
+app/src/index.ts(4,28): error TS1192: Module '"/home/src/workspaces/project/lib/dist/a"' has no default export.
+app/src/index.ts(5,28): error TS1192: Module '"/home/src/workspaces/project/lib/dist/a"' has no default export.
//// [/home/src/workspaces/project/app/dist/local.js]
From 4e968bbc21ac7efa07a8a499e6317ab880a88170 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 20:14:50 +0000
Subject: [PATCH 06/10] Replace fourslash test with proper tsc test that
reproduces the bug
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
.../unittests/tsc/projectReferences.ts | 29 +++++++++
...ule-disallows-synthetic-default-imports.js | 62 +++++++++++++++++++
...heticDefaultExportWithProjectReferences.ts | 21 -------
3 files changed, 91 insertions(+), 21 deletions(-)
create mode 100644 tests/baselines/reference/tsc/projectReferences/referenced-project-with-esnext-module-disallows-synthetic-default-imports.js
delete mode 100644 tests/cases/fourslash/syntheticDefaultExportWithProjectReferences.ts
diff --git a/src/testRunner/unittests/tsc/projectReferences.ts b/src/testRunner/unittests/tsc/projectReferences.ts
index 3a5b1e09de7ad..9e1460e6ff6a6 100644
--- a/src/testRunner/unittests/tsc/projectReferences.ts
+++ b/src/testRunner/unittests/tsc/projectReferences.ts
@@ -90,6 +90,35 @@ describe("unittests:: tsc:: projectReferences::", () => {
commandLineArgs: ["--p", "app", "--pretty", "false"],
});
+ verifyTsc({
+ scenario: "projectReferences",
+ subScenario: "referenced project with esnext module disallows synthetic default imports",
+ sys: () =>
+ TestServerHost.createWatchedSystem({
+ "/home/src/workspaces/project/lib/tsconfig.json": jsonToReadableText({
+ compilerOptions: {
+ composite: true,
+ declaration: true,
+ module: "esnext",
+ moduleResolution: "bundler",
+ },
+ }),
+ "/home/src/workspaces/project/lib/utils.ts": "export const test = () => 'test';",
+ "/home/src/workspaces/project/lib/utils.d.ts": "export declare const test: () => string;",
+ "/home/src/workspaces/project/app/tsconfig.json": jsonToReadableText({
+ compilerOptions: {
+ module: "esnext",
+ moduleResolution: "bundler",
+ },
+ references: [
+ { path: "../lib" },
+ ],
+ }),
+ "/home/src/workspaces/project/app/index.ts": `import Test from '../lib/utils';\nconsole.log(Test.test());`,
+ }),
+ commandLineArgs: ["--p", "app", "--pretty", "false"],
+ });
+
verifyTsc({
scenario: "projectReferences",
subScenario: "referencing ambient const enum from referenced project with preserveConstEnums",
diff --git a/tests/baselines/reference/tsc/projectReferences/referenced-project-with-esnext-module-disallows-synthetic-default-imports.js b/tests/baselines/reference/tsc/projectReferences/referenced-project-with-esnext-module-disallows-synthetic-default-imports.js
new file mode 100644
index 0000000000000..f76acb14c6ead
--- /dev/null
+++ b/tests/baselines/reference/tsc/projectReferences/referenced-project-with-esnext-module-disallows-synthetic-default-imports.js
@@ -0,0 +1,62 @@
+currentDirectory:: /home/src/workspaces/project useCaseSensitiveFileNames:: false
+Input::
+//// [/home/src/workspaces/project/lib/tsconfig.json]
+{
+ "compilerOptions": {
+ "composite": true,
+ "declaration": true,
+ "module": "esnext",
+ "moduleResolution": "bundler"
+ }
+}
+
+//// [/home/src/workspaces/project/lib/utils.ts]
+export const test = () => 'test';
+
+//// [/home/src/workspaces/project/lib/utils.d.ts]
+export declare const test: () => string;
+
+//// [/home/src/workspaces/project/app/tsconfig.json]
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "moduleResolution": "bundler"
+ },
+ "references": [
+ {
+ "path": "../lib"
+ }
+ ]
+}
+
+//// [/home/src/workspaces/project/app/index.ts]
+import Test from '../lib/utils';
+console.log(Test.test());
+
+//// [/home/src/tslibs/TS/Lib/lib.d.ts]
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+declare const console: { log(msg: any): void; };
+
+
+/home/src/tslibs/TS/Lib/tsc.js --p app --pretty false
+Output::
+app/index.ts(1,8): error TS1192: Module '"/home/src/workspaces/project/lib/utils"' has no default export.
+
+
+//// [/home/src/workspaces/project/app/index.js]
+import Test from '../lib/utils';
+console.log(Test.test());
+
+
+
+exitCode:: ExitStatus.DiagnosticsPresent_OutputsGenerated
diff --git a/tests/cases/fourslash/syntheticDefaultExportWithProjectReferences.ts b/tests/cases/fourslash/syntheticDefaultExportWithProjectReferences.ts
deleted file mode 100644
index 1581de77c9a58..0000000000000
--- a/tests/cases/fourslash/syntheticDefaultExportWithProjectReferences.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-///
-
-// Test case for synthetic default imports from declaration files.
-// Without project references, .d.ts files allow synthetic defaults due to ambiguity.
-// With project references (tested manually), the referenced project's options determine
-// if synthetic defaults are allowed.
-
-// @module: esnext
-// @moduleResolution: bundler
-
-// @Filename: /a/utils.d.ts
-//// export declare const test: () => string;
-
-// @Filename: /b/index.ts
-//// import Test from '../a/utils';
-////
-//// console.log(Test.test());
-
-// Without project references, this is allowed (no error) because we can't determine
-// if the original JS was ESM or CJS
-verify.getSemanticDiagnostics([]);
From d392ebdbb8bd078705d9ac8120174091fb8620ae Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 20:32:24 +0000
Subject: [PATCH 07/10] Use getEmitModuleKind for referenced project options
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
src/compiler/checker.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index ae8ec56cd6498..a05f9ec26bbd4 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -3824,7 +3824,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// If the referenced project's module format is ESM (ES2015 or later),
// it cannot have a synthetic default
- const referencedModuleKind = referencedOptions.module ?? ModuleKind.CommonJS;
+ const referencedModuleKind = getEmitModuleKind(referencedOptions);
if (referencedModuleKind >= ModuleKind.ES2015) {
return false;
}
From 5a2ac972f7d433465307e338a014792e950c47d8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 21:06:10 +0000
Subject: [PATCH 08/10] Use getEmitModuleFormatOfFile for project reference
declaration files
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
src/compiler/checker.ts | 26 ++++++++------------------
1 file changed, 8 insertions(+), 18 deletions(-)
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index a05f9ec26bbd4..6b4d89a74baca 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -3794,6 +3794,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// are ESM, there cannot be a synthetic default.
return false;
}
+ // For declaration files from project references, check if the referenced project's
+ // emit format is ESM, in which case there cannot be a synthetic default
+ if (file.isDeclarationFile && host.getRedirectFromSourceFile(file.path)) {
+ const targetModuleKind = host.getEmitModuleFormatOfFile(file);
+ if (usageMode === ModuleKind.ESNext && targetModuleKind >= ModuleKind.ES2015) {
+ return false;
+ }
+ }
}
if (!allowSyntheticDefaultImports) {
return false;
@@ -3812,24 +3820,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// it definitely is a module and does not have a synthetic default
return false;
}
- // If this is a declaration file from a project reference, check the referenced project's options
- // to determine if the module format is ESM
- if (file) {
- const redirect = host.getRedirectFromSourceFile(file.path);
- if (redirect?.resolvedRef) {
- const referencedOptions = redirect.resolvedRef.commandLine.options;
- // If the referenced project has allowSyntheticDefaultImports disabled, respect that
- if (!getAllowSyntheticDefaultImports(referencedOptions)) {
- return false;
- }
- // If the referenced project's module format is ESM (ES2015 or later),
- // it cannot have a synthetic default
- const referencedModuleKind = getEmitModuleKind(referencedOptions);
- if (referencedModuleKind >= ModuleKind.ES2015) {
- return false;
- }
- }
- }
// There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set
// Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member
// as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm
From 0d532eb66fa6522b3edfc0f12ad63c9a92fcc559 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 21:21:55 +0000
Subject: [PATCH 09/10] Apply getEmitModuleFormatOfFile only for project
reference declaration files
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
src/compiler/checker.ts | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 6b4d89a74baca..9cf5532396b0d 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -3794,12 +3794,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// are ESM, there cannot be a synthetic default.
return false;
}
- // For declaration files from project references, check if the referenced project's
- // emit format is ESM, in which case there cannot be a synthetic default
- if (file.isDeclarationFile && host.getRedirectFromSourceFile(file.path)) {
- const targetModuleKind = host.getEmitModuleFormatOfFile(file);
- if (usageMode === ModuleKind.ESNext && targetModuleKind >= ModuleKind.ES2015) {
- return false;
+ // For other files (not node16/nodenext with impliedNodeFormat), check if we can determine
+ // the module format from project references
+ if (!targetMode && file.isDeclarationFile) {
+ const redirect = host.getRedirectFromSourceFile(file.path);
+ if (redirect) {
+ // This is a declaration file from a project reference, so we can determine
+ // its module format from the referenced project's options
+ const targetModuleKind = host.getEmitModuleFormatOfFile(file);
+ if (usageMode === ModuleKind.ESNext && targetModuleKind >= ModuleKind.ES2015) {
+ return false;
+ }
}
}
}
From d7ac3ad13be50342217dba4049a8beb51213fe7b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 22 Jan 2026 22:04:51 +0000
Subject: [PATCH 10/10] Use ES module range check and try both redirect
mappings
Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
---
src/compiler/checker.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 9cf5532396b0d..5d446f6799418 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -3797,12 +3797,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// For other files (not node16/nodenext with impliedNodeFormat), check if we can determine
// the module format from project references
if (!targetMode && file.isDeclarationFile) {
- const redirect = host.getRedirectFromSourceFile(file.path);
+ // Try to get the project reference - try both source file mapping and output file mapping
+ // since declaration files can be mapped either way depending on how they're resolved
+ const redirect = host.getRedirectFromSourceFile(file.path) || host.getRedirectFromOutput(file.path);
if (redirect) {
// This is a declaration file from a project reference, so we can determine
// its module format from the referenced project's options
const targetModuleKind = host.getEmitModuleFormatOfFile(file);
- if (usageMode === ModuleKind.ESNext && targetModuleKind >= ModuleKind.ES2015) {
+ if (usageMode === ModuleKind.ESNext && ModuleKind.ES2015 <= targetModuleKind && targetModuleKind <= ModuleKind.ESNext) {
return false;
}
}