Add support for copying files and folders.

This commit is contained in:
2021-12-19 14:31:57 +01:00
parent 161cb79b88
commit 311339685c
450 changed files with 232338 additions and 3 deletions

649
vendor/github.com/evanw/esbuild/internal/fs/filepath.go generated vendored Normal file
View File

@ -0,0 +1,649 @@
// Code in this file has been forked from the "filepath" module in the Go
// source code to work around bugs with the WebAssembly build target. More
// information about why here: https://github.com/golang/go/issues/43768.
////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs
import (
"errors"
"os"
"strings"
"syscall"
)
type goFilepath struct {
cwd string
isWindows bool
pathSeparator byte
}
func isSlash(c uint8) bool {
return c == '\\' || c == '/'
}
// reservedNames lists reserved Windows names. Search for PRN in
// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
// for details.
var reservedNames = []string{
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
}
// isReservedName returns true, if path is Windows reserved name.
// See reservedNames for the full list.
func isReservedName(path string) bool {
if len(path) == 0 {
return false
}
for _, reserved := range reservedNames {
if strings.EqualFold(path, reserved) {
return true
}
}
return false
}
// IsAbs reports whether the path is absolute.
func (fp goFilepath) isAbs(path string) bool {
if !fp.isWindows {
return strings.HasPrefix(path, "/")
}
if isReservedName(path) {
return true
}
l := fp.volumeNameLen(path)
if l == 0 {
return false
}
path = path[l:]
if path == "" {
return false
}
return isSlash(path[0])
}
// Abs returns an absolute representation of path.
// If the path is not absolute it will be joined with the current
// working directory to turn it into an absolute path. The absolute
// path name for a given file is not guaranteed to be unique.
// Abs calls Clean on the result.
func (fp goFilepath) abs(path string) (string, error) {
if fp.isAbs(path) {
return fp.clean(path), nil
}
return fp.join([]string{fp.cwd, path}), nil
}
// IsPathSeparator reports whether c is a directory separator character.
func (fp goFilepath) isPathSeparator(c uint8) bool {
return c == '/' || (fp.isWindows && c == '\\')
}
// volumeNameLen returns length of the leading volume name on Windows.
// It returns 0 elsewhere.
func (fp goFilepath) volumeNameLen(path string) int {
if !fp.isWindows {
return 0
}
if len(path) < 2 {
return 0
}
// with drive letter
c := path[0]
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
return 2
}
// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
!isSlash(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if isSlash(path[n]) {
n++
// third, following something characters. its share name.
if !isSlash(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if isSlash(path[n]) {
break
}
}
return n
}
break
}
}
}
return 0
}
// EvalSymlinks returns the path name after the evaluation of any symbolic
// links.
// If path is relative the result will be relative to the current directory,
// unless one of the components is an absolute symbolic link.
// EvalSymlinks calls Clean on the result.
func (fp goFilepath) evalSymlinks(path string) (string, error) {
volLen := fp.volumeNameLen(path)
pathSeparator := string(fp.pathSeparator)
if volLen < len(path) && fp.isPathSeparator(path[volLen]) {
volLen++
}
vol := path[:volLen]
dest := vol
linksWalked := 0
for start, end := volLen, volLen; start < len(path); start = end {
for start < len(path) && fp.isPathSeparator(path[start]) {
start++
}
end = start
for end < len(path) && !fp.isPathSeparator(path[end]) {
end++
}
// On Windows, "." can be a symlink.
// We look it up, and use the value if it is absolute.
// If not, we just return ".".
isWindowsDot := fp.isWindows && path[fp.volumeNameLen(path):] == "."
// The next path component is in path[start:end].
if end == start {
// No more path components.
break
} else if path[start:end] == "." && !isWindowsDot {
// Ignore path component ".".
continue
} else if path[start:end] == ".." {
// Back up to previous component if possible.
// Note that volLen includes any leading slash.
// Set r to the index of the last slash in dest,
// after the volume.
var r int
for r = len(dest) - 1; r >= volLen; r-- {
if fp.isPathSeparator(dest[r]) {
break
}
}
if r < volLen || dest[r+1:] == ".." {
// Either path has no slashes
// (it's empty or just "C:")
// or it ends in a ".." we had to keep.
// Either way, keep this "..".
if len(dest) > volLen {
dest += pathSeparator
}
dest += ".."
} else {
// Discard everything since the last slash.
dest = dest[:r]
}
continue
}
// Ordinary path component. Add it to result.
if len(dest) > fp.volumeNameLen(dest) && !fp.isPathSeparator(dest[len(dest)-1]) {
dest += pathSeparator
}
dest += path[start:end]
// Resolve symlink.
fi, err := os.Lstat(dest)
if err != nil {
return "", err
}
if fi.Mode()&os.ModeSymlink == 0 {
if !fi.Mode().IsDir() && end < len(path) {
return "", syscall.ENOTDIR
}
continue
}
// Found symlink.
linksWalked++
if linksWalked > 255 {
return "", errors.New("EvalSymlinks: too many links")
}
link, err := os.Readlink(dest)
if err != nil {
return "", err
}
if isWindowsDot && !fp.isAbs(link) {
// On Windows, if "." is a relative symlink,
// just return ".".
break
}
path = link + path[end:]
v := fp.volumeNameLen(link)
if v > 0 {
// Symlink to drive name is an absolute path.
if v < len(link) && fp.isPathSeparator(link[v]) {
v++
}
vol = link[:v]
dest = vol
end = len(vol)
} else if len(link) > 0 && fp.isPathSeparator(link[0]) {
// Symlink to absolute path.
dest = link[:1]
end = 1
} else {
// Symlink to relative path; replace last
// path component in dest.
var r int
for r = len(dest) - 1; r >= volLen; r-- {
if fp.isPathSeparator(dest[r]) {
break
}
}
if r < volLen {
dest = vol
} else {
dest = dest[:r]
}
end = 0
}
}
return fp.clean(dest), nil
}
// A lazybuf is a lazily constructed path buffer.
// It supports append, reading previously appended bytes,
// and retrieving the final string. It does not allocate a buffer
// to hold the output until that output diverges from s.
type lazybuf struct {
path string
buf []byte
w int
volAndPath string
volLen int
}
func (b *lazybuf) index(i int) byte {
if b.buf != nil {
return b.buf[i]
}
return b.path[i]
}
func (b *lazybuf) append(c byte) {
if b.buf == nil {
if b.w < len(b.path) && b.path[b.w] == c {
b.w++
return
}
b.buf = make([]byte, len(b.path))
copy(b.buf, b.path[:b.w])
}
b.buf[b.w] = c
b.w++
}
func (b *lazybuf) string() string {
if b.buf == nil {
return b.volAndPath[:b.volLen+b.w]
}
return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
}
// FromSlash returns the result of replacing each slash ('/') character
// in path with a separator character. Multiple slashes are replaced
// by multiple separators.
func (fp goFilepath) fromSlash(path string) string {
if !fp.isWindows {
return path
}
return strings.ReplaceAll(path, "/", "\\")
}
// Clean returns the shortest path name equivalent to path
// by purely lexical processing. It applies the following rules
// iteratively until no further processing can be done:
//
// 1. Replace multiple Separator elements with a single one.
// 2. Eliminate each . path name element (the current directory).
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path,
// assuming Separator is '/'.
//
// The returned path ends in a slash only if it represents a root directory,
// such as "/" on Unix or `C:\` on Windows.
//
// Finally, any occurrences of slash are replaced by Separator.
//
// If the result of this process is an empty string, Clean
// returns the string ".".
//
// See also Rob Pike, ``Lexical File Names in Plan 9 or
// Getting Dot-Dot Right,''
// https://9p.io/sys/doc/lexnames.html
func (fp goFilepath) clean(path string) string {
originalPath := path
volLen := fp.volumeNameLen(path)
path = path[volLen:]
if path == "" {
if volLen > 1 && originalPath[1] != ':' {
// should be UNC
return fp.fromSlash(originalPath)
}
return originalPath + "."
}
rooted := fp.isPathSeparator(path[0])
// Invariants:
// reading from path; r is index of next byte to process.
// writing to buf; w is index of next byte to write.
// dotdot is index in buf where .. must stop, either because
// it is the leading slash or it is a leading ../../.. prefix.
n := len(path)
out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
r, dotdot := 0, 0
if rooted {
out.append(fp.pathSeparator)
r, dotdot = 1, 1
}
for r < n {
switch {
case fp.isPathSeparator(path[r]):
// empty path element
r++
case path[r] == '.' && (r+1 == n || fp.isPathSeparator(path[r+1])):
// . element
r++
case path[r] == '.' && path[r+1] == '.' && (r+2 == n || fp.isPathSeparator(path[r+2])):
// .. element: remove to last separator
r += 2
switch {
case out.w > dotdot:
// can backtrack
out.w--
for out.w > dotdot && !fp.isPathSeparator(out.index(out.w)) {
out.w--
}
case !rooted:
// cannot backtrack, but not rooted, so append .. element.
if out.w > 0 {
out.append(fp.pathSeparator)
}
out.append('.')
out.append('.')
dotdot = out.w
}
default:
// real path element.
// add slash if needed
if rooted && out.w != 1 || !rooted && out.w != 0 {
out.append(fp.pathSeparator)
}
// copy element
for ; r < n && !fp.isPathSeparator(path[r]); r++ {
out.append(path[r])
}
}
}
// Turn empty string into "."
if out.w == 0 {
out.append('.')
}
return fp.fromSlash(out.string())
}
// VolumeName returns leading volume name.
// Given "C:\foo\bar" it returns "C:" on Windows.
// Given "\\host\share\foo" it returns "\\host\share".
// On other platforms it returns "".
func (fp goFilepath) volumeName(path string) string {
return path[:fp.volumeNameLen(path)]
}
// Base returns the last element of path.
// Trailing path separators are removed before extracting the last element.
// If the path is empty, Base returns ".".
// If the path consists entirely of separators, Base returns a single separator.
func (fp goFilepath) base(path string) string {
if path == "" {
return "."
}
// Strip trailing slashes.
for len(path) > 0 && fp.isPathSeparator(path[len(path)-1]) {
path = path[0 : len(path)-1]
}
// Throw away volume name
path = path[len(fp.volumeName(path)):]
// Find the last element
i := len(path) - 1
for i >= 0 && !fp.isPathSeparator(path[i]) {
i--
}
if i >= 0 {
path = path[i+1:]
}
// If empty now, it had only slashes.
if path == "" {
return string(fp.pathSeparator)
}
return path
}
// Dir returns all but the last element of path, typically the path's directory.
// After dropping the final element, Dir calls Clean on the path and trailing
// slashes are removed.
// If the path is empty, Dir returns ".".
// If the path consists entirely of separators, Dir returns a single separator.
// The returned path does not end in a separator unless it is the root directory.
func (fp goFilepath) dir(path string) string {
vol := fp.volumeName(path)
i := len(path) - 1
for i >= len(vol) && !fp.isPathSeparator(path[i]) {
i--
}
dir := fp.clean(path[len(vol) : i+1])
if dir == "." && len(vol) > 2 {
// must be UNC
return vol
}
return vol + dir
}
// Ext returns the file name extension used by path.
// The extension is the suffix beginning at the final dot
// in the final element of path; it is empty if there is
// no dot.
func (fp goFilepath) ext(path string) string {
for i := len(path) - 1; i >= 0 && !fp.isPathSeparator(path[i]); i-- {
if path[i] == '.' {
return path[i:]
}
}
return ""
}
// Join joins any number of path elements into a single path,
// separating them with an OS specific Separator. Empty elements
// are ignored. The result is Cleaned. However, if the argument
// list is empty or all its elements are empty, Join returns
// an empty string.
// On Windows, the result will only be a UNC path if the first
// non-empty element is a UNC path.
func (fp goFilepath) join(elem []string) string {
for i, e := range elem {
if e != "" {
if fp.isWindows {
return fp.joinNonEmpty(elem[i:])
}
return fp.clean(strings.Join(elem[i:], string(fp.pathSeparator)))
}
}
return ""
}
// joinNonEmpty is like join, but it assumes that the first element is non-empty.
func (fp goFilepath) joinNonEmpty(elem []string) string {
if len(elem[0]) == 2 && elem[0][1] == ':' {
// First element is drive letter without terminating slash.
// Keep path relative to current directory on that drive.
// Skip empty elements.
i := 1
for ; i < len(elem); i++ {
if elem[i] != "" {
break
}
}
return fp.clean(elem[0] + strings.Join(elem[i:], string(fp.pathSeparator)))
}
// The following logic prevents Join from inadvertently creating a
// UNC path on Windows. Unless the first element is a UNC path, Join
// shouldn't create a UNC path. See golang.org/issue/9167.
p := fp.clean(strings.Join(elem, string(fp.pathSeparator)))
if !fp.isUNC(p) {
return p
}
// p == UNC only allowed when the first element is a UNC path.
head := fp.clean(elem[0])
if fp.isUNC(head) {
return p
}
// head + tail == UNC, but joining two non-UNC paths should not result
// in a UNC path. Undo creation of UNC path.
tail := fp.clean(strings.Join(elem[1:], string(fp.pathSeparator)))
if head[len(head)-1] == fp.pathSeparator {
return head + tail
}
return head + string(fp.pathSeparator) + tail
}
// isUNC reports whether path is a UNC path.
func (fp goFilepath) isUNC(path string) bool {
return fp.volumeNameLen(path) > 2
}
// Rel returns a relative path that is lexically equivalent to targpath when
// joined to basepath with an intervening separator. That is,
// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
// On success, the returned path will always be relative to basepath,
// even if basepath and targpath share no elements.
// An error is returned if targpath can't be made relative to basepath or if
// knowing the current working directory would be necessary to compute it.
// Rel calls Clean on the result.
func (fp goFilepath) rel(basepath, targpath string) (string, error) {
baseVol := fp.volumeName(basepath)
targVol := fp.volumeName(targpath)
base := fp.clean(basepath)
targ := fp.clean(targpath)
if fp.sameWord(targ, base) {
return ".", nil
}
base = base[len(baseVol):]
targ = targ[len(targVol):]
if base == "." {
base = ""
}
// Can't use IsAbs - `\a` and `a` are both relative in Windows.
baseSlashed := len(base) > 0 && base[0] == fp.pathSeparator
targSlashed := len(targ) > 0 && targ[0] == fp.pathSeparator
if baseSlashed != targSlashed || !fp.sameWord(baseVol, targVol) {
return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
}
// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
bl := len(base)
tl := len(targ)
var b0, bi, t0, ti int
for {
for bi < bl && base[bi] != fp.pathSeparator {
bi++
}
for ti < tl && targ[ti] != fp.pathSeparator {
ti++
}
if !fp.sameWord(targ[t0:ti], base[b0:bi]) {
break
}
if bi < bl {
bi++
}
if ti < tl {
ti++
}
b0 = bi
t0 = ti
}
if base[b0:bi] == ".." {
return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
}
if b0 != bl {
// Base elements left. Must go up before going down.
seps := strings.Count(base[b0:bl], string(fp.pathSeparator))
size := 2 + seps*3
if tl != t0 {
size += 1 + tl - t0
}
buf := make([]byte, size)
n := copy(buf, "..")
for i := 0; i < seps; i++ {
buf[n] = fp.pathSeparator
copy(buf[n+1:], "..")
n += 3
}
if t0 != tl {
buf[n] = fp.pathSeparator
copy(buf[n+1:], targ[t0:])
}
return string(buf), nil
}
return targ[t0:], nil
}
func (fp goFilepath) sameWord(a, b string) bool {
if !fp.isWindows {
return a == b
}
return strings.EqualFold(a, b)
}

270
vendor/github.com/evanw/esbuild/internal/fs/fs.go generated vendored Normal file
View File

@ -0,0 +1,270 @@
package fs
import (
"errors"
"os"
"sort"
"strings"
"sync"
"syscall"
)
type EntryKind uint8
const (
DirEntry EntryKind = 1
FileEntry EntryKind = 2
)
type Entry struct {
symlink string
dir string
base string
mutex sync.Mutex
kind EntryKind
needStat bool
}
func (e *Entry) Kind(fs FS) EntryKind {
e.mutex.Lock()
defer e.mutex.Unlock()
if e.needStat {
e.needStat = false
e.symlink, e.kind = fs.kind(e.dir, e.base)
}
return e.kind
}
func (e *Entry) Symlink(fs FS) string {
e.mutex.Lock()
defer e.mutex.Unlock()
if e.needStat {
e.needStat = false
e.symlink, e.kind = fs.kind(e.dir, e.base)
}
return e.symlink
}
type accessedEntries struct {
mutex sync.Mutex
wasPresent map[string]bool
// If this is nil, "SortedKeys()" was not accessed. This means we should
// check for whether this directory has changed or not by seeing if any of
// the entries in the "wasPresent" map have changed in "present or not"
// status, since the only access was to individual entries via "Get()".
//
// If this is non-nil, "SortedKeys()" was accessed. This means we should
// check for whether this directory has changed or not by checking the
// "allEntries" array for equality with the existing entries list, since the
// code asked for all entries and may have used the presence or absence of
// entries in that list.
//
// The goal of having these two checks is to be as narrow as possible to
// avoid unnecessary rebuilds. If only "Get()" is called on a few entries,
// then we won't invalidate the build if random unrelated entries are added
// or removed. But if "SortedKeys()" is called, we need to invalidate the
// build if anything about the set of entries in this directory is changed.
allEntries []string
}
type DirEntries struct {
dir string
data map[string]*Entry
accessedEntries *accessedEntries
}
func MakeEmptyDirEntries(dir string) DirEntries {
return DirEntries{dir, make(map[string]*Entry), nil}
}
type DifferentCase struct {
Dir string
Query string
Actual string
}
func (entries DirEntries) Get(query string) (*Entry, *DifferentCase) {
if entries.data != nil {
key := strings.ToLower(query)
entry := entries.data[key]
// Track whether this specific entry was present or absent for watch mode
if accessed := entries.accessedEntries; accessed != nil {
accessed.mutex.Lock()
accessed.wasPresent[key] = entry != nil
accessed.mutex.Unlock()
}
if entry != nil {
if entry.base != query {
return entry, &DifferentCase{
Dir: entries.dir,
Query: query,
Actual: entry.base,
}
}
return entry, nil
}
}
return nil, nil
}
func (entries DirEntries) SortedKeys() (keys []string) {
if entries.data != nil {
keys = make([]string, 0, len(entries.data))
for _, entry := range entries.data {
keys = append(keys, entry.base)
}
sort.Strings(keys)
// Track the exact set of all entries for watch mode
if entries.accessedEntries != nil {
entries.accessedEntries.mutex.Lock()
entries.accessedEntries.allEntries = keys
entries.accessedEntries.mutex.Unlock()
}
return keys
}
return
}
type OpenedFile interface {
Len() int
Read(start int, end int) ([]byte, error)
Close() error
}
type InMemoryOpenedFile struct {
Contents []byte
}
func (f *InMemoryOpenedFile) Len() int {
return len(f.Contents)
}
func (f *InMemoryOpenedFile) Read(start int, end int) ([]byte, error) {
return []byte(f.Contents[start:end]), nil
}
func (f *InMemoryOpenedFile) Close() error {
return nil
}
type FS interface {
// The returned map is immutable and is cached across invocations. Do not
// mutate it.
ReadDirectory(path string) (entries DirEntries, canonicalError error, originalError error)
ReadFile(path string) (contents string, canonicalError error, originalError error)
OpenFile(path string) (result OpenedFile, canonicalError error, originalError error)
// This is a key made from the information returned by "stat". It is intended
// to be different if the file has been edited, and to otherwise be equal if
// the file has not been edited. It should usually work, but no guarantees.
//
// See https://apenwarr.ca/log/20181113 for more information about why this
// can be broken. For example, writing to a file with mmap on WSL on Windows
// won't change this key. Hopefully this isn't too much of an issue.
//
// Additional reading:
// - https://github.com/npm/npm/pull/20027
// - https://github.com/golang/go/commit/7dea509703eb5ad66a35628b12a678110fbb1f72
ModKey(path string) (ModKey, error)
// This is part of the interface because the mock interface used for tests
// should not depend on file system behavior (i.e. different slashes for
// Windows) while the real interface should.
IsAbs(path string) bool
Abs(path string) (string, bool)
Dir(path string) string
Base(path string) string
Ext(path string) string
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)
// This is a set of all files used and all directories checked. The build
// must be invalidated if any of these watched files change.
WatchData() WatchData
}
type WatchData struct {
// These functions return a non-empty path as a string if the file system
// entry has been modified. For files, the returned path is the same as the
// file path. For directories, the returned path is either the directory
// itself or a file in the directory that was changed.
Paths map[string]func() string
}
type ModKey struct {
// What gets filled in here is OS-dependent
inode uint64
size int64
mtime_sec int64
mtime_nsec int64
mode uint32
uid uint32
}
// Some file systems have a time resolution of only a few seconds. If a mtime
// value is too new, we won't be able to tell if it has been recently modified
// or not. So we only use mtimes for comparison if they are sufficiently old.
// Apparently the FAT file system has a resolution of two seconds according to
// this article: https://en.wikipedia.org/wiki/Stat_(system_call).
const modKeySafetyGap = 3 // In seconds
var modKeyUnusable = errors.New("The modification key is unusable")
// Limit the number of files open simultaneously to avoid ulimit issues
var fileOpenLimit = make(chan bool, 32)
func BeforeFileOpen() {
// This will block if the number of open files is already at the limit
fileOpenLimit <- false
}
func AfterFileClose() {
<-fileOpenLimit
}
// This is a fork of "os.MkdirAll" to work around bugs with the WebAssembly
// build target. More information here: https://github.com/golang/go/issues/43768.
func MkdirAll(fs FS, path string, perm os.FileMode) error {
// Run "Join" once to run "Clean" on the path, which removes trailing slashes
return mkdirAll(fs, fs.Join(path), perm)
}
func mkdirAll(fs FS, path string, perm os.FileMode) error {
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
if dir, err := os.Stat(path); err == nil {
if dir.IsDir() {
return nil
}
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
}
// Slow path: make sure parent exists and then call Mkdir for path.
if parent := fs.Dir(path); parent != path {
// Create parent.
if err := mkdirAll(fs, parent, perm); err != nil {
return err
}
}
// Parent now exists; invoke Mkdir and use its result.
if err := os.Mkdir(path, perm); err != nil {
// Handle arguments like "foo/." by
// double-checking that directory doesn't exist.
dir, err1 := os.Lstat(path)
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
return nil
}

157
vendor/github.com/evanw/esbuild/internal/fs/fs_mock.go generated vendored Normal file
View File

@ -0,0 +1,157 @@
// This is a mock implementation of the "fs" module for use with tests. It does
// not actually read from the file system. Instead, it reads from a pre-specified
// map of file paths to files.
package fs
import (
"errors"
"path"
"strings"
"syscall"
)
type mockFS struct {
dirs map[string]DirEntries
files map[string]string
}
func MockFS(input map[string]string) FS {
dirs := make(map[string]DirEntries)
files := make(map[string]string)
for k, v := range input {
files[k] = v
original := k
// Build the directory map
for {
kDir := path.Dir(k)
dir, ok := dirs[kDir]
if !ok {
dir = DirEntries{kDir, make(map[string]*Entry), nil}
dirs[kDir] = dir
}
if kDir == k {
break
}
base := path.Base(k)
if k == original {
dir.data[strings.ToLower(base)] = &Entry{kind: FileEntry, base: base}
} else {
dir.data[strings.ToLower(base)] = &Entry{kind: DirEntry, base: base}
}
k = kDir
}
}
return &mockFS{dirs, files}
}
func (fs *mockFS) ReadDirectory(path string) (DirEntries, error, error) {
if dir, ok := fs.dirs[path]; ok {
return dir, nil, nil
}
return DirEntries{}, syscall.ENOENT, syscall.ENOENT
}
func (fs *mockFS) ReadFile(path string) (string, error, error) {
if contents, ok := fs.files[path]; ok {
return contents, nil, nil
}
return "", syscall.ENOENT, syscall.ENOENT
}
func (fs *mockFS) OpenFile(path string) (OpenedFile, error, error) {
if contents, ok := fs.files[path]; ok {
return &InMemoryOpenedFile{Contents: []byte(contents)}, nil, nil
}
return nil, syscall.ENOENT, syscall.ENOENT
}
func (fs *mockFS) ModKey(path string) (ModKey, error) {
return ModKey{}, errors.New("This is not available during tests")
}
func (*mockFS) IsAbs(p string) bool {
return path.IsAbs(p)
}
func (*mockFS) Abs(p string) (string, bool) {
return path.Clean(path.Join("/", p)), true
}
func (*mockFS) Dir(p string) string {
return path.Dir(p)
}
func (*mockFS) Base(p string) string {
return path.Base(p)
}
func (*mockFS) Ext(p string) string {
return path.Ext(p)
}
func (*mockFS) Join(parts ...string) string {
return path.Clean(path.Join(parts...))
}
func (*mockFS) Cwd() string {
return "/"
}
func splitOnSlash(path string) (string, string) {
if slash := strings.IndexByte(path, '/'); slash != -1 {
return path[:slash], path[slash+1:]
}
return path, ""
}
func (*mockFS) Rel(base string, target string) (string, bool) {
base = path.Clean(base)
target = path.Clean(target)
// Base cases
if base == "" || base == "." {
return target, true
}
if base == target {
return ".", true
}
// Find the common parent directory
for {
bHead, bTail := splitOnSlash(base)
tHead, tTail := splitOnSlash(target)
if bHead != tHead {
break
}
base = bTail
target = tTail
}
// Stop now if base is a subpath of target
if base == "" {
return target, true
}
// Traverse up to the common parent
commonParent := strings.Repeat("../", strings.Count(base, "/")+1)
// Stop now if target is a subpath of base
if target == "" {
return commonParent[:len(commonParent)-1], true
}
// Otherwise, down to the parent
return commonParent + target, true
}
func (fs *mockFS) kind(dir string, base string) (symlink string, kind EntryKind) {
panic("This should never be called")
}
func (fs *mockFS) WatchData() WatchData {
panic("This should never be called")
}

529
vendor/github.com/evanw/esbuild/internal/fs/fs_real.go generated vendored Normal file
View File

@ -0,0 +1,529 @@
package fs
import (
"fmt"
"io"
"io/ioutil"
"os"
"sort"
"strings"
"sync"
"syscall"
)
type realFS struct {
// Stores the file entries for directories we've listed before
entriesMutex sync.Mutex
entries map[string]entriesOrErr
// If true, do not use the "entries" cache
doNotCacheEntries bool
// This stores data that will end up being returned by "WatchData()"
watchMutex sync.Mutex
watchData map[string]privateWatchData
// When building with WebAssembly, the Go compiler doesn't correctly handle
// platform-specific path behavior. Hack around these bugs by compiling
// support for both Unix and Windows paths into all executables and switch
// between them at run-time instead.
fp goFilepath
}
type entriesOrErr struct {
entries DirEntries
canonicalError error
originalError error
}
type watchState uint8
const (
stateNone watchState = iota
stateDirHasAccessedEntries // Compare "accessedEntries"
stateDirMissing // Compare directory presence
stateFileHasModKey // Compare "modKey"
stateFileNeedModKey // Need to transition to "stateFileHasModKey" or "stateFileUnusableModKey" before "WatchData()" returns
stateFileMissing // Compare file presence
stateFileUnusableModKey // Compare "fileContents"
)
type privateWatchData struct {
accessedEntries *accessedEntries
fileContents string
modKey ModKey
state watchState
}
type RealFSOptions struct {
WantWatchData bool
AbsWorkingDir string
DoNotCache bool
}
func RealFS(options RealFSOptions) (FS, error) {
var fp goFilepath
if CheckIfWindows() {
fp.isWindows = true
fp.pathSeparator = '\\'
} else {
fp.isWindows = false
fp.pathSeparator = '/'
}
// Come up with a default working directory if one was not specified
fp.cwd = options.AbsWorkingDir
if fp.cwd == "" {
if cwd, err := os.Getwd(); err == nil {
fp.cwd = cwd
} else if fp.isWindows {
fp.cwd = "C:\\"
} else {
fp.cwd = "/"
}
} else if !fp.isAbs(fp.cwd) {
return nil, fmt.Errorf("The working directory %q is not an absolute path", fp.cwd)
}
// Resolve symlinks in the current working directory. Symlinks are resolved
// when input file paths are converted to absolute paths because we need to
// recognize an input file as unique even if it has multiple symlinks
// pointing to it. The build will generate relative paths from the current
// working directory to the absolute input file paths for error messages,
// so the current working directory should be processed the same way. Not
// doing this causes test failures with esbuild when run from inside a
// symlinked directory.
//
// This deliberately ignores errors due to e.g. infinite loops. If there is
// an error, we will just use the original working directory and likely
// encounter an error later anyway. And if we don't encounter an error
// later, then the current working directory didn't even matter and the
// error is unimportant.
if path, err := fp.evalSymlinks(fp.cwd); err == nil {
fp.cwd = path
}
// Only allocate memory for watch data if necessary
var watchData map[string]privateWatchData
if options.WantWatchData {
watchData = make(map[string]privateWatchData)
}
return &realFS{
entries: make(map[string]entriesOrErr),
fp: fp,
watchData: watchData,
doNotCacheEntries: options.DoNotCache,
}, nil
}
func (fs *realFS) ReadDirectory(dir string) (entries DirEntries, canonicalError error, originalError error) {
if !fs.doNotCacheEntries {
// First, check the cache
cached, ok := func() (cached entriesOrErr, ok bool) {
fs.entriesMutex.Lock()
defer fs.entriesMutex.Unlock()
cached, ok = fs.entries[dir]
return
}()
if ok {
// Cache hit: stop now
return cached.entries, cached.canonicalError, cached.originalError
}
}
// Cache miss: read the directory entries
names, canonicalError, originalError := fs.readdir(dir)
entries = DirEntries{dir, make(map[string]*Entry), nil}
// Unwrap to get the underlying error
if pathErr, ok := canonicalError.(*os.PathError); ok {
canonicalError = pathErr.Unwrap()
}
if canonicalError == nil {
for _, name := range names {
// Call "stat" lazily for performance. The "@material-ui/icons" package
// contains a directory with over 11,000 entries in it and running "stat"
// for each entry was a big performance issue for that package.
entries.data[strings.ToLower(name)] = &Entry{
dir: dir,
base: name,
needStat: true,
}
}
}
// Store data for watch mode
if fs.watchData != nil {
defer fs.watchMutex.Unlock()
fs.watchMutex.Lock()
state := stateDirHasAccessedEntries
if canonicalError != nil {
state = stateDirMissing
}
entries.accessedEntries = &accessedEntries{wasPresent: make(map[string]bool)}
fs.watchData[dir] = privateWatchData{
accessedEntries: entries.accessedEntries,
state: state,
}
}
// Update the cache unconditionally. Even if the read failed, we don't want to
// retry again later. The directory is inaccessible so trying again is wasted.
if canonicalError != nil {
entries.data = nil
}
if !fs.doNotCacheEntries {
fs.entriesMutex.Lock()
defer fs.entriesMutex.Unlock()
fs.entries[dir] = entriesOrErr{
entries: entries,
canonicalError: canonicalError,
originalError: originalError,
}
}
return entries, canonicalError, originalError
}
func (fs *realFS) ReadFile(path string) (contents string, canonicalError error, originalError error) {
BeforeFileOpen()
defer AfterFileClose()
buffer, originalError := ioutil.ReadFile(path)
canonicalError = fs.canonicalizeError(originalError)
// Allocate the string once
fileContents := string(buffer)
// Store data for watch mode
if fs.watchData != nil {
defer fs.watchMutex.Unlock()
fs.watchMutex.Lock()
data, ok := fs.watchData[path]
if canonicalError != nil {
data.state = stateFileMissing
} else if !ok {
data.state = stateFileNeedModKey
}
data.fileContents = fileContents
fs.watchData[path] = data
}
return fileContents, canonicalError, originalError
}
type realOpenedFile struct {
handle *os.File
len int
}
func (f *realOpenedFile) Len() int {
return f.len
}
func (f *realOpenedFile) Read(start int, end int) ([]byte, error) {
bytes := make([]byte, end-start)
remaining := bytes
_, err := f.handle.Seek(int64(start), io.SeekStart)
if err != nil {
return nil, err
}
for len(remaining) > 0 {
n, err := f.handle.Read(remaining)
if err != nil && n <= 0 {
return nil, err
}
remaining = remaining[n:]
}
return bytes, nil
}
func (f *realOpenedFile) Close() error {
return f.handle.Close()
}
func (fs *realFS) OpenFile(path string) (OpenedFile, error, error) {
BeforeFileOpen()
defer AfterFileClose()
f, err := os.Open(path)
if err != nil {
return nil, fs.canonicalizeError(err), err
}
info, err := f.Stat()
if err != nil {
f.Close()
return nil, fs.canonicalizeError(err), err
}
return &realOpenedFile{f, int(info.Size())}, nil, nil
}
func (fs *realFS) ModKey(path string) (ModKey, error) {
BeforeFileOpen()
defer AfterFileClose()
key, err := modKey(path)
// Store data for watch mode
if fs.watchData != nil {
defer fs.watchMutex.Unlock()
fs.watchMutex.Lock()
data, ok := fs.watchData[path]
if !ok {
if err == modKeyUnusable {
data.state = stateFileUnusableModKey
} else if err != nil {
data.state = stateFileMissing
} else {
data.state = stateFileHasModKey
}
} else if data.state == stateFileNeedModKey {
data.state = stateFileHasModKey
}
data.modKey = key
fs.watchData[path] = data
}
return key, err
}
func (fs *realFS) IsAbs(p string) bool {
return fs.fp.isAbs(p)
}
func (fs *realFS) Abs(p string) (string, bool) {
abs, err := fs.fp.abs(p)
return abs, err == nil
}
func (fs *realFS) Dir(p string) string {
return fs.fp.dir(p)
}
func (fs *realFS) Base(p string) string {
return fs.fp.base(p)
}
func (fs *realFS) Ext(p string) string {
return fs.fp.ext(p)
}
func (fs *realFS) Join(parts ...string) string {
return fs.fp.clean(fs.fp.join(parts))
}
func (fs *realFS) Cwd() string {
return fs.fp.cwd
}
func (fs *realFS) Rel(base string, target string) (string, bool) {
if rel, err := fs.fp.rel(base, target); err == nil {
return rel, true
}
return "", false
}
func (fs *realFS) readdir(dirname string) (entries []string, canonicalError error, originalError error) {
BeforeFileOpen()
defer AfterFileClose()
f, originalError := os.Open(dirname)
canonicalError = fs.canonicalizeError(originalError)
// Stop now if there was an error
if canonicalError != nil {
return nil, canonicalError, originalError
}
defer f.Close()
entries, err := f.Readdirnames(-1)
// Unwrap to get the underlying error
if syscallErr, ok := err.(*os.SyscallError); ok {
err = syscallErr.Unwrap()
}
// Don't convert ENOTDIR to ENOENT here. ENOTDIR is a legitimate error
// condition for Readdirnames() on non-Windows platforms.
return entries, canonicalError, originalError
}
func (fs *realFS) canonicalizeError(err error) error {
// Unwrap to get the underlying error
if pathErr, ok := err.(*os.PathError); ok {
err = pathErr.Unwrap()
}
// This has been copied from golang.org/x/sys/windows
const ERROR_INVALID_NAME syscall.Errno = 123
// Windows is much more restrictive than Unix about file names. If a file name
// is invalid, it will return ERROR_INVALID_NAME. Treat this as ENOENT (i.e.
// "the file does not exist") so that the resolver continues trying to resolve
// the path on this failure instead of aborting with an error.
if fs.fp.isWindows && err == ERROR_INVALID_NAME {
err = syscall.ENOENT
}
// Windows returns ENOTDIR here even though nothing we've done yet has asked
// for a directory. This really means ENOENT on Windows. Return ENOENT here
// so callers that check for ENOENT will successfully detect this file as
// missing.
if err == syscall.ENOTDIR {
err = syscall.ENOENT
}
return err
}
func (fs *realFS) kind(dir string, base string) (symlink string, kind EntryKind) {
entryPath := fs.fp.join([]string{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 {
symlink = entryPath
linksWalked := 0
for {
linksWalked++
if linksWalked > 255 {
return // Error: too many links
}
link, err := os.Readlink(symlink)
if err != nil {
return // Skip over this entry
}
if !fs.fp.isAbs(link) {
link = fs.fp.join([]string{dir, link})
}
symlink = fs.fp.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 {
break
}
dir = fs.fp.dir(symlink)
}
}
// We consider the entry either a directory or a file
if (mode & os.ModeDir) != 0 {
kind = DirEntry
} else {
kind = FileEntry
}
return
}
func (fs *realFS) WatchData() WatchData {
paths := make(map[string]func() string)
for path, data := range fs.watchData {
// Each closure below needs its own copy of these loop variables
path := path
data := data
// Each function should return true if the state has been changed
if data.state == stateFileNeedModKey {
key, err := modKey(path)
if err == modKeyUnusable {
data.state = stateFileUnusableModKey
} else if err != nil {
data.state = stateFileMissing
} else {
data.state = stateFileHasModKey
data.modKey = key
}
}
switch data.state {
case stateDirMissing:
paths[path] = func() string {
info, err := os.Stat(path)
if err == nil && info.IsDir() {
return path
}
return ""
}
case stateDirHasAccessedEntries:
paths[path] = func() string {
names, err, _ := fs.readdir(path)
if err != nil {
return path
}
data.accessedEntries.mutex.Lock()
defer data.accessedEntries.mutex.Unlock()
if allEntries := data.accessedEntries.allEntries; allEntries != nil {
// Check all entries
if len(names) != len(allEntries) {
return path
}
sort.Strings(names)
for i, s := range names {
if s != allEntries[i] {
return path
}
}
} else {
// Check individual entries
isPresent := make(map[string]bool, len(names))
for _, name := range names {
isPresent[strings.ToLower(name)] = true
}
for name, wasPresent := range data.accessedEntries.wasPresent {
if wasPresent != isPresent[name] {
return fs.Join(path, name)
}
}
}
return ""
}
case stateFileMissing:
paths[path] = func() string {
if info, err := os.Stat(path); err == nil && !info.IsDir() {
return path
}
return ""
}
case stateFileHasModKey:
paths[path] = func() string {
if key, err := modKey(path); err != nil || key != data.modKey {
return path
}
return ""
}
case stateFileUnusableModKey:
paths[path] = func() string {
if buffer, err := ioutil.ReadFile(path); err != nil || string(buffer) != data.fileContents {
return path
}
return ""
}
}
}
return WatchData{
Paths: paths,
}
}

View File

@ -0,0 +1,9 @@
//go:build (!js || !wasm) && !windows
// +build !js !wasm
// +build !windows
package fs
func CheckIfWindows() bool {
return false
}

View File

@ -0,0 +1,25 @@
//go:build js && wasm
// +build js,wasm
package fs
import (
"os"
)
var checkedIfWindows bool
var cachedIfWindows bool
func CheckIfWindows() bool {
if !checkedIfWindows {
checkedIfWindows = true
// Hack: Assume that we're on Windows if we're running WebAssembly and
// the "C:\\" directory exists. This is a workaround for a bug in Go's
// WebAssembly support: https://github.com/golang/go/issues/43768.
_, err := os.Stat("C:\\")
cachedIfWindows = err == nil
}
return cachedIfWindows
}

View File

@ -0,0 +1,8 @@
//go:build windows
// +build windows
package fs
func CheckIfWindows() bool {
return true
}

View File

@ -0,0 +1,35 @@
//go:build !darwin && !freebsd && !linux
// +build !darwin,!freebsd,!linux
package fs
import (
"os"
"time"
)
var zeroTime time.Time
func modKey(path string) (ModKey, error) {
info, err := os.Stat(path)
if err != nil {
return ModKey{}, err
}
// We can't detect changes if the file system zeros out the modification time
mtime := info.ModTime()
if mtime == zeroTime || mtime.Unix() == 0 {
return ModKey{}, modKeyUnusable
}
// Don't generate a modification key if the file is too new
if mtime.Add(modKeySafetyGap * time.Second).After(time.Now()) {
return ModKey{}, modKeyUnusable
}
return ModKey{
size: info.Size(),
mtime_sec: mtime.Unix(),
mode: uint32(info.Mode()),
}, nil
}

View File

@ -0,0 +1,41 @@
//go:build darwin || freebsd || linux
// +build darwin freebsd linux
package fs
import (
"time"
"golang.org/x/sys/unix"
)
func modKey(path string) (ModKey, error) {
stat := unix.Stat_t{}
if err := unix.Stat(path, &stat); err != nil {
return ModKey{}, err
}
// We can't detect changes if the file system zeros out the modification time
if stat.Mtim.Sec == 0 && stat.Mtim.Nsec == 0 {
return ModKey{}, modKeyUnusable
}
// Don't generate a modification key if the file is too new
now, err := unix.TimeToTimespec(time.Now())
if err != nil {
return ModKey{}, err
}
mtimeSec := stat.Mtim.Sec + modKeySafetyGap
if mtimeSec > now.Sec || (mtimeSec == now.Sec && stat.Mtim.Nsec > now.Nsec) {
return ModKey{}, modKeyUnusable
}
return ModKey{
inode: stat.Ino,
size: stat.Size,
mtime_sec: int64(stat.Mtim.Sec),
mtime_nsec: int64(stat.Mtim.Nsec),
mode: uint32(stat.Mode),
uid: stat.Uid,
}, nil
}