kittenipc/kitcom/internal/tsgo/compiler/projectreferencedtsfakinghost.go
2025-10-15 10:12:44 +03:00

227 lines
7.4 KiB
Go

package compiler
import (
"strings"
"time"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/module"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/cachedvfs"
)
type projectReferenceDtsFakingHost struct {
host CompilerHost
fs *cachedvfs.FS
}
var _ module.ResolutionHost = (*projectReferenceDtsFakingHost)(nil)
func newProjectReferenceDtsFakingHost(loader *fileLoader) module.ResolutionHost {
// Create a new host that will fake the dts files
host := &projectReferenceDtsFakingHost{
host: loader.opts.Host,
fs: cachedvfs.From(&projectReferenceDtsFakingVfs{
projectReferenceFileMapper: loader.projectReferenceFileMapper,
dtsDirectories: loader.dtsDirectories,
knownSymlinks: knownSymlinks{},
}),
}
return host
}
// FS implements module.ResolutionHost.
func (h *projectReferenceDtsFakingHost) FS() vfs.FS {
return h.fs
}
// GetCurrentDirectory implements module.ResolutionHost.
func (h *projectReferenceDtsFakingHost) GetCurrentDirectory() string {
return h.host.GetCurrentDirectory()
}
type projectReferenceDtsFakingVfs struct {
projectReferenceFileMapper *projectReferenceFileMapper
dtsDirectories collections.Set[tspath.Path]
knownSymlinks knownSymlinks
}
var _ vfs.FS = (*projectReferenceDtsFakingVfs)(nil)
// UseCaseSensitiveFileNames implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) UseCaseSensitiveFileNames() bool {
return fs.projectReferenceFileMapper.opts.Host.FS().UseCaseSensitiveFileNames()
}
// FileExists implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) FileExists(path string) bool {
if fs.projectReferenceFileMapper.opts.Host.FS().FileExists(path) {
return true
}
if !tspath.IsDeclarationFileName(path) {
return false
}
// Project references go to source file instead of .d.ts file
return fs.fileOrDirectoryExistsUsingSource(path /*isFile*/, true)
}
// ReadFile implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) ReadFile(path string) (contents string, ok bool) {
// Dont need to override as we cannot mimick read file
return fs.projectReferenceFileMapper.opts.Host.FS().ReadFile(path)
}
// WriteFile implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) WriteFile(path string, data string, writeByteOrderMark bool) error {
panic("should not be called by resolver")
}
// Remove implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) Remove(path string) error {
panic("should not be called by resolver")
}
// Chtimes implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) Chtimes(path string, aTime time.Time, mTime time.Time) error {
panic("should not be called by resolver")
}
// DirectoryExists implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) DirectoryExists(path string) bool {
if fs.projectReferenceFileMapper.opts.Host.FS().DirectoryExists(path) {
fs.handleDirectoryCouldBeSymlink(path)
return true
}
return fs.fileOrDirectoryExistsUsingSource(path /*isFile*/, false)
}
// GetAccessibleEntries implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) GetAccessibleEntries(path string) vfs.Entries {
panic("should not be called by resolver")
}
// Stat implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) Stat(path string) vfs.FileInfo {
panic("should not be called by resolver")
}
// WalkDir implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
panic("should not be called by resolver")
}
// Realpath implements vfs.FS.
func (fs *projectReferenceDtsFakingVfs) Realpath(path string) string {
result, ok := fs.knownSymlinks.Files().Load(fs.toPath(path))
if ok {
return result
}
return fs.projectReferenceFileMapper.opts.Host.FS().Realpath(path)
}
func (fs *projectReferenceDtsFakingVfs) toPath(path string) tspath.Path {
return tspath.ToPath(path, fs.projectReferenceFileMapper.opts.Host.GetCurrentDirectory(), fs.UseCaseSensitiveFileNames())
}
func (fs *projectReferenceDtsFakingVfs) handleDirectoryCouldBeSymlink(directory string) {
if tspath.ContainsIgnoredPath(directory) {
return
}
// Because we already watch node_modules, handle symlinks in there
if !strings.Contains(directory, "/node_modules/") {
return
}
directoryPath := tspath.Path(tspath.EnsureTrailingDirectorySeparator(string(fs.toPath(directory))))
if _, ok := fs.knownSymlinks.Directories().Load(directoryPath); ok {
return
}
realDirectory := fs.Realpath(directory)
var realPath tspath.Path
if realDirectory == directory {
// not symlinked
return
}
if realPath = tspath.Path(tspath.EnsureTrailingDirectorySeparator(string(fs.toPath(realDirectory)))); realPath == directoryPath {
// not symlinked
return
}
fs.knownSymlinks.SetDirectory(directory, directoryPath, &knownDirectoryLink{
Real: tspath.EnsureTrailingDirectorySeparator(realDirectory),
RealPath: realPath,
})
}
func (fs *projectReferenceDtsFakingVfs) fileOrDirectoryExistsUsingSource(fileOrDirectory string, isFile bool) bool {
fileOrDirectoryExistsUsingSource := core.IfElse(isFile, fs.fileExistsIfProjectReferenceDts, fs.directoryExistsIfProjectReferenceDeclDir)
// Check current directory or file
result := fileOrDirectoryExistsUsingSource(fileOrDirectory)
if result != core.TSUnknown {
return result == core.TSTrue
}
knownDirectoryLinks := fs.knownSymlinks.Directories()
if knownDirectoryLinks.Size() == 0 {
return false
}
fileOrDirectoryPath := fs.toPath(fileOrDirectory)
if !strings.Contains(string(fileOrDirectoryPath), "/node_modules/") {
return false
}
if isFile {
_, ok := fs.knownSymlinks.Files().Load(fileOrDirectoryPath)
if ok {
return true
}
}
// If it contains node_modules check if its one of the symlinked path we know of
var exists bool
knownDirectoryLinks.Range(func(directoryPath tspath.Path, knownDirectoryLink *knownDirectoryLink) bool {
relative, hasPrefix := strings.CutPrefix(string(fileOrDirectoryPath), string(directoryPath))
if !hasPrefix {
return true
}
if exists = fileOrDirectoryExistsUsingSource(string(knownDirectoryLink.RealPath) + relative).IsTrue(); exists {
if isFile {
// Store the real path for the file
absolutePath := tspath.GetNormalizedAbsolutePath(fileOrDirectory, fs.projectReferenceFileMapper.opts.Host.GetCurrentDirectory())
fs.knownSymlinks.SetFile(
fileOrDirectoryPath,
knownDirectoryLink.Real+absolutePath[len(directoryPath):],
)
}
return false
}
return true
})
return exists
}
func (fs *projectReferenceDtsFakingVfs) fileExistsIfProjectReferenceDts(file string) core.Tristate {
source := fs.projectReferenceFileMapper.getProjectReferenceFromOutputDts(fs.toPath(file))
if source != nil {
return core.IfElse(fs.projectReferenceFileMapper.opts.Host.FS().FileExists(source.Source), core.TSTrue, core.TSFalse)
}
return core.TSUnknown
}
func (fs *projectReferenceDtsFakingVfs) directoryExistsIfProjectReferenceDeclDir(dir string) core.Tristate {
dirPath := fs.toPath(dir)
dirPathWithTrailingDirectorySeparator := dirPath + "/"
for declDirPath := range fs.dtsDirectories.Keys() {
if dirPath == declDirPath ||
// Any parent directory of declaration dir
strings.HasPrefix(string(declDirPath), string(dirPathWithTrailingDirectorySeparator)) ||
// Any directory inside declaration dir
strings.HasPrefix(string(dirPath), string(declDirPath)+"/") {
return core.TSTrue
}
}
return core.TSUnknown
}