github.com/evanw/esbuild@v0.21.4/internal/fs/fs_mock.go (about)

     1  package fs
     2  
     3  // This is a mock implementation of the "fs" module for use with tests. It does
     4  // not actually read from the file system. Instead, it reads from a pre-specified
     5  // map of file paths to files.
     6  
     7  import (
     8  	"errors"
     9  	"path"
    10  	"strings"
    11  	"syscall"
    12  )
    13  
    14  type MockKind uint8
    15  
    16  const (
    17  	MockUnix MockKind = iota
    18  	MockWindows
    19  )
    20  
    21  type mockFS struct {
    22  	dirs          map[string]DirEntries
    23  	files         map[string]string
    24  	absWorkingDir string
    25  	Kind          MockKind
    26  }
    27  
    28  func MockFS(input map[string]string, kind MockKind, absWorkingDir string) FS {
    29  	dirs := make(map[string]DirEntries)
    30  	files := make(map[string]string)
    31  
    32  	for k, v := range input {
    33  		key := k
    34  		if kind == MockWindows {
    35  			key = "C:" + strings.ReplaceAll(key, "/", "\\")
    36  		}
    37  		files[key] = v
    38  		original := k
    39  
    40  		// Build the directory map
    41  		for {
    42  			kDir := path.Dir(k)
    43  			key := kDir
    44  			if kind == MockWindows {
    45  				key = "C:" + strings.ReplaceAll(key, "/", "\\")
    46  			}
    47  			dir, ok := dirs[key]
    48  			if !ok {
    49  				dir = DirEntries{dir: key, data: make(map[string]*Entry)}
    50  				dirs[key] = dir
    51  			}
    52  			if kDir == k {
    53  				break
    54  			}
    55  			base := path.Base(k)
    56  			if k == original {
    57  				dir.data[strings.ToLower(base)] = &Entry{kind: FileEntry, base: base}
    58  			} else {
    59  				dir.data[strings.ToLower(base)] = &Entry{kind: DirEntry, base: base}
    60  			}
    61  			k = kDir
    62  		}
    63  	}
    64  
    65  	return &mockFS{dirs, files, absWorkingDir, kind}
    66  }
    67  
    68  func (fs *mockFS) ReadDirectory(path string) (DirEntries, error, error) {
    69  	if fs.Kind == MockWindows {
    70  		path = strings.ReplaceAll(path, "/", "\\")
    71  	}
    72  
    73  	var slash byte = '/'
    74  	if fs.Kind == MockWindows {
    75  		slash = '\\'
    76  	}
    77  
    78  	// Trim trailing slashes before lookup
    79  	firstSlash := strings.IndexByte(path, slash)
    80  	for {
    81  		i := strings.LastIndexByte(path, slash)
    82  		if i != len(path)-1 || i <= firstSlash {
    83  			break
    84  		}
    85  		path = path[:i]
    86  	}
    87  
    88  	if dir, ok := fs.dirs[path]; ok {
    89  		return dir, nil, nil
    90  	}
    91  	return DirEntries{}, syscall.ENOENT, syscall.ENOENT
    92  }
    93  
    94  func (fs *mockFS) ReadFile(path string) (string, error, error) {
    95  	if fs.Kind == MockWindows {
    96  		path = strings.ReplaceAll(path, "/", "\\")
    97  	}
    98  	if contents, ok := fs.files[path]; ok {
    99  		return contents, nil, nil
   100  	}
   101  	return "", syscall.ENOENT, syscall.ENOENT
   102  }
   103  
   104  func (fs *mockFS) OpenFile(path string) (OpenedFile, error, error) {
   105  	if fs.Kind == MockWindows {
   106  		path = strings.ReplaceAll(path, "/", "\\")
   107  	}
   108  	if contents, ok := fs.files[path]; ok {
   109  		return &InMemoryOpenedFile{Contents: []byte(contents)}, nil, nil
   110  	}
   111  	return nil, syscall.ENOENT, syscall.ENOENT
   112  }
   113  
   114  func (fs *mockFS) ModKey(path string) (ModKey, error) {
   115  	return ModKey{}, errors.New("This is not available during tests")
   116  }
   117  
   118  func win2unix(p string) string {
   119  	if strings.HasPrefix(p, "C:\\") {
   120  		p = p[2:]
   121  	}
   122  	p = strings.ReplaceAll(p, "\\", "/")
   123  	return p
   124  }
   125  
   126  func unix2win(p string) string {
   127  	p = strings.ReplaceAll(p, "/", "\\")
   128  	if strings.HasPrefix(p, "\\") {
   129  		p = "C:" + p
   130  	}
   131  	return p
   132  }
   133  
   134  func (fs *mockFS) IsAbs(p string) bool {
   135  	if fs.Kind == MockWindows {
   136  		p = win2unix(p)
   137  	}
   138  	return path.IsAbs(p)
   139  }
   140  
   141  func (fs *mockFS) Abs(p string) (string, bool) {
   142  	if fs.Kind == MockWindows {
   143  		p = win2unix(p)
   144  	}
   145  
   146  	p = path.Clean(path.Join("/", p))
   147  
   148  	if fs.Kind == MockWindows {
   149  		p = unix2win(p)
   150  	}
   151  
   152  	return p, true
   153  }
   154  
   155  func (fs *mockFS) Dir(p string) string {
   156  	if fs.Kind == MockWindows {
   157  		p = win2unix(p)
   158  	}
   159  
   160  	p = path.Dir(p)
   161  
   162  	if fs.Kind == MockWindows {
   163  		p = unix2win(p)
   164  	}
   165  
   166  	return p
   167  }
   168  
   169  func (fs *mockFS) Base(p string) string {
   170  	if fs.Kind == MockWindows {
   171  		p = win2unix(p)
   172  	}
   173  
   174  	p = path.Base(p)
   175  
   176  	if fs.Kind == MockWindows && p == "/" {
   177  		p = "\\"
   178  	}
   179  
   180  	return p
   181  }
   182  
   183  func (fs *mockFS) Ext(p string) string {
   184  	if fs.Kind == MockWindows {
   185  		p = win2unix(p)
   186  	}
   187  
   188  	return path.Ext(p)
   189  }
   190  
   191  func (fs *mockFS) Join(parts ...string) string {
   192  	if fs.Kind == MockWindows {
   193  		converted := make([]string, len(parts))
   194  		for i, part := range parts {
   195  			converted[i] = win2unix(part)
   196  		}
   197  		parts = converted
   198  	}
   199  
   200  	p := path.Clean(path.Join(parts...))
   201  
   202  	if fs.Kind == MockWindows {
   203  		p = unix2win(p)
   204  	}
   205  
   206  	return p
   207  }
   208  
   209  func (fs *mockFS) Cwd() string {
   210  	return fs.absWorkingDir
   211  }
   212  
   213  func splitOnSlash(path string) (string, string) {
   214  	if slash := strings.IndexByte(path, '/'); slash != -1 {
   215  		return path[:slash], path[slash+1:]
   216  	}
   217  	return path, ""
   218  }
   219  
   220  func (fs *mockFS) Rel(base string, target string) (string, bool) {
   221  	if fs.Kind == MockWindows {
   222  		base = win2unix(base)
   223  		target = win2unix(target)
   224  	}
   225  
   226  	base = path.Clean(base)
   227  	target = path.Clean(target)
   228  
   229  	// Go's implementation does these checks
   230  	if base == target {
   231  		return ".", true
   232  	}
   233  	if base == "." {
   234  		base = ""
   235  	}
   236  
   237  	// Go's implementation fails when this condition is false. I believe this is
   238  	// because of this part of the contract, from Go's documentation: "An error
   239  	// is returned if targpath can't be made relative to basepath or if knowing
   240  	// the current working directory would be necessary to compute it."
   241  	if (len(base) > 0 && base[0] == '/') != (len(target) > 0 && target[0] == '/') {
   242  		return "", false
   243  	}
   244  
   245  	// Find the common parent directory
   246  	for {
   247  		bHead, bTail := splitOnSlash(base)
   248  		tHead, tTail := splitOnSlash(target)
   249  		if bHead != tHead {
   250  			break
   251  		}
   252  		base = bTail
   253  		target = tTail
   254  	}
   255  
   256  	// Stop now if base is a subpath of target
   257  	if base == "" {
   258  		if fs.Kind == MockWindows {
   259  			target = unix2win(target)
   260  		}
   261  		return target, true
   262  	}
   263  
   264  	// Traverse up to the common parent
   265  	commonParent := strings.Repeat("../", strings.Count(base, "/")+1)
   266  
   267  	// Stop now if target is a subpath of base
   268  	if target == "" {
   269  		target = commonParent[:len(commonParent)-1]
   270  		if fs.Kind == MockWindows {
   271  			target = unix2win(target)
   272  		}
   273  		return target, true
   274  	}
   275  
   276  	// Otherwise, down to the parent
   277  	target = commonParent + target
   278  	if fs.Kind == MockWindows {
   279  		target = unix2win(target)
   280  	}
   281  	return target, true
   282  }
   283  
   284  func (fs *mockFS) EvalSymlinks(path string) (string, bool) {
   285  	return "", false
   286  }
   287  
   288  func (fs *mockFS) kind(dir string, base string) (symlink string, kind EntryKind) {
   289  	panic("This should never be called")
   290  }
   291  
   292  func (fs *mockFS) WatchData() WatchData {
   293  	panic("This should never be called")
   294  }