microsoft / microsoft/TypeScript

tsconfig `include` silently drops `Foo.tsx` when `foo.ts` exists, on a case-insensitive filesystem

Ouverte
#64,098 2 commentaires 1 réaction 2 personnes assignées Voir sur GitHub

@RyanCavanaugh y travaille déjà.

Depuis le 4/9/2026.

  • #64176 par @copilot-swe-agent — ouverte
Bug
Langage dominant
Go
Étoiles
111k
Forks
14.3k
Merge moyen
2 j 4 h
PR mergées (30 j)
132

Description

### 🔎 Search Terms

include, wildcard, extension priority, case-insensitive file system, `hasFileWithHigherPriorityExtension`, `.ts` vs `.tsx`, file silently excluded from program

### 🕗 Version & Regression Information

Reproduces on **6.0.3** and on **7.0.2** (native). This is long-standing behaviour, not a recent regression — I did not bisect it.

### ⏯ Playground Link

Not applicable: the bug is in `tsconfig` file selection, so it needs two files on disk.

### 💻 Code

Three files, no dependencies:

`src/brand.ts`
```ts
export const brand = "ok";
```

`src/Brand.tsx` — note the capital `B`
```tsx
// A deliberate type error. If this file is in the program, tsc MUST report it.
export const oops: number = "not a number";
```

`tsconfig.json`
```json
{
"compilerOptions": { "noEmit": true, "strict": true, "jsx": "react-jsx", "types": [] },
"include": ["src/**/*"]
}
```

Run `tsc --showConfig -p tsconfig.json` and `tsc --noEmit -p tsconfig.json`.

One-liner for the Linux side, if you are on a Mac:

```
docker run --rm -v "$PWD":/src:ro -w /app oven/bun:latest \
sh -c 'cp -a /src/. /app/ && bun add typescript@7.0.2 >/dev/null 2>&1 && \
./node_modules/.bin/tsc --noEmit -p tsconfig.json'
```

### 🙁 Actual behavior

On a **case-insensitive** filesystem (macOS APFS, and Windows), `src/Brand.tsx` is dropped from the program **with no diagnostic at all**:

```
$ tsc --showConfig -p tsconfig.json
"files": [
"./src/brand.ts"
],
$ tsc --noEmit -p tsconfig.json
$ echo $?
0
```

On a **case-sensitive** filesystem, the same tree, same compiler, same tsconfig:

```
$ tsc --showConfig -p tsconfig.json
"files": [
"./src/Brand.tsx",
"./src/brand.ts"
],
$ tsc --noEmit -p tsconfig.json
src/Brand.tsx(2,14): error TS2322: Type 'string' is not assignable to type 'number'.
$ echo $? # 2 on 6.0.3, 1 on 7.0.2
```

Verified identical on 6.0.3 (JS) and 7.0.2 (native) — both compilers, both directions.

### 🙂 Expected behavior

`src/brand.ts` and `src/Brand.tsx` are two different files with two different **names**. The `.ts` > `.tsx` > `.d.ts` de-duplication is meant for files that differ only in **extension** (`foo.ts` vs `foo.tsx`), and it should not fire across a base-name case difference.

If the de-dup is intentional here, it should at least not be silent. Today a file simply vanishes from the build: `tsc` exits 0 and prints nothing, so nothing tells the author their source was never compiled.

### Cause

`getFileNames` in `src/compiler/commandLineParser.ts` (TS 7: `internal/tsoptions/tsconfigparsing.go`) calls:

```js
if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) {
continue;
}
```

and `hasFileWithHigherPriorityExtension` does:

```js
const higherPriorityPath = keyMapper(changeExtension(file, ext));
if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) {
...
return true;
}
```

`keyMapper` is `createGetCanonicalFileName(host.useCaseSensitiveFileNames)`, which **case-folds** on a case-insensitive host. So for `file = /p/src/Brand.tsx` and `ext = ".ts"`:

`changeExtension` → `/p/src/Brand.ts` → `keyMapper` → `/p/src/brand.ts`

which is already in `wildcardFileMap` (from the real `src/brand.ts`), so `Brand.tsx` is skipped. The extension key is case-folded together with the base name, and the base name is what actually differs.

### Notes

- The file is **not** unresolvable on macOS. Adding `import "./Brand.tsx"` from another file in the program pulls it in and the error appears immediately. Only the `include` wildcard refuses it.
- `forceConsistentCasingInFileNames` does not help: there is no import to check the casing of. The file at issue here is a bundler entry point with no importer in the TS program, which is exactly the shape that hides this.
- Listing the file explicitly in `"files"` also works around it (`literalFileMap` is populated before the wildcard pass).

### Why this hurt

A repo of mine had `packages/templates/src/Brand.tsx` (a Remotion render entry) sitting next to `packages/templates/src/brand.ts` (its config module). The `.tsx` had a real type error and had **never been compiled** on any developer machine — every local `tsc` was green. CI runs on Linux, where it was red. Because the file itself looked fine and the tooling versions were pinned and identical, the divergence got misdiagnosed for a full session as a generic-inference difference in a third-party library's `.d.ts`, and "fixed" by adding `as unknown as` casts to silence it. The actual bug was that half the team's compiler had never read the file.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.