Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3794,6 +3794,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// are ESM, there cannot be a synthetic default.
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) {
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For declaration files from project references, consider using getRedirectFromOutput instead of getRedirectFromSourceFile. The function getRedirectFromSourceFile maps source file paths to project references, while getRedirectFromOutput maps output file paths to project references. Since this code block handles declaration files (outputs), using getRedirectFromOutput(file.path)?.resolvedRef would be more semantically correct and consistent with the pattern in getRedirectReferenceForResolution (program.ts:2112).

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot investigate whether this suggestion is correct, and apply the other suggestion above (#63038 (comment))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigated and found that getRedirectFromOutput alone doesn't work for all test cases. Using the pattern from getRedirectReferenceForResolution, I now try both getRedirectFromSourceFile || getRedirectFromOutput since declaration files can be mapped either way depending on resolution context. This ensures the check works regardless of how the file was resolved. (commit d7ac3ad)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage now proves this works the same whether you try to import the input .ts file or output .d.ts file of the referenced project, so current implementation seems good

// 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;
}
}
}
}
if (!allowSyntheticDefaultImports) {
return false;
Expand Down
29 changes: 29 additions & 0 deletions src/testRunner/unittests/tsc/projectReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +89 to +90
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The presence of these errors was actually always asserted in the test content (lines 66–67 in the baseline). Not sure if this used to work and regressed without noticing or what, but this baseline change matches the original intention.



//// [/home/src/workspaces/project/app/dist/local.js]
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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
Loading