Skip to content

Commit 46fdb68

Browse files
committed
fix #3898: incorrect cyclic tsconfig.json warning
1 parent b500443 commit 46fdb68

File tree

4 files changed

+76
-6
lines changed

4 files changed

+76
-6
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
./esbuild --version
2020
```
2121
22+
* Avoid incorrect cycle warning with `tsconfig.json` multiple inheritance ([#3898](https://github.com/evanw/esbuild/issues/3898))
23+
24+
TypeScript 5.0 introduced multiple inheritance for `tsconfig.json` files where `extends` can be an array of file paths. Previously esbuild would incorrectly treat files encountered more than once when processing separate subtrees of the multiple inheritance hierarchy as an inheritance cycle. With this release, `tsconfig.json` files containing this edge case should work correctly without generating a warning.
25+
2226
* Handle Yarn Plug'n'Play stack overflow with `tsconfig.json` ([#3915](https://github.com/evanw/esbuild/issues/3915))
2327
2428
Previously a `tsconfig.json` file that `extends` another file in a package with an `exports` map could cause a stack overflow when Yarn's Plug'n'Play resolution was active. This edge case should work now starting with this release.

internal/bundler_tests/bundler_tsconfig_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -2817,3 +2817,55 @@ func TestTsconfigStackOverflowYarnPnP(t *testing.T) {
28172817
},
28182818
})
28192819
}
2820+
2821+
func TestTsconfigJsonExtendsArrayIssue3898(t *testing.T) {
2822+
tsconfig_suite.expectBundled(t, bundled{
2823+
files: map[string]string{
2824+
"/Users/user/project/index.tsx": `
2825+
import { type SomeType } from 'MUST_KEEP'
2826+
console.log(<>
2827+
<div/>
2828+
</>)
2829+
`,
2830+
"/Users/user/project/tsconfig.json": `
2831+
{
2832+
"extends": [
2833+
"./tsconfigs/a.json",
2834+
"./tsconfigs/b.json",
2835+
]
2836+
}
2837+
`,
2838+
"/Users/user/project/tsconfigs/base.json": `
2839+
{
2840+
"compilerOptions": {
2841+
"verbatimModuleSyntax": true,
2842+
}
2843+
}
2844+
`,
2845+
"/Users/user/project/tsconfigs/a.json": `
2846+
{
2847+
"extends": "./base.json",
2848+
"compilerOptions": {
2849+
"jsxFactory": "SUCCESS",
2850+
}
2851+
}
2852+
`,
2853+
"/Users/user/project/tsconfigs/b.json": `
2854+
{
2855+
"extends": "./base.json",
2856+
"compilerOptions": {
2857+
"jsxFragmentFactory": "WORKS",
2858+
}
2859+
}
2860+
`,
2861+
},
2862+
entryPaths: []string{"/Users/user/project/index.tsx"},
2863+
options: config.Options{
2864+
Mode: config.ModePassThrough,
2865+
AbsOutputFile: "/Users/user/project/out.js",
2866+
JSX: config.JSXOptions{
2867+
SideEffects: true,
2868+
},
2869+
},
2870+
})
2871+
}

internal/bundler_tests/snapshots/snapshots_tsconfig.txt

+6
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,12 @@ TestTsconfigJsonExtendsAbsolute
290290
// Users/user/project/entry.jsx
291291
console.log(/* @__PURE__ */ baseFactory("div", null), /* @__PURE__ */ baseFactory(derivedFragment, null));
292292

293+
================================================================================
294+
TestTsconfigJsonExtendsArrayIssue3898
295+
---------- /Users/user/project/out.js ----------
296+
import {} from "MUST_KEEP";
297+
console.log(SUCCESS(WORKS, null, SUCCESS("div", null)));
298+
293299
================================================================================
294300
TestTsconfigJsonExtendsLoop
295301
---------- /out.js ----------

internal/resolver/resolver.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -1183,11 +1183,6 @@ func (r resolverQuery) parseTSConfig(file string, visited map[string]bool, confi
11831183
if visited[file] {
11841184
return nil, errParseErrorImportCycle
11851185
}
1186-
if visited != nil {
1187-
// This is only non-nil for "build" API calls. This is nil for "transform"
1188-
// API calls, which tells us to not process "extends" fields.
1189-
visited[file] = true
1190-
}
11911186

11921187
contents, err, originalError := r.caches.FSCache.ReadFile(r.fs, file)
11931188
if r.debugLogs != nil && originalError != nil {
@@ -1206,7 +1201,20 @@ func (r resolverQuery) parseTSConfig(file string, visited map[string]bool, confi
12061201
PrettyPath: PrettyPath(r.fs, keyPath),
12071202
Contents: contents,
12081203
}
1209-
return r.parseTSConfigFromSource(source, visited, configDir)
1204+
if visited != nil {
1205+
// This is only non-nil for "build" API calls. This is nil for "transform"
1206+
// API calls, which tells us to not process "extends" fields.
1207+
visited[file] = true
1208+
}
1209+
result, err := r.parseTSConfigFromSource(source, visited, configDir)
1210+
if visited != nil {
1211+
// Reset this to back false in case something uses TypeScript 5.0's multiple
1212+
// inheritance feature for "tsconfig.json" files. It should be valid to visit
1213+
// the same base "tsconfig.json" file multiple times from different multiple
1214+
// inheritance subtrees.
1215+
visited[file] = false
1216+
}
1217+
return result, err
12101218
}
12111219

12121220
func (r resolverQuery) parseTSConfigFromSource(source logger.Source, visited map[string]bool, configDir string) (*TSConfigJSON, error) {

0 commit comments

Comments
 (0)