Skip to content

Commit

Permalink
fs: abstract out stat call
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jan 26, 2021
1 parent 1cd33d4 commit 46c9200
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 62 deletions.
2 changes: 1 addition & 1 deletion internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,7 @@ func (s *scanner) addEntryPoints(entryPoints []string) []uint32 {
dir := s.fs.Dir(absPath)
base := s.fs.Base(absPath)
if entries, err := s.fs.ReadDirectory(dir); err == nil {
if entry := entries[base]; entry != nil && entry.Kind() == fs.FileEntry {
if entry := entries[base]; entry != nil && entry.Kind(s.fs) == fs.FileEntry {
entryPoints[i] = "./" + path
}
}
Expand Down
58 changes: 9 additions & 49 deletions internal/fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package fs

import (
"errors"
"os"
"path/filepath"
"sync"
)

Expand All @@ -23,67 +21,26 @@ type Entry struct {
needStat bool
}

func (e *Entry) Kind() EntryKind {
func (e *Entry) Kind(fs FS) EntryKind {
e.mutex.Lock()
defer e.mutex.Unlock()
if e.needStat {
e.stat()
e.needStat = false
e.symlink, e.kind = fs.kind(e.dir, e.base)
}
return e.kind
}

func (e *Entry) Symlink() string {
func (e *Entry) Symlink(fs FS) string {
e.mutex.Lock()
defer e.mutex.Unlock()
if e.needStat {
e.stat()
e.needStat = false
e.symlink, e.kind = fs.kind(e.dir, e.base)
}
return e.symlink
}

func (e *Entry) stat() {
e.needStat = false
entryPath := filepath.Join(e.dir, e.base)

// Use "lstat" since we want information about symbolic links
BeforeFileOpen()
defer AfterFileClose()
stat, err := os.Lstat(entryPath)
if err != nil {
return
}
mode := stat.Mode()

// Follow symlinks now so the cache contains the translation
if (mode & os.ModeSymlink) != 0 {
link, err := os.Readlink(entryPath)
if err != nil {
return // Skip over this entry
}
if !filepath.IsAbs(link) {
link = filepath.Join(e.dir, link)
}
e.symlink = filepath.Clean(link)

// Re-run "lstat" on the symlink target
stat2, err2 := os.Lstat(e.symlink)
if err2 != nil {
return // Skip over this entry
}
mode = stat2.Mode()
if (mode & os.ModeSymlink) != 0 {
return // Symlink chains are not supported
}
}

// We consider the entry either a directory or a file
if (mode & os.ModeDir) != 0 {
e.kind = DirEntry
} else {
e.kind = FileEntry
}
}

type FS interface {
// The returned map is immutable and is cached across invocations. Do not
// mutate it.
Expand Down Expand Up @@ -114,6 +71,9 @@ type FS interface {
Join(parts ...string) string
Cwd() string
Rel(base string, target string) (string, bool)

// This is used in the implementation of "Entry"
kind(dir string, base string) (symlink string, kind EntryKind)
}

type ModKey struct {
Expand Down
5 changes: 5 additions & 0 deletions internal/fs/fs_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,8 @@ func (*mockFS) Rel(base string, target string) (string, bool) {
// Otherwise, down to the parent
return commonParent + target, true
}

func (fs *mockFS) kind(dir string, base string) (symlink string, kind EntryKind) {
// This will never be called
return
}
4 changes: 2 additions & 2 deletions internal/fs/fs_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestMockFSBasic(t *testing.T) {
if err != nil {
t.Fatal("Expected to find /src")
}
if len(src) != 2 || src["index.js"].Kind() != FileEntry || src["util.js"].Kind() != FileEntry {
if len(src) != 2 || src["index.js"].Kind(fs) != FileEntry || src["util.js"].Kind(fs) != FileEntry {
t.Fatalf("Incorrect contents for /src: %v", src)
}

Expand All @@ -57,7 +57,7 @@ func TestMockFSBasic(t *testing.T) {
if err != nil {
t.Fatal("Expected to find /")
}
if len(slash) != 3 || slash["src"].Kind() != DirEntry || slash["README.md"].Kind() != FileEntry || slash["package.json"].Kind() != FileEntry {
if len(slash) != 3 || slash["src"].Kind(fs) != DirEntry || slash["README.md"].Kind(fs) != FileEntry || slash["package.json"].Kind(fs) != FileEntry {
t.Fatalf("Incorrect contents for /: %v", slash)
}
}
Expand Down
43 changes: 43 additions & 0 deletions internal/fs/fs_real.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,46 @@ func readdir(dirname string) ([]string, error) {

return entries, err
}

func (fs *realFS) kind(dir string, base string) (symlink string, kind EntryKind) {
entryPath := filepath.Join(dir, base)

// Use "lstat" since we want information about symbolic links
BeforeFileOpen()
defer AfterFileClose()
stat, err := os.Lstat(entryPath)
if err != nil {
return
}
mode := stat.Mode()

// Follow symlinks now so the cache contains the translation
if (mode & os.ModeSymlink) != 0 {
link, err := os.Readlink(entryPath)
if err != nil {
return // Skip over this entry
}
if !filepath.IsAbs(link) {
link = filepath.Join(dir, link)
}
symlink = filepath.Clean(link)

// Re-run "lstat" on the symlink target
stat2, err2 := os.Lstat(symlink)
if err2 != nil {
return // Skip over this entry
}
mode = stat2.Mode()
if (mode & os.ModeSymlink) != 0 {
return // Symlink chains are not supported
}
}

// We consider the entry either a directory or a file
if (mode & os.ModeDir) != 0 {
kind = DirEntry
} else {
kind = FileEntry
}
return
}
20 changes: 10 additions & 10 deletions internal/resolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ func (r *resolver) finalizeResolve(result ResolveResult) *ResolveResult {
}

if entry, ok := dirInfo.entries[base]; ok {
if symlink := entry.Symlink(); symlink != "" {
if symlink := entry.Symlink(r.fs); symlink != "" {
// Is this entry itself a symlink?
path.Text = symlink
} else if dirInfo.absRealPath != "" {
Expand Down Expand Up @@ -726,7 +726,7 @@ func (r *resolver) dirInfoUncached(path string) *dirInfo {
base := r.fs.Base(path)
if base != "node_modules" {
if entry, ok := entries["node_modules"]; ok {
info.hasNodeModules = entry.Kind() == fs.DirEntry
info.hasNodeModules = entry.Kind(r.fs) == fs.DirEntry
}
}

Expand All @@ -736,7 +736,7 @@ func (r *resolver) dirInfoUncached(path string) *dirInfo {

// Make sure "absRealPath" is the real path of the directory (resolving any symlinks)
if entry, ok := parentInfo.entries[base]; ok {
if symlink := entry.Symlink(); symlink != "" {
if symlink := entry.Symlink(r.fs); symlink != "" {
info.absRealPath = symlink
} else if parentInfo.absRealPath != "" {
info.absRealPath = r.fs.Join(parentInfo.absRealPath, base)
Expand All @@ -745,7 +745,7 @@ func (r *resolver) dirInfoUncached(path string) *dirInfo {
}

// Record if this directory has a package.json file
if entry, ok := entries["package.json"]; ok && entry.Kind() == fs.FileEntry {
if entry, ok := entries["package.json"]; ok && entry.Kind(r.fs) == fs.FileEntry {
info.packageJSON = r.parsePackageJSON(path)

// Propagate this browser scope into child directories
Expand All @@ -758,9 +758,9 @@ func (r *resolver) dirInfoUncached(path string) *dirInfo {
{
var tsConfigPath string
if forceTsConfig := r.options.TsConfigOverride; forceTsConfig == "" {
if entry, ok := entries["tsconfig.json"]; ok && entry.Kind() == fs.FileEntry {
if entry, ok := entries["tsconfig.json"]; ok && entry.Kind(r.fs) == fs.FileEntry {
tsConfigPath = r.fs.Join(path, "tsconfig.json")
} else if entry, ok := entries["jsconfig.json"]; ok && entry.Kind() == fs.FileEntry {
} else if entry, ok := entries["jsconfig.json"]; ok && entry.Kind(r.fs) == fs.FileEntry {
tsConfigPath = r.fs.Join(path, "jsconfig.json")
}
} else if parentInfo == nil {
Expand Down Expand Up @@ -1007,13 +1007,13 @@ func (r *resolver) loadAsFile(path string, extensionOrder []string) (string, boo
base := r.fs.Base(path)

// Try the plain path without any extensions
if entry, ok := entries[base]; ok && entry.Kind() == fs.FileEntry {
if entry, ok := entries[base]; ok && entry.Kind(r.fs) == fs.FileEntry {
return path, true
}

// Try the path with extensions
for _, ext := range extensionOrder {
if entry, ok := entries[base+ext]; ok && entry.Kind() == fs.FileEntry {
if entry, ok := entries[base+ext]; ok && entry.Kind(r.fs) == fs.FileEntry {
return path + ext, true
}
}
Expand All @@ -1038,7 +1038,7 @@ func (r *resolver) loadAsFile(path string, extensionOrder []string) (string, boo
// Note that the official compiler code always tries ".ts" before
// ".tsx" even if the original extension was ".jsx".
for _, ext := range []string{".ts", ".tsx"} {
if entry, ok := entries[base[:lastDot]+ext]; ok && entry.Kind() == fs.FileEntry {
if entry, ok := entries[base[:lastDot]+ext]; ok && entry.Kind(r.fs) == fs.FileEntry {
return path[:len(path)-(len(base)-lastDot)] + ext, true
}
}
Expand All @@ -1054,7 +1054,7 @@ func (r *resolver) loadAsIndex(path string, entries map[string]*fs.Entry) (strin
// Try the "index" file with extensions
for _, ext := range r.options.ExtensionOrder {
base := "index" + ext
if entry, ok := entries[base]; ok && entry.Kind() == fs.FileEntry {
if entry, ok := entries[base]; ok && entry.Kind(r.fs) == fs.FileEntry {
return r.fs.Join(path, base), true
}
}
Expand Down

0 comments on commit 46c9200

Please sign in to comment.