vendor tsgo
This commit is contained in:
222
tools/tsgo/internal/vfs/iovfs/iofs.go
Normal file
222
tools/tsgo/internal/vfs/iovfs/iofs.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package iovfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/internal"
|
||||
)
|
||||
|
||||
type RealpathFS interface {
|
||||
fs.FS
|
||||
Realpath(path string) (string, error)
|
||||
}
|
||||
|
||||
type WritableFS interface {
|
||||
fs.FS
|
||||
WriteFile(path string, data string, perm fs.FileMode) error
|
||||
AppendFile(path string, data string, perm fs.FileMode) error
|
||||
MkdirAll(path string, perm fs.FileMode) error
|
||||
// Removes `path` and all its contents. Will return the first error it encounters.
|
||||
Remove(path string) error
|
||||
Chtimes(path string, aTime time.Time, mTime time.Time) error
|
||||
}
|
||||
|
||||
type FsWithSys interface {
|
||||
vfs.FS
|
||||
FSys() fs.FS
|
||||
}
|
||||
|
||||
// From creates a new FS from an [fs.FS].
|
||||
//
|
||||
// For paths like `c:/foo/bar`, fsys will be used as though it's rooted at `/` and the path is `/c:/foo/bar`.
|
||||
//
|
||||
// If the provided [fs.FS] implements [RealpathFS], it will be used to implement the Realpath method.
|
||||
// If the provided [fs.FS] implements [WritableFS], it will be used to implement the WriteFile method.
|
||||
//
|
||||
// From does not actually handle case-insensitivity; ensure the passed in [fs.FS]
|
||||
// respects case-insensitive file names if needed. Consider using [vfstest.FromMap] for testing.
|
||||
func From(fsys fs.FS, useCaseSensitiveFileNames bool) FsWithSys {
|
||||
var realpath func(path string) (string, error)
|
||||
if fsys, ok := fsys.(RealpathFS); ok {
|
||||
realpath = func(path string) (string, error) {
|
||||
rest, hadSlash := strings.CutPrefix(path, "/")
|
||||
rp, err := fsys.Realpath(rest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hadSlash {
|
||||
return "/" + rp, nil
|
||||
}
|
||||
return rp, nil
|
||||
}
|
||||
} else {
|
||||
realpath = func(path string) (string, error) {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
|
||||
var writeFile func(path string, content string) error
|
||||
var appendFile func(path string, content string) error
|
||||
var mkdirAll func(path string) error
|
||||
var remove func(path string) error
|
||||
var chtimes func(path string, aTime time.Time, mTime time.Time) error
|
||||
if fsys, ok := fsys.(WritableFS); ok {
|
||||
writeFile = func(path string, content string) error {
|
||||
rest, _ := strings.CutPrefix(path, "/")
|
||||
return fsys.WriteFile(rest, content, 0o666)
|
||||
}
|
||||
appendFile = func(path string, content string) error {
|
||||
rest, _ := strings.CutPrefix(path, "/")
|
||||
return fsys.AppendFile(rest, content, 0o666)
|
||||
}
|
||||
mkdirAll = func(path string) error {
|
||||
rest, _ := strings.CutPrefix(path, "/")
|
||||
return fsys.MkdirAll(rest, 0o777)
|
||||
}
|
||||
remove = func(path string) error {
|
||||
rest, _ := strings.CutPrefix(path, "/")
|
||||
return fsys.Remove(rest)
|
||||
}
|
||||
chtimes = func(path string, aTime time.Time, mTime time.Time) error {
|
||||
rest, _ := strings.CutPrefix(path, "/")
|
||||
return fsys.Chtimes(rest, aTime, mTime)
|
||||
}
|
||||
} else {
|
||||
writeFile = func(string, string) error {
|
||||
panic("writeFile not supported")
|
||||
}
|
||||
appendFile = func(string, string) error {
|
||||
panic("appendFile not supported")
|
||||
}
|
||||
mkdirAll = func(string) error {
|
||||
panic("mkdirAll not supported")
|
||||
}
|
||||
remove = func(string) error {
|
||||
panic("remove not supported")
|
||||
}
|
||||
chtimes = func(string, time.Time, time.Time) error {
|
||||
panic("chtimes not supported")
|
||||
}
|
||||
}
|
||||
|
||||
return &ioFS{
|
||||
common: internal.Common{
|
||||
RootFor: func(root string) fs.FS {
|
||||
if root == "/" {
|
||||
return fsys
|
||||
}
|
||||
|
||||
p := tspath.RemoveTrailingDirectorySeparator(root)
|
||||
sub, err := fs.Sub(fsys, p)
|
||||
if err != nil {
|
||||
if tspath.IsUrl(root) {
|
||||
return nil
|
||||
}
|
||||
panic(fmt.Sprintf("vfs: failed to create sub file system for %q: %v", p, err))
|
||||
}
|
||||
return sub
|
||||
},
|
||||
},
|
||||
useCaseSensitiveFileNames: useCaseSensitiveFileNames,
|
||||
realpath: realpath,
|
||||
writeFile: writeFile,
|
||||
appendFile: appendFile,
|
||||
mkdirAll: mkdirAll,
|
||||
remove: remove,
|
||||
chtimes: chtimes,
|
||||
fsys: fsys,
|
||||
}
|
||||
}
|
||||
|
||||
type ioFS struct {
|
||||
common internal.Common
|
||||
|
||||
useCaseSensitiveFileNames bool
|
||||
realpath func(path string) (string, error)
|
||||
writeFile func(path string, content string) error
|
||||
appendFile func(path string, content string) error
|
||||
mkdirAll func(path string) error
|
||||
remove func(path string) error
|
||||
chtimes func(path string, aTime time.Time, mTime time.Time) error
|
||||
fsys fs.FS
|
||||
}
|
||||
|
||||
var _ FsWithSys = (*ioFS)(nil)
|
||||
|
||||
func (vfs *ioFS) UseCaseSensitiveFileNames() bool {
|
||||
return vfs.useCaseSensitiveFileNames
|
||||
}
|
||||
|
||||
func (vfs *ioFS) DirectoryExists(path string) bool {
|
||||
return vfs.common.DirectoryExists(path)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) FileExists(path string) bool {
|
||||
return vfs.common.FileExists(path)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) GetAccessibleEntries(path string) vfs.Entries {
|
||||
return vfs.common.GetAccessibleEntries(path)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) Stat(path string) vfs.FileInfo {
|
||||
_ = internal.RootLength(path) // Assert path is rooted
|
||||
return vfs.common.Stat(path)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) ReadFile(path string) (contents string, ok bool) {
|
||||
return vfs.common.ReadFile(path)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
|
||||
return vfs.common.WalkDir(root, walkFn)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) Remove(path string) error {
|
||||
_ = internal.RootLength(path) // Assert path is rooted
|
||||
return vfs.remove(path)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) Chtimes(path string, aTime time.Time, mTime time.Time) error {
|
||||
_ = internal.RootLength(path) // Assert path is rooted
|
||||
return vfs.chtimes(path, aTime, mTime)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) Realpath(path string) string {
|
||||
root, rest := internal.SplitPath(path)
|
||||
// splitPath normalizes the path into parts (e.g. "c:/foo/bar" -> "c:/", "foo/bar")
|
||||
// Put them back together to call realpath.
|
||||
realpath, err := vfs.realpath(root + rest)
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
return realpath
|
||||
}
|
||||
|
||||
func (vfs *ioFS) writeFileEnsuringDir(path string, content string, write func(path, content string) error) error {
|
||||
_ = internal.RootLength(path) // Assert path is rooted
|
||||
if err := write(path, content); err == nil {
|
||||
return nil
|
||||
}
|
||||
if err := vfs.mkdirAll(tspath.GetDirectoryPath(tspath.NormalizePath(path))); err != nil {
|
||||
return err
|
||||
}
|
||||
return write(path, content)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) WriteFile(path string, content string) error {
|
||||
return vfs.writeFileEnsuringDir(path, content, vfs.writeFile)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) AppendFile(path string, content string) error {
|
||||
return vfs.writeFileEnsuringDir(path, content, vfs.appendFile)
|
||||
}
|
||||
|
||||
func (vfs *ioFS) FSys() fs.FS {
|
||||
return vfs.fsys
|
||||
}
|
||||
134
tools/tsgo/internal/vfs/iovfs/iofs_test.go
Normal file
134
tools/tsgo/internal/vfs/iovfs/iofs_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package iovfs_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/testutil"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/iovfs"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestIOFS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testfs := fstest.MapFS{
|
||||
"foo.ts": &fstest.MapFile{
|
||||
Data: []byte("hello, world"),
|
||||
},
|
||||
"dir1/file1.ts": &fstest.MapFile{
|
||||
Data: []byte("export const foo = 42;"),
|
||||
},
|
||||
"dir1/file2.ts": &fstest.MapFile{
|
||||
Data: []byte("export const foo = 42;"),
|
||||
},
|
||||
"dir2/file1.ts": &fstest.MapFile{
|
||||
Data: []byte("export const foo = 42;"),
|
||||
},
|
||||
}
|
||||
|
||||
fs := iovfs.From(testfs, true)
|
||||
|
||||
t.Run("ReadFile", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
content, ok := fs.ReadFile("/foo.ts")
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, content, "hello, world")
|
||||
|
||||
content, ok = fs.ReadFile("/does/not/exist.ts")
|
||||
assert.Assert(t, !ok)
|
||||
assert.Equal(t, content, "")
|
||||
})
|
||||
|
||||
t.Run("ReadFileUnrooted", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testutil.AssertPanics(t, func() { fs.ReadFile("bar") }, `vfs: path "bar" is not absolute`)
|
||||
})
|
||||
|
||||
t.Run("FileExists", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Assert(t, fs.FileExists("/foo.ts"))
|
||||
assert.Assert(t, !fs.FileExists("/bar"))
|
||||
})
|
||||
|
||||
t.Run("DirectoryExists", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Assert(t, fs.DirectoryExists("/"))
|
||||
assert.Assert(t, fs.DirectoryExists("/dir1"))
|
||||
assert.Assert(t, fs.DirectoryExists("/dir1/"))
|
||||
assert.Assert(t, fs.DirectoryExists("/dir1/./"))
|
||||
assert.Assert(t, !fs.DirectoryExists("/bar"))
|
||||
})
|
||||
|
||||
t.Run("GetAccessibleEntries", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
entries := fs.GetAccessibleEntries("/")
|
||||
assert.DeepEqual(t, entries.Directories, []string{"dir1", "dir2"})
|
||||
assert.DeepEqual(t, entries.Files, []string{"foo.ts"})
|
||||
})
|
||||
|
||||
t.Run("WalkDir", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var files []string
|
||||
err := fs.WalkDir("/", func(path string, d vfs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
slices.Sort(files)
|
||||
|
||||
assert.DeepEqual(t, files, []string{"/dir1/file1.ts", "/dir1/file2.ts", "/dir2/file1.ts", "/foo.ts"})
|
||||
})
|
||||
|
||||
t.Run("WalkDirSkip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var files []string
|
||||
err := fs.WalkDir("/", func(path string, d vfs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
files = append(files, path)
|
||||
}
|
||||
|
||||
if path == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return vfs.SkipDir
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
slices.Sort(files)
|
||||
|
||||
assert.DeepEqual(t, files, []string{"/foo.ts"})
|
||||
})
|
||||
|
||||
t.Run("Realpath", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
realpath := fs.Realpath("/foo.ts")
|
||||
assert.Equal(t, realpath, "/foo.ts")
|
||||
})
|
||||
|
||||
t.Run("UseCaseSensitiveFileNames", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Assert(t, fs.UseCaseSensitiveFileNames())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user