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 }