From aa470d55df15af42726e9a1b842e20ceeeef6444 Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Tue, 29 Sep 2026 16:43:24 +0530 Subject: [PATCH 1/4] Fix checkJs behavior for .mjs and .cjs files next to declarations Fixes #64312 --- tsc/internal/tsoptions/tsconfigparsing.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tsc/internal/tsoptions/tsconfigparsing.go b/tsc/internal/tsoptions/tsconfigparsing.go index 8f50493b2747c..bff52e739f39a 100644 --- a/tsc/internal/tsoptions/tsconfigparsing.go +++ b/tsc/internal/tsoptions/tsconfigparsing.go @@ -1890,7 +1890,9 @@ func hasFileWithHigherPriorityExtension(file string, extensions [][]string, hasF return false } if hasFile(tspath.ChangeExtension(file, ext)) { - if ext == tspath.ExtensionDts && (tspath.FileExtensionIs(file, tspath.ExtensionJs) || tspath.FileExtensionIs(file, tspath.ExtensionJsx)) { + if (ext == tspath.ExtensionDts && (tspath.FileExtensionIs(file, tspath.ExtensionJs) || tspath.FileExtensionIs(file, tspath.ExtensionJsx))) || + (ext == tspath.ExtensionDmts && tspath.FileExtensionIs(file, tspath.ExtensionMjs)) || + (ext == tspath.ExtensionDcts && tspath.FileExtensionIs(file, tspath.ExtensionCjs)) { // LEGACY BEHAVIOR: An off-by-one bug somewhere in the extension priority system for wildcard module loading allowed declaration // files to be loaded alongside their js(x) counterparts. We regard this as generally undesirable, but retain the behavior to // prevent breakage. @@ -1919,6 +1921,11 @@ func removeWildcardFilesWithLowerPriorityExtension(file string, wildcardFiles *c if tspath.FileExtensionIs(file, ext) { return } + if (tspath.FileExtensionIs(file, tspath.ExtensionDts) && (ext == tspath.ExtensionJs || ext == tspath.ExtensionJsx)) || + (tspath.FileExtensionIs(file, tspath.ExtensionDmts) && ext == tspath.ExtensionMjs) || + (tspath.FileExtensionIs(file, tspath.ExtensionDcts) && ext == tspath.ExtensionCjs) { + continue + } lowerPriorityPath := keyMapper(tspath.ChangeExtension(file, ext)) wildcardFiles.Delete(lowerPriorityPath) } From b9da8c7b429b92c9d9fcdda5b5117a930076b43f Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Tue, 29 Sep 2026 21:33:28 +0530 Subject: [PATCH 2/4] test: add regression test for checkJs with ESM/CJS extensions (#64312) --- .../checkJsExtensionPriority.errors.txt | 53 +++++++++++++++ .../compiler/checkJsExtensionPriority.symbols | 44 ++++++++++++ .../compiler/checkJsExtensionPriority.types | 68 +++++++++++++++++++ .../compiler/checkJsExtensionPriority.ts | 41 +++++++++++ 4 files changed, 206 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.types create mode 100644 tsc/testdata/tests/cases/compiler/checkJsExtensionPriority.ts diff --git a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.errors.txt b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.errors.txt new file mode 100644 index 0000000000000..5ce088463dd63 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.errors.txt @@ -0,0 +1,53 @@ +/a.js(3,1): error TS18047: 'bad' is possibly 'null'. +/b.mjs(3,1): error TS18047: 'bad' is possibly 'null'. +/c.cjs(3,1): error TS18047: 'bad' is possibly 'null'. + + +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "allowJs": true, + "checkJs": true, + "strict": true, + "noEmit": true + } + } + +==== /a.js (1 errors) ==== + export const n = 1; + const bad = null; + bad.a.b.c(); + ~~~ +!!! error TS18047: 'bad' is possibly 'null'. + +==== /a.d.ts (0 errors) ==== + export declare const n: number; + +==== /b.mjs (1 errors) ==== + export const n = 1; + const bad = null; + bad.a.b.c(); + ~~~ +!!! error TS18047: 'bad' is possibly 'null'. + +==== /b.d.mts (0 errors) ==== + export declare const n: number; + +==== /c.cjs (1 errors) ==== + export const n = 1; + const bad = null; + bad.a.b.c(); + ~~~ +!!! error TS18047: 'bad' is possibly 'null'. + +==== /c.d.cts (0 errors) ==== + export declare const n: number; + +==== /package.json (0 errors) ==== + { + "type": "module" + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.symbols b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.symbols new file mode 100644 index 0000000000000..2fe27d87c3956 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.symbols @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/checkJsExtensionPriority.ts] //// + +=== /a.js === +export const n = 1; +>n : Symbol(n, Decl(a.js, 0, 12)) + +const bad = null; +>bad : Symbol(bad, Decl(a.js, 1, 5)) + +bad.a.b.c(); +>bad : Symbol(bad, Decl(a.js, 1, 5)) + +=== /a.d.ts === +export declare const n: number; +>n : Symbol(n, Decl(a.d.ts, 0, 20)) + +=== /b.mjs === +export const n = 1; +>n : Symbol(n, Decl(b.mjs, 0, 12)) + +const bad = null; +>bad : Symbol(bad, Decl(b.mjs, 1, 5)) + +bad.a.b.c(); +>bad : Symbol(bad, Decl(b.mjs, 1, 5)) + +=== /b.d.mts === +export declare const n: number; +>n : Symbol(n, Decl(b.d.mts, 0, 20)) + +=== /c.cjs === +export const n = 1; +>n : Symbol(n, Decl(c.cjs, 0, 12)) + +const bad = null; +>bad : Symbol(bad, Decl(c.cjs, 1, 5)) + +bad.a.b.c(); +>bad : Symbol(bad, Decl(c.cjs, 1, 5)) + +=== /c.d.cts === +export declare const n: number; +>n : Symbol(n, Decl(c.d.cts, 0, 20)) + diff --git a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.types b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.types new file mode 100644 index 0000000000000..7d3dc28a516c9 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.types @@ -0,0 +1,68 @@ +//// [tests/cases/compiler/checkJsExtensionPriority.ts] //// + +=== /a.js === +export const n = 1; +>n : 1 +>1 : 1 + +const bad = null; +>bad : null + +bad.a.b.c(); +>bad.a.b.c() : any +>bad.a.b.c : any +>bad.a.b : any +>bad.a : any +>bad : null +>a : any +>b : any +>c : any + +=== /a.d.ts === +export declare const n: number; +>n : number + +=== /b.mjs === +export const n = 1; +>n : 1 +>1 : 1 + +const bad = null; +>bad : null + +bad.a.b.c(); +>bad.a.b.c() : any +>bad.a.b.c : any +>bad.a.b : any +>bad.a : any +>bad : null +>a : any +>b : any +>c : any + +=== /b.d.mts === +export declare const n: number; +>n : number + +=== /c.cjs === +export const n = 1; +>n : 1 +>1 : 1 + +const bad = null; +>bad : null + +bad.a.b.c(); +>bad.a.b.c() : any +>bad.a.b.c : any +>bad.a.b : any +>bad.a : any +>bad : null +>a : any +>b : any +>c : any + +=== /c.d.cts === +export declare const n: number; +>n : number + diff --git a/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority.ts b/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority.ts new file mode 100644 index 0000000000000..1bc1127c62b85 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority.ts @@ -0,0 +1,41 @@ +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "allowJs": true, + "checkJs": true, + "strict": true, + "noEmit": true + } +} + +// @Filename: /package.json +{ + "type": "module" +} + +// @Filename: /a.js +export const n = 1; +const bad = null; +bad.a.b.c(); + +// @Filename: /a.d.ts +export declare const n: number; + +// @Filename: /b.mjs +export const n = 1; +const bad = null; +bad.a.b.c(); + +// @Filename: /b.d.mts +export declare const n: number; + +// @Filename: /c.cjs +export const n = 1; +const bad = null; +bad.a.b.c(); + +// @Filename: /c.d.cts +export declare const n: number; From 61dd8180650b8afae06113d630e378f3a794967b Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Tue, 29 Sep 2026 22:26:17 +0530 Subject: [PATCH 3/4] test: add explicit coverage for include ordering #63523 --- .../compiler/checkJsExtensionPriority.ts | 1 + .../checkJsExtensionPriority_includeOrder.ts | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tsc/testdata/tests/cases/compiler/checkJsExtensionPriority_includeOrder.ts diff --git a/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority.ts b/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority.ts index 1bc1127c62b85..b93888dac4e54 100644 --- a/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority.ts +++ b/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority.ts @@ -39,3 +39,4 @@ bad.a.b.c(); // @Filename: /c.d.cts export declare const n: number; + diff --git a/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority_includeOrder.ts b/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority_includeOrder.ts new file mode 100644 index 0000000000000..88151cbbbebc9 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/checkJsExtensionPriority_includeOrder.ts @@ -0,0 +1,46 @@ +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "allowJs": true, + "checkJs": true, + "strict": true, + "noEmit": true + }, + "include": [ + "**/*.js", + "**/*.d.ts", + "**/*.mjs", + "**/*.d.mts", + "**/*.cjs", + "**/*.d.cts" + ] +} + +// @Filename: /package.json +{ + "type": "module" +} + +// @Filename: /a.js +const bad = null; +bad.a(); + +// @Filename: /a.d.ts +export declare const n: number; + +// @Filename: /b.mjs +const bad = null; +bad.b(); + +// @Filename: /b.d.mts +export declare const n: number; + +// @Filename: /c.cjs +const bad = null; +bad.c(); + +// @Filename: /c.d.cts +export declare const n: number; From 466fc94ba8b6b77b46c301ef0090c56eac589b48 Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Thu, 1 Oct 2026 00:20:03 +0530 Subject: [PATCH 4/4] fix: update wildcard resolution to drop legacy .js/.d.ts exception per maintainer feedback --- tsc/internal/tsoptions/tsconfigparsing.go | 13 ----- .../checkJsExtensionPriority.errors.txt | 53 ------------------ .../compiler/checkJsExtensionPriority.symbols | 31 +---------- .../compiler/checkJsExtensionPriority.types | 55 +------------------ ...ckJsExtensionPriority_includeOrder.symbols | 14 +++++ ...heckJsExtensionPriority_includeOrder.types | 14 +++++ 6 files changed, 30 insertions(+), 150 deletions(-) delete mode 100644 tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority_includeOrder.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority_includeOrder.types diff --git a/tsc/internal/tsoptions/tsconfigparsing.go b/tsc/internal/tsoptions/tsconfigparsing.go index bff52e739f39a..71f27d6a74b1c 100644 --- a/tsc/internal/tsoptions/tsconfigparsing.go +++ b/tsc/internal/tsoptions/tsconfigparsing.go @@ -1890,14 +1890,6 @@ func hasFileWithHigherPriorityExtension(file string, extensions [][]string, hasF return false } if hasFile(tspath.ChangeExtension(file, ext)) { - if (ext == tspath.ExtensionDts && (tspath.FileExtensionIs(file, tspath.ExtensionJs) || tspath.FileExtensionIs(file, tspath.ExtensionJsx))) || - (ext == tspath.ExtensionDmts && tspath.FileExtensionIs(file, tspath.ExtensionMjs)) || - (ext == tspath.ExtensionDcts && tspath.FileExtensionIs(file, tspath.ExtensionCjs)) { - // LEGACY BEHAVIOR: An off-by-one bug somewhere in the extension priority system for wildcard module loading allowed declaration - // files to be loaded alongside their js(x) counterparts. We regard this as generally undesirable, but retain the behavior to - // prevent breakage. - continue - } return true } } @@ -1921,11 +1913,6 @@ func removeWildcardFilesWithLowerPriorityExtension(file string, wildcardFiles *c if tspath.FileExtensionIs(file, ext) { return } - if (tspath.FileExtensionIs(file, tspath.ExtensionDts) && (ext == tspath.ExtensionJs || ext == tspath.ExtensionJsx)) || - (tspath.FileExtensionIs(file, tspath.ExtensionDmts) && ext == tspath.ExtensionMjs) || - (tspath.FileExtensionIs(file, tspath.ExtensionDcts) && ext == tspath.ExtensionCjs) { - continue - } lowerPriorityPath := keyMapper(tspath.ChangeExtension(file, ext)) wildcardFiles.Delete(lowerPriorityPath) } diff --git a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.errors.txt b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.errors.txt deleted file mode 100644 index 5ce088463dd63..0000000000000 --- a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.errors.txt +++ /dev/null @@ -1,53 +0,0 @@ -/a.js(3,1): error TS18047: 'bad' is possibly 'null'. -/b.mjs(3,1): error TS18047: 'bad' is possibly 'null'. -/c.cjs(3,1): error TS18047: 'bad' is possibly 'null'. - - -==== /tsconfig.json (0 errors) ==== - { - "compilerOptions": { - "target": "esnext", - "module": "nodenext", - "moduleResolution": "nodenext", - "allowJs": true, - "checkJs": true, - "strict": true, - "noEmit": true - } - } - -==== /a.js (1 errors) ==== - export const n = 1; - const bad = null; - bad.a.b.c(); - ~~~ -!!! error TS18047: 'bad' is possibly 'null'. - -==== /a.d.ts (0 errors) ==== - export declare const n: number; - -==== /b.mjs (1 errors) ==== - export const n = 1; - const bad = null; - bad.a.b.c(); - ~~~ -!!! error TS18047: 'bad' is possibly 'null'. - -==== /b.d.mts (0 errors) ==== - export declare const n: number; - -==== /c.cjs (1 errors) ==== - export const n = 1; - const bad = null; - bad.a.b.c(); - ~~~ -!!! error TS18047: 'bad' is possibly 'null'. - -==== /c.d.cts (0 errors) ==== - export declare const n: number; - -==== /package.json (0 errors) ==== - { - "type": "module" - } - \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.symbols b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.symbols index 2fe27d87c3956..7b93d669d151a 100644 --- a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.symbols +++ b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.symbols @@ -1,44 +1,15 @@ //// [tests/cases/compiler/checkJsExtensionPriority.ts] //// -=== /a.js === -export const n = 1; ->n : Symbol(n, Decl(a.js, 0, 12)) - -const bad = null; ->bad : Symbol(bad, Decl(a.js, 1, 5)) - -bad.a.b.c(); ->bad : Symbol(bad, Decl(a.js, 1, 5)) - === /a.d.ts === export declare const n: number; >n : Symbol(n, Decl(a.d.ts, 0, 20)) -=== /b.mjs === -export const n = 1; ->n : Symbol(n, Decl(b.mjs, 0, 12)) - -const bad = null; ->bad : Symbol(bad, Decl(b.mjs, 1, 5)) - -bad.a.b.c(); ->bad : Symbol(bad, Decl(b.mjs, 1, 5)) - === /b.d.mts === export declare const n: number; >n : Symbol(n, Decl(b.d.mts, 0, 20)) -=== /c.cjs === -export const n = 1; ->n : Symbol(n, Decl(c.cjs, 0, 12)) - -const bad = null; ->bad : Symbol(bad, Decl(c.cjs, 1, 5)) - -bad.a.b.c(); ->bad : Symbol(bad, Decl(c.cjs, 1, 5)) - === /c.d.cts === export declare const n: number; >n : Symbol(n, Decl(c.d.cts, 0, 20)) + diff --git a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.types b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.types index 7d3dc28a516c9..e2d4db7f44368 100644 --- a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.types +++ b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority.types @@ -1,68 +1,15 @@ //// [tests/cases/compiler/checkJsExtensionPriority.ts] //// -=== /a.js === -export const n = 1; ->n : 1 ->1 : 1 - -const bad = null; ->bad : null - -bad.a.b.c(); ->bad.a.b.c() : any ->bad.a.b.c : any ->bad.a.b : any ->bad.a : any ->bad : null ->a : any ->b : any ->c : any - === /a.d.ts === export declare const n: number; >n : number -=== /b.mjs === -export const n = 1; ->n : 1 ->1 : 1 - -const bad = null; ->bad : null - -bad.a.b.c(); ->bad.a.b.c() : any ->bad.a.b.c : any ->bad.a.b : any ->bad.a : any ->bad : null ->a : any ->b : any ->c : any - === /b.d.mts === export declare const n: number; >n : number -=== /c.cjs === -export const n = 1; ->n : 1 ->1 : 1 - -const bad = null; ->bad : null - -bad.a.b.c(); ->bad.a.b.c() : any ->bad.a.b.c : any ->bad.a.b : any ->bad.a : any ->bad : null ->a : any ->b : any ->c : any - === /c.d.cts === export declare const n: number; >n : number + diff --git a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority_includeOrder.symbols b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority_includeOrder.symbols new file mode 100644 index 0000000000000..a18c297699957 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority_includeOrder.symbols @@ -0,0 +1,14 @@ +//// [tests/cases/compiler/checkJsExtensionPriority_includeOrder.ts] //// + +=== /a.d.ts === +export declare const n: number; +>n : Symbol(n, Decl(a.d.ts, 0, 20)) + +=== /b.d.mts === +export declare const n: number; +>n : Symbol(n, Decl(b.d.mts, 0, 20)) + +=== /c.d.cts === +export declare const n: number; +>n : Symbol(n, Decl(c.d.cts, 0, 20)) + diff --git a/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority_includeOrder.types b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority_includeOrder.types new file mode 100644 index 0000000000000..9e63783c747bb --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/checkJsExtensionPriority_includeOrder.types @@ -0,0 +1,14 @@ +//// [tests/cases/compiler/checkJsExtensionPriority_includeOrder.ts] //// + +=== /a.d.ts === +export declare const n: number; +>n : number + +=== /b.d.mts === +export declare const n: number; +>n : number + +=== /c.d.cts === +export declare const n: number; +>n : number +