microsoft / microsoft/TypeScript
tsconfig `include` silently drops `Foo.tsx` when `foo.ts` exists, on a case-insensitive filesystem
- 主要语言
- Go
- 星标
- 111k
- 派生
- 14.3k
- 平均合并
- 2 天 4 小时
- 30 天内合并 PR
- 132
描述
### 🔎 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.
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
评估
这个 Issue 还没有评估数据。