microsoft / microsoft/TypeScript

`checkJs` skips `.mjs`/`.cjs` beside a `.d.mts`/`.d.cts`, but not `.js` beside a `.d.ts`

Open
#64,312 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

### 🔎 Search Terms

checkJs, allowJs, .d.mts, .d.cts, declaration file adjacent to implementation, file silently dropped from program, extension priority

### 🕗 Version & Regression Information

Reproduces identically on 5.9.3, 6.0.3, and 7.1.0-dev.20260916.1. Long-standing, not a regression.

### ⏯ Playground Link

Not applicable: needs two sibling files on disk plus `moduleResolution: nodenext`.

### 💻 Code

Three directories, identical but for extensions. Same `tsconfig.json` in each; `package.json` has `"type": "module"` only for the `.mjs` case.

`index.js` / `index.mjs` / `index.cjs`:

```js
export const n = 1;

const bad = null;
bad.a.b.c();
```

`index.d.ts` / `index.d.mts` / `index.d.cts`:

```ts
export declare const n: number;
```

```json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"allowJs": true,
"checkJs": true,
"strict": true,
"noEmit": true
}
}
```

### 🙁 Actual behavior

```
index.js + index.d.ts -> error TS18047: 'bad' is possibly 'null'.
index.mjs + index.d.mts -> (no output)
index.cjs + index.d.cts -> (no output)
```

The same authoring relationship — a hand-written declaration file beside its implementation — produces opposite outcomes based only on the file extension.

In the `.mjs`/`.cjs` cases the implementation is not merely unchecked, it is absent from the program: `--listFiles` reports only `index.d.mts`. In the `.js` case `--listFiles` reports both `index.d.ts` and `index.js`.

### 🙂 Expected behavior

The three rows should agree. Either all of them check the implementation, or none do.

I'd expect the `.js` row to be the correct one — the declaration file describes the module for consumers, and `checkJs` still checks the implementation — but consistency matters more than which way it goes.

### Additional information

#47796 reported the `.mjs`/`.mts` variant and was closed as "Working as Intended", on the rationale that the JS file is a build artifact of the TS file, and that "the compiler only automatically pulls in one copy of a file with the same basename (the most TS-y one)".

That rationale doesn't extend to this case:

- A `.d.mts` is not something a `.mjs` is emitted *from*, so the build-artifact framing doesn't apply. `.d.ts` + `.js` is the same relationship, and there the compiler does pull in both.
- Neither does the one-copy-per-basename rule, for the same reason — `index.js` and `index.d.ts` are both in the program.

The failure is silent, which is what makes it expensive: a package shipping `.mjs`/`.cjs` with adjacent hand-written declarations gets no checking of its implementation at all, while every sibling module without a declaration file is checked normally. `tsc` exits 0 and CI is green. I found this only by deliberately breaking a file and noticing the build still passed. Per #47796, VS Code *does* report these errors, so the editor and CI silently disagree.

#64098 is the same silent-drop shape from wildcard `include` (`hasFileWithHigherPriorityExtension`), if the two are related.

So, concretely:

1. Make the three rows consistent, or
2. if this is intended, document which sibling extensions suppress checking, and emit a diagnostic when an input file is dropped from the program for this reason.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the three-directory reproduction with tsc --listFiles and compare the .js/.d.ts, .mjs/.d.mts, and .cjs/.d.cts results. Trace the extension-priority behavior around hasFileWithHigherPriorityExtension, referenced from #64098. Done means the three cases behave consistently, with coverage for the silent-drop scenario.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.