Add support for copying files and folders.
This commit is contained in:
649
vendor/github.com/evanw/esbuild/internal/fs/filepath.go
generated
vendored
Normal file
649
vendor/github.com/evanw/esbuild/internal/fs/filepath.go
generated
vendored
Normal 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
270
vendor/github.com/evanw/esbuild/internal/fs/fs.go
generated
vendored
Normal 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
157
vendor/github.com/evanw/esbuild/internal/fs/fs_mock.go
generated
vendored
Normal 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
529
vendor/github.com/evanw/esbuild/internal/fs/fs_real.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
9
vendor/github.com/evanw/esbuild/internal/fs/iswin_other.go
generated
vendored
Normal file
9
vendor/github.com/evanw/esbuild/internal/fs/iswin_other.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
//go:build (!js || !wasm) && !windows
|
||||
// +build !js !wasm
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
func CheckIfWindows() bool {
|
||||
return false
|
||||
}
|
25
vendor/github.com/evanw/esbuild/internal/fs/iswin_wasm.go
generated
vendored
Normal file
25
vendor/github.com/evanw/esbuild/internal/fs/iswin_wasm.go
generated
vendored
Normal 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
|
||||
}
|
8
vendor/github.com/evanw/esbuild/internal/fs/iswin_windows.go
generated
vendored
Normal file
8
vendor/github.com/evanw/esbuild/internal/fs/iswin_windows.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
func CheckIfWindows() bool {
|
||||
return true
|
||||
}
|
35
vendor/github.com/evanw/esbuild/internal/fs/modkey_other.go
generated
vendored
Normal file
35
vendor/github.com/evanw/esbuild/internal/fs/modkey_other.go
generated
vendored
Normal 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
|
||||
}
|
41
vendor/github.com/evanw/esbuild/internal/fs/modkey_unix.go
generated
vendored
Normal file
41
vendor/github.com/evanw/esbuild/internal/fs/modkey_unix.go
generated
vendored
Normal 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
|
||||
}
|
Reference in New Issue
Block a user