Skip to content

Commit

Permalink
fix #2411: ignore tsconfig extends directories
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 25, 2022
1 parent 7208846 commit 69d356b
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@

An error message is already thrown when the esbuild package is corrupted and esbuild can't be run. However, if you are using a synchronous call in the JavaScript API in worker mode, esbuild will use a child worker to initialize esbuild once so that the overhead of initializing esbuild can be amortized across multiple synchronous API calls. However, errors thrown during initialization weren't being propagated correctly which resulted in a hang while the main thread waited forever for the child worker to finish initializing. With this release, initialization errors are now propagated correctly so calling a synchronous API call when the package is corrupted should now result in an error instead of a hang.

* Fix `tsconfig.json` files that collide with directory names ([#2411](https://github.com/evanw/esbuild/issues/2411))

TypeScript lets you write `tsconfig.json` files with `extends` clauses that refer to another config file using an implicit `.json` file extension. However, if the config file without the `.json` extension existed as a directory name, esbuild and TypeScript had different behavior. TypeScript ignores the directory and continues looking for the config file by adding the `.json` extension while esbuild previously terminated the search and then failed to load the config file (because it's a directory). With this release, esbuild will now ignore exact matches when resolving `extends` fields in `tsconfig.json` files if the exact match results in a directory.

## 0.14.49

* Keep inlined constants when direct `eval` is present ([#2361](https://github.com/evanw/esbuild/issues/2361))
Expand Down
41 changes: 33 additions & 8 deletions internal/resolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,16 @@ func (r resolverQuery) parseTSConfig(file string, visited map[string]bool) (*TSC
filesToCheck := []string{r.fs.Join(join, "tsconfig.json"), join, join + ".json"}
for _, fileToCheck := range filesToCheck {
base, err := r.parseTSConfig(fileToCheck, visited)

// Explicitly ignore matches if they are directories instead of files
if err != nil && err != syscall.ENOENT {
if entries, _, dirErr := r.fs.ReadDirectory(r.fs.Dir(fileToCheck)); dirErr == nil {
if entry, _ := entries.Get(r.fs.Base(fileToCheck)); entry != nil && entry.Kind(r.fs) == fs.DirEntry {
continue
}
}
}

if err == nil {
return base
} else if err == syscall.ENOENT {
Expand Down Expand Up @@ -962,19 +972,34 @@ func (r resolverQuery) parseTSConfig(file string, visited map[string]bool) (*TSC
if !r.fs.IsAbs(extends) {
extendsFile = r.fs.Join(fileDir, extends)
}
for _, fileToCheck := range []string{extendsFile, extendsFile + ".json"} {
base, err := r.parseTSConfig(fileToCheck, visited)
if err == nil {
return base
} else if err == syscall.ENOENT {
continue
} else if err == errParseErrorImportCycle {
base, err := r.parseTSConfig(extendsFile, visited)

// TypeScript's handling of "extends" has some specific edge cases. We
// must only try adding ".json" if it's not already present, which is
// unlike how node path resolution works. We also need to explicitly
// ignore matches if they are directories instead of files. Some users
// name directories the same name as their config files.
if err != nil && !strings.HasSuffix(extendsFile, ".json") {
if entries, _, dirErr := r.fs.ReadDirectory(r.fs.Dir(extendsFile)); dirErr == nil {
extendsBase := r.fs.Base(extendsFile)
if entry, _ := entries.Get(extendsBase); entry == nil || entry.Kind(r.fs) != fs.FileEntry {
if entry, _ := entries.Get(extendsBase + ".json"); entry != nil && entry.Kind(r.fs) == fs.FileEntry {
base, err = r.parseTSConfig(extendsFile+".json", visited)
}
}
}
}

if err == nil {
return base
} else if err != syscall.ENOENT {
if err == errParseErrorImportCycle {
r.log.AddID(logger.MsgID_TsconfigJSON_Cycle, logger.Warning, &tracker, extendsRange,
fmt.Sprintf("Base config file %q forms cycle", extends))
} else if err != errParseErrorAlreadyLogged {
r.log.AddError(&tracker, extendsRange,
fmt.Sprintf("Cannot read file %q: %s",
r.PrettyPath(logger.Path{Text: fileToCheck, Namespace: "file"}), err.Error()))
r.PrettyPath(logger.Path{Text: extendsFile, Namespace: "file"}), err.Error()))
}
return nil
}
Expand Down
16 changes: 15 additions & 1 deletion scripts/end-to-end-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -5799,7 +5799,7 @@
'src/tsconfig.json': `{"extends": "foo"}`,
'node_modules/foo/tsconfig.json/x': ``,
}, {
expectedStderr: `${errorIcon} [ERROR] Cannot read file "node_modules/foo/tsconfig.json": ${errorText}
expectedStderr: `▲ [WARNING] Cannot find base config file "foo" [tsconfig.json]
src/tsconfig.json:1:12:
1 │ {"extends": "foo"}
Expand All @@ -5816,6 +5816,20 @@
"module": "dist/esm/index.js"
}`,
}),
test(['src/entry.js', '--bundle', '--outfile=node.js'], {
'src/entry.js': ``,
'src/tsconfig.json': `{"extends": "./lib"}`,
'src/lib.json': `{"compilerOptions": {"target": "1"}}`, // We should get a warning about this file
'src/lib/index.json': `{"compilerOptions": {"target": "2"}}`, // Not about this file
}, {
expectedStderr: `▲ [WARNING] Unrecognized target environment "1" [tsconfig.json]
src/lib.json:1:31:
1 │ {"compilerOptions": {"target": "1"}}
╵ ~~~
`,
}),
)
}

Expand Down

0 comments on commit 69d356b

Please sign in to comment.