vendor tsgo
This commit is contained in:
226
tools/tsgo/internal/api/callbackfs.go
Normal file
226
tools/tsgo/internal/api/callbackfs.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
)
|
||||
|
||||
// callbackFS wraps a base filesystem and delegates certain operations
|
||||
// to the client via RPC callbacks. This allows the API client to provide
|
||||
// a virtual filesystem (e.g., in-memory files for testing).
|
||||
//
|
||||
// The callbacks to enable are specified at construction time via the
|
||||
// --callbacks CLI flag. The connection is set via SetConnection after
|
||||
// the transport connection is established.
|
||||
type callbackFS struct {
|
||||
base vfs.FS
|
||||
enabledCallbacks map[string]bool
|
||||
|
||||
// conn and ctx are set after connection is established
|
||||
conn Conn
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// Callback names that can be enabled
|
||||
const (
|
||||
callbackReadFile = "readFile"
|
||||
callbackFileExists = "fileExists"
|
||||
callbackDirectoryExists = "directoryExists"
|
||||
callbackGetAccessibleEntries = "getAccessibleEntries"
|
||||
callbackRealpath = "realpath"
|
||||
)
|
||||
|
||||
func isCallbackName(name string) bool {
|
||||
switch name {
|
||||
case callbackReadFile,
|
||||
callbackFileExists,
|
||||
callbackDirectoryExists,
|
||||
callbackGetAccessibleEntries,
|
||||
callbackRealpath:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// newCallbackFS creates a new callbackFS wrapping the given base filesystem.
|
||||
// The callbacks slice specifies which filesystem operations should be delegated
|
||||
// to the client (e.g., "readFile", "fileExists").
|
||||
func newCallbackFS(base vfs.FS, callbacks []string) *callbackFS {
|
||||
enabled := make(map[string]bool, len(callbacks))
|
||||
for _, cb := range callbacks {
|
||||
if !isCallbackName(cb) {
|
||||
panic("unknown callback name: " + cb)
|
||||
}
|
||||
enabled[cb] = true
|
||||
}
|
||||
return &callbackFS{
|
||||
base: base,
|
||||
enabledCallbacks: enabled,
|
||||
}
|
||||
}
|
||||
|
||||
// SetConnection sets the RPC connection for callbacks.
|
||||
// This must be called after the transport connection is established
|
||||
// but before any filesystem operations that need callbacks.
|
||||
func (fs *callbackFS) SetConnection(ctx context.Context, conn Conn) {
|
||||
fs.ctx = ctx
|
||||
fs.conn = conn
|
||||
}
|
||||
|
||||
// isEnabled returns true if the named callback is enabled.
|
||||
func (fs *callbackFS) isEnabled(name string) bool {
|
||||
return fs.enabledCallbacks[name]
|
||||
}
|
||||
|
||||
// call invokes a callback on the client and returns the result.
|
||||
func (fs *callbackFS) call(name string, arg any) ([]byte, error) {
|
||||
if fs.conn == nil {
|
||||
return nil, fmt.Errorf("CallbackFS: %s called before connection set", name)
|
||||
}
|
||||
|
||||
result, err := fs.conn.Call(fs.ctx, name, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// UseCaseSensitiveFileNames implements vfs.FS.
|
||||
func (fs *callbackFS) UseCaseSensitiveFileNames() bool {
|
||||
return fs.base.UseCaseSensitiveFileNames()
|
||||
}
|
||||
|
||||
// ReadFile implements vfs.FS.
|
||||
//
|
||||
// The readFile callback uses a wrapped response format to distinguish three states:
|
||||
// - undefined (fall back to real FS): null or empty on wire
|
||||
// - null (not found, no fallback): {"content": null}
|
||||
// - string content: {"content": "..."}
|
||||
func (fs *callbackFS) ReadFile(path string) (contents string, ok bool) {
|
||||
if fs.isEnabled(callbackReadFile) {
|
||||
result, err := fs.call(callbackReadFile, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(result) > 0 && string(result) != "null" {
|
||||
var wrapper struct {
|
||||
Content *string `json:"content"`
|
||||
}
|
||||
if err := json.Unmarshal(result, &wrapper); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if wrapper.Content == nil {
|
||||
return "", false
|
||||
}
|
||||
return *wrapper.Content, true
|
||||
}
|
||||
}
|
||||
return fs.base.ReadFile(path)
|
||||
}
|
||||
|
||||
// FileExists implements vfs.FS.
|
||||
func (fs *callbackFS) FileExists(path string) bool {
|
||||
if fs.isEnabled(callbackFileExists) {
|
||||
result, err := fs.call(callbackFileExists, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(result) > 0 && string(result) != "null" {
|
||||
return string(result) == "true"
|
||||
}
|
||||
}
|
||||
return fs.base.FileExists(path)
|
||||
}
|
||||
|
||||
// DirectoryExists implements vfs.FS.
|
||||
func (fs *callbackFS) DirectoryExists(path string) bool {
|
||||
if fs.isEnabled(callbackDirectoryExists) {
|
||||
result, err := fs.call(callbackDirectoryExists, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(result) > 0 && string(result) != "null" {
|
||||
return string(result) == "true"
|
||||
}
|
||||
}
|
||||
return fs.base.DirectoryExists(path)
|
||||
}
|
||||
|
||||
// GetAccessibleEntries implements vfs.FS.
|
||||
func (fs *callbackFS) GetAccessibleEntries(path string) vfs.Entries {
|
||||
if fs.isEnabled(callbackGetAccessibleEntries) {
|
||||
result, err := fs.call(callbackGetAccessibleEntries, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(result) > 0 {
|
||||
var rawEntries *struct {
|
||||
Files []string `json:"files"`
|
||||
Directories []string `json:"directories"`
|
||||
}
|
||||
if err := json.Unmarshal(result, &rawEntries); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if rawEntries != nil {
|
||||
return vfs.Entries{
|
||||
Files: rawEntries.Files,
|
||||
Directories: rawEntries.Directories,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fs.base.GetAccessibleEntries(path)
|
||||
}
|
||||
|
||||
// Realpath implements vfs.FS.
|
||||
func (fs *callbackFS) Realpath(path string) string {
|
||||
if fs.isEnabled(callbackRealpath) {
|
||||
result, err := fs.call(callbackRealpath, path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(result) > 0 && string(result) != "null" {
|
||||
var realpath string
|
||||
if err := json.Unmarshal(result, &realpath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return realpath
|
||||
}
|
||||
}
|
||||
return fs.base.Realpath(path)
|
||||
}
|
||||
|
||||
// WriteFile implements vfs.FS - always delegates to base (no callback support).
|
||||
func (fs *callbackFS) WriteFile(path string, data string) error {
|
||||
return fs.base.WriteFile(path, data)
|
||||
}
|
||||
|
||||
// AppendFile implements vfs.FS - always delegates to base (no callback support).
|
||||
func (fs *callbackFS) AppendFile(path string, data string) error {
|
||||
return fs.base.AppendFile(path, data)
|
||||
}
|
||||
|
||||
// Remove implements vfs.FS - always delegates to base (no callback support).
|
||||
func (fs *callbackFS) Remove(path string) error {
|
||||
return fs.base.Remove(path)
|
||||
}
|
||||
|
||||
// Chtimes implements vfs.FS - always delegates to base (no callback support).
|
||||
func (fs *callbackFS) Chtimes(path string, aTime time.Time, mTime time.Time) error {
|
||||
return fs.base.Chtimes(path, aTime, mTime)
|
||||
}
|
||||
|
||||
// Stat implements vfs.FS - always delegates to base (no callback support).
|
||||
func (fs *callbackFS) Stat(path string) vfs.FileInfo {
|
||||
return fs.base.Stat(path)
|
||||
}
|
||||
|
||||
// WalkDir implements vfs.FS - always delegates to base (no callback support).
|
||||
func (fs *callbackFS) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
|
||||
return fs.base.WalkDir(root, walkFn)
|
||||
}
|
||||
Reference in New Issue
Block a user