diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 9dac7a0507b..47e7511e49f 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -14465,6 +14465,20 @@ func (c *Checker) canHaveSyntheticDefault(file *ast.Node, moduleSymbol *ast.Symb // 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 == core.ModuleKindNone && file.AsSourceFile().IsDeclarationFile { + // 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 + if c.program.GetRedirectForResolution(file.AsSourceFile()) != nil || c.program.GetProjectReferenceFromOutputDts(file.AsSourceFile().Path()) != nil { + // This is a declaration file from a project reference, so we can determine + // its module format from the referenced project's options + targetModuleKind := c.program.GetEmitModuleFormatOfFile(file.AsSourceFile()) + if usageMode == core.ModuleKindESNext && core.ModuleKindES2015 <= targetModuleKind && targetModuleKind <= core.ModuleKindESNext { + return false + } + } + } } if !c.allowSyntheticDefaultImports { return false diff --git a/internal/execute/tsctests/tsc_test.go b/internal/execute/tsctests/tsc_test.go index ad71e344f74..e5db89897bc 100644 --- a/internal/execute/tsctests/tsc_test.go +++ b/internal/execute/tsctests/tsc_test.go @@ -3939,6 +3939,41 @@ func TestTscProjectReferences(t *testing.T) { }, commandLineArgs: []string{"--p", "app", "--pretty", "false"}, }, + { + subScenario: "referenced project with esnext module disallows synthetic default imports", + files: FileMap{ + "/home/src/workspaces/project/lib/tsconfig.json": stringtestutil.Dedent(` + { + "compilerOptions": { + "composite": true, + "declaration": true, + "module": "esnext", + "moduleResolution": "bundler", + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"] + }`), + "/home/src/workspaces/project/lib/src/utils.ts": "export const test = () => 'test';", + "/home/src/workspaces/project/lib/dist/utils.d.ts": "export declare const test: () => string;", + "/home/src/workspaces/project/app/tsconfig.json": stringtestutil.Dedent(` + { + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler" + }, + "references": [ + { "path": "../lib" } + ] + }`), + "/home/src/workspaces/project/app/index.ts": stringtestutil.Dedent(` + import TestSrc from '../lib/src/utils'; // Error + import TestDecl from '../lib/dist/utils'; // Error + console.log(TestSrc.test()); + console.log(TestDecl.test());`), + }, + commandLineArgs: []string{"--p", "app", "--pretty", "false"}, + }, { subScenario: "referencing ambient const enum from referenced project with preserveConstEnums", files: FileMap{ diff --git a/testdata/baselines/reference/tsbuild/javascriptProjectEmit/loads-js-based-projects-with-non-moved-json-files-and-emits-them-correctly.js b/testdata/baselines/reference/tsbuild/javascriptProjectEmit/loads-js-based-projects-with-non-moved-json-files-and-emits-them-correctly.js index f0454940016..6cc2746e15f 100644 --- a/testdata/baselines/reference/tsbuild/javascriptProjectEmit/loads-js-based-projects-with-non-moved-json-files-and-emits-them-correctly.js +++ b/testdata/baselines/reference/tsbuild/javascriptProjectEmit/loads-js-based-projects-with-non-moved-json-files-and-emits-them-correctly.js @@ -91,8 +91,17 @@ Output:: 2 export = x;   ~~~~~~~~~~~ +sub-project/index.js:1:8 - error TS1192: Module '"/home/src/workspaces/solution/common/index"' has no default export. -Found 2 errors in the same file, starting at: common/index.ts:1 +1 import mod from '../common'; +   ~~~ + + +Found 3 errors in 2 files. + +Errors Files + 2 common/index.ts:1 + 1 sub-project/index.js:1 //// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* /// @@ -119,9 +128,7 @@ interface Symbol { declare const console: { log(msg: any): void; }; //// [/home/src/workspaces/out/sub-project-2/index.d.ts] *new* export declare function getVar(): { - key: { - val: number; - }; + key: any; }; //// [/home/src/workspaces/out/sub-project-2/index.js] *new* @@ -134,7 +141,7 @@ export function getVar() { } //// [/home/src/workspaces/out/sub-project-2/tsconfig.tsbuildinfo] *new* -{"version":"FakeTSVersion","root":[3],"fileNames":["lib.es2025.full.d.ts","../sub-project/index.d.ts","../../solution/sub-project-2/index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},"b13b16e08eb0717669fa55818828b2cb-export declare const m: {\n val: number;\n};\n",{"version":"56ecb5738c72a131a1514873df723721-import { m } from '../sub-project/index';\n\nconst variable = {\n key: m,\n};\n\nexport function getVar() {\n return variable;\n}","signature":"f6a0b1edad82fddabb5c98ad5da1660d-export declare function getVar(): {\n key: {\n val: number;\n };\n};\n","impliedNodeFormat":1}],"fileIdsList":[[2]],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"esModuleInterop":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"referencedMap":[[3,1]],"latestChangedDtsFile":"./index.d.ts"} +{"version":"FakeTSVersion","root":[3],"fileNames":["lib.es2025.full.d.ts","../sub-project/index.d.ts","../../solution/sub-project-2/index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},"ce0233db1f3aabecabdb072a4f4c8d1e-export declare const m: any;\n",{"version":"56ecb5738c72a131a1514873df723721-import { m } from '../sub-project/index';\n\nconst variable = {\n key: m,\n};\n\nexport function getVar() {\n return variable;\n}","signature":"cb28999e3dc9364c7b77f755d2449d70-export declare function getVar(): {\n key: any;\n};\n","impliedNodeFormat":1}],"fileIdsList":[[2]],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"esModuleInterop":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"referencedMap":[[3,1]],"latestChangedDtsFile":"./index.d.ts"} //// [/home/src/workspaces/out/sub-project-2/tsconfig.tsbuildinfo.readable.baseline.txt] *new* { "version": "FakeTSVersion", @@ -166,18 +173,18 @@ export function getVar() { }, { "fileName": "../sub-project/index.d.ts", - "version": "b13b16e08eb0717669fa55818828b2cb-export declare const m: {\n val: number;\n};\n", - "signature": "b13b16e08eb0717669fa55818828b2cb-export declare const m: {\n val: number;\n};\n", + "version": "ce0233db1f3aabecabdb072a4f4c8d1e-export declare const m: any;\n", + "signature": "ce0233db1f3aabecabdb072a4f4c8d1e-export declare const m: any;\n", "impliedNodeFormat": "CommonJS" }, { "fileName": "../../solution/sub-project-2/index.js", "version": "56ecb5738c72a131a1514873df723721-import { m } from '../sub-project/index';\n\nconst variable = {\n key: m,\n};\n\nexport function getVar() {\n return variable;\n}", - "signature": "f6a0b1edad82fddabb5c98ad5da1660d-export declare function getVar(): {\n key: {\n val: number;\n };\n};\n", + "signature": "cb28999e3dc9364c7b77f755d2449d70-export declare function getVar(): {\n key: any;\n};\n", "impliedNodeFormat": "CommonJS", "original": { "version": "56ecb5738c72a131a1514873df723721-import { m } from '../sub-project/index';\n\nconst variable = {\n key: m,\n};\n\nexport function getVar() {\n return variable;\n}", - "signature": "f6a0b1edad82fddabb5c98ad5da1660d-export declare function getVar(): {\n key: {\n val: number;\n };\n};\n", + "signature": "cb28999e3dc9364c7b77f755d2449d70-export declare function getVar(): {\n key: any;\n};\n", "impliedNodeFormat": 1 } } @@ -203,19 +210,17 @@ export function getVar() { ] }, "latestChangedDtsFile": "./index.d.ts", - "size": 1592 + "size": 1546 } //// [/home/src/workspaces/out/sub-project/index.d.ts] *new* -export declare const m: { - val: number; -}; +export declare const m: any; //// [/home/src/workspaces/out/sub-project/index.js] *new* import mod from '../common'; export const m = mod; //// [/home/src/workspaces/out/sub-project/tsconfig.tsbuildinfo] *new* -{"version":"FakeTSVersion","root":[4],"fileNames":["lib.es2025.full.d.ts","../../solution/common/obj.json","../../solution/common/index.d.ts","../../solution/sub-project/index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"d47747c9a3b20f363d6de91e2bd8ed62-{\n \"val\": 42,\n}"},"641f5162aeaa035322008b19df89c663-import x = require(\"./obj.json\");\nexport = x;\n",{"version":"4c69d0c670e9dc788b5e107f277aa8ab-import mod from '../common';\n\nexport const m = mod;","signature":"b13b16e08eb0717669fa55818828b2cb-export declare const m: {\n val: number;\n};\n","impliedNodeFormat":1}],"fileIdsList":[[2],[3]],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"esModuleInterop":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"referencedMap":[[3,1],[4,2]],"latestChangedDtsFile":"./index.d.ts"} +{"version":"FakeTSVersion","root":[4],"fileNames":["lib.es2025.full.d.ts","../../solution/common/obj.json","../../solution/common/index.d.ts","../../solution/sub-project/index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"d47747c9a3b20f363d6de91e2bd8ed62-{\n \"val\": 42,\n}"},"641f5162aeaa035322008b19df89c663-import x = require(\"./obj.json\");\nexport = x;\n",{"version":"4c69d0c670e9dc788b5e107f277aa8ab-import mod from '../common';\n\nexport const m = mod;","signature":"ce0233db1f3aabecabdb072a4f4c8d1e-export declare const m: any;\n","impliedNodeFormat":1}],"fileIdsList":[[2],[3]],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"esModuleInterop":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"referencedMap":[[3,1],[4,2]],"semanticDiagnosticsPerFile":[[4,[{"pos":7,"end":10,"code":1192,"category":1,"messageKey":"Module_0_has_no_default_export_1192","messageArgs":["\"/home/src/workspaces/solution/common/index\""]}]]],"latestChangedDtsFile":"./index.d.ts"} //// [/home/src/workspaces/out/sub-project/tsconfig.tsbuildinfo.readable.baseline.txt] *new* { "version": "FakeTSVersion", @@ -264,11 +269,11 @@ export const m = mod; { "fileName": "../../solution/sub-project/index.js", "version": "4c69d0c670e9dc788b5e107f277aa8ab-import mod from '../common';\n\nexport const m = mod;", - "signature": "b13b16e08eb0717669fa55818828b2cb-export declare const m: {\n val: number;\n};\n", + "signature": "ce0233db1f3aabecabdb072a4f4c8d1e-export declare const m: any;\n", "impliedNodeFormat": "CommonJS", "original": { "version": "4c69d0c670e9dc788b5e107f277aa8ab-import mod from '../common';\n\nexport const m = mod;", - "signature": "b13b16e08eb0717669fa55818828b2cb-export declare const m: {\n val: number;\n};\n", + "signature": "ce0233db1f3aabecabdb072a4f4c8d1e-export declare const m: any;\n", "impliedNodeFormat": 1 } } @@ -299,8 +304,25 @@ export const m = mod; "../../solution/common/index.d.ts" ] }, + "semanticDiagnosticsPerFile": [ + [ + "../../solution/sub-project/index.js", + [ + { + "pos": 7, + "end": 10, + "code": 1192, + "category": 1, + "messageKey": "Module_0_has_no_default_export_1192", + "messageArgs": [ + "\"/home/src/workspaces/solution/common/index\"" + ] + } + ] + ] + ], "latestChangedDtsFile": "./index.d.ts", - "size": 1595 + "size": 1773 } //// [/home/src/workspaces/solution/common/index.d.ts] *new* import x = require("./obj.json"); diff --git a/testdata/baselines/reference/tsc/projectReferences/default-import-interop-uses-referenced-project-settings.js b/testdata/baselines/reference/tsc/projectReferences/default-import-interop-uses-referenced-project-settings.js index 4d2bce478b1..d1431013b4a 100644 --- a/testdata/baselines/reference/tsc/projectReferences/default-import-interop-uses-referenced-project-settings.js +++ b/testdata/baselines/reference/tsc/projectReferences/default-import-interop-uses-referenced-project-settings.js @@ -57,6 +57,8 @@ ExitStatus:: DiagnosticsPresent_OutputsGenerated Output:: app/src/index.ts(1,8): 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(2,8): 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(3,8): error TS1192: Module '"/home/src/workspaces/project/lib/dist/a"' has no default export. +app/src/index.ts(4,8): error TS1192: Module '"/home/src/workspaces/project/lib/dist/a"' has no default export. //// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* /// interface Boolean {} diff --git a/testdata/baselines/reference/tsc/projectReferences/referenced-project-with-esnext-module-disallows-synthetic-default-imports.js b/testdata/baselines/reference/tsc/projectReferences/referenced-project-with-esnext-module-disallows-synthetic-default-imports.js new file mode 100644 index 00000000000..cbed14ee8f7 --- /dev/null +++ b/testdata/baselines/reference/tsc/projectReferences/referenced-project-with-esnext-module-disallows-synthetic-default-imports.js @@ -0,0 +1,70 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/app/index.ts] *new* +import TestSrc from '../lib/src/utils'; // Error +import TestDecl from '../lib/dist/utils'; // Error +console.log(TestSrc.test()); +console.log(TestDecl.test()); +//// [/home/src/workspaces/project/app/tsconfig.json] *new* +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler" + }, + "references": [ + { "path": "../lib" } + ] +} +//// [/home/src/workspaces/project/lib/dist/utils.d.ts] *new* +export declare const test: () => string; +//// [/home/src/workspaces/project/lib/src/utils.ts] *new* +export const test = () => 'test'; +//// [/home/src/workspaces/project/lib/tsconfig.json] *new* +{ + "compilerOptions": { + "composite": true, + "declaration": true, + "module": "esnext", + "moduleResolution": "bundler", + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"] +} + +tsgo --p app --pretty false +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +app/index.ts(1,8): error TS1192: Module '"/home/src/workspaces/project/lib/dist/utils"' has no default export. +app/index.ts(2,8): error TS1192: Module '"/home/src/workspaces/project/lib/dist/utils"' has no default export. +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +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 {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/app/index.js] *new* +import TestSrc from '../lib/src/utils'; // Error +import TestDecl from '../lib/dist/utils'; // Error +console.log(TestSrc.test()); +console.log(TestDecl.test()); + +