tsconfig `include` silently drops `Foo.tsx` when `foo.ts` exists, on a case-insensitive filesystem
評価
この issue はまだ評価されていません。
説明
🔎 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
export const brand = "ok";
src/Brand.tsx — note the capital B
// A deliberate type error. If this file is in the program, tsc MUST report it.
export const oops: number = "not a number";
tsconfig.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:
if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) {
continue;
}
and hasFileWithHigherPriorityExtension does:
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 theincludewildcard refuses it. forceConsistentCasingInFileNamesdoes 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 (literalFileMapis 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.
- 主要言語
- Go
- スター
- 111k
- フォーク
- 14.4k
- 平均マージ
- 1日 19時間
- マージ済み PR(30日)
- 117
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
microsoft/TypeScript のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
microsoft/TypeScript#64322 · コメント 2 件 · リアクション 1 件 · 担当者 2 名 ·
-
Possible Improvement
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
microsoft/TypeScript#64278 · コメント 1 件 · リアクション 1 件 ·
-
Docs
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
microsoft/TypeScript#64118 · コメント 1 件 ·
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
microsoft/TypeScript#64094 ·
-
Docs
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
microsoft/TypeScript#63959 · コメント 5 件 ·
microsoft/TypeScript の issue をすべて見る
似ている issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 92/100
milvus-io/birdwatcher#545 ·
-
kind/bug
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
kubernetes-sigs/prow#953 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
caddyserver/caddy#8046 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
jaegertracing/jaeger#9588 ·