Skip to content

Commit 3bbf3cf

Browse files
authored
Add FS implementation (#80)
1 parent 4a51ebc commit 3bbf3cf

File tree

12 files changed

+669
-204
lines changed

12 files changed

+669
-204
lines changed

cmd/tsgo/main.go

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import (
88
"runtime"
99
"strings"
1010
"time"
11-
"unicode"
1211

1312
"github.com/microsoft/typescript-go/internal/ast"
1413
ts "github.com/microsoft/typescript-go/internal/compiler"
1514
"github.com/microsoft/typescript-go/internal/core"
1615
"github.com/microsoft/typescript-go/internal/scanner"
1716
"github.com/microsoft/typescript-go/internal/tspath"
17+
"github.com/microsoft/typescript-go/internal/vfs"
1818
)
1919

2020
var quiet = false
@@ -54,8 +54,22 @@ func main() {
5454

5555
rootPath := flag.Arg(0)
5656
compilerOptions := &core.CompilerOptions{Strict: core.TSTrue, Target: core.ScriptTargetESNext, ModuleKind: core.ModuleKindNodeNext}
57-
programOptions := ts.ProgramOptions{RootPath: rootPath, Options: compilerOptions, SingleThreaded: singleThreaded}
58-
useCaseSensitiveFileNames := isFileSystemCaseSensitive()
57+
currentDirectory, err := os.Getwd()
58+
if err != nil {
59+
fmt.Fprintf(os.Stderr, "Error getting current directory: %v\n", err)
60+
os.Exit(1)
61+
}
62+
fs := vfs.FromOS()
63+
useCaseSensitiveFileNames := fs.UseCaseSensitiveFileNames()
64+
host := ts.NewCompilerHost(compilerOptions, singleThreaded, currentDirectory, fs)
65+
66+
normalizedRootPath := tspath.ResolvePath(currentDirectory, rootPath)
67+
if !fs.DirectoryExists(normalizedRootPath) {
68+
fmt.Fprintf(os.Stderr, "Error: The directory %v does not exist.\n", normalizedRootPath)
69+
os.Exit(1)
70+
}
71+
72+
programOptions := ts.ProgramOptions{RootPath: normalizedRootPath, Options: compilerOptions, SingleThreaded: singleThreaded, Host: host}
5973

6074
startTime := time.Now()
6175
program := ts.NewProgram(programOptions)
@@ -78,11 +92,6 @@ func main() {
7892
runtime.ReadMemStats(&memStats)
7993
if !quiet && len(diagnostics) != 0 {
8094
if pretty {
81-
currentDirectory, err := os.Getwd()
82-
if err != nil {
83-
panic("no current directory")
84-
}
85-
8695
var output strings.Builder
8796
formatOpts := ts.DiagnosticsFormattingOptions{
8897
NewLine: "\n",
@@ -107,28 +116,3 @@ func main() {
107116
fmt.Printf("Compile time: %v\n", compileTime)
108117
fmt.Printf("Memory used: %vK\n", memStats.Alloc/1024)
109118
}
110-
111-
func isFileSystemCaseSensitive() bool {
112-
// win32/win64 are case insensitive platforms
113-
if runtime.GOOS == "windows" {
114-
return false
115-
}
116-
117-
// If the current executable exists under a different case, we must be case-insensitve.
118-
if _, err := os.Stat(swapCase(os.Args[0])); os.IsNotExist(err) {
119-
return false
120-
}
121-
return true
122-
}
123-
124-
// Convert all lowercase chars to uppercase, and vice-versa
125-
func swapCase(str string) string {
126-
return strings.Map(func(r rune) rune {
127-
upper := unicode.ToUpper(r)
128-
if upper == r {
129-
return unicode.ToLower(r)
130-
} else {
131-
return upper
132-
}
133-
}, str)
134-
}

internal/ast/ast.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ast
22

33
import (
44
"github.com/microsoft/typescript-go/internal/core"
5+
"github.com/microsoft/typescript-go/internal/tspath"
56
)
67

78
// Visitor
@@ -5093,7 +5094,7 @@ type SourceFile struct {
50935094
LocalsContainerBase
50945095
Text string
50955096
fileName string
5096-
path string
5097+
path tspath.Path
50975098
Statements *NodeList // NodeList[*Statement]
50985099
diagnostics []*Diagnostic
50995100
bindDiagnostics []*Diagnostic
@@ -5132,11 +5133,11 @@ func (node *SourceFile) FileName() string {
51325133
return node.fileName
51335134
}
51345135

5135-
func (node *SourceFile) Path() string {
5136+
func (node *SourceFile) Path() tspath.Path {
51365137
return node.path
51375138
}
51385139

5139-
func (node *SourceFile) SetPath(p string) {
5140+
func (node *SourceFile) SetPath(p tspath.Path) {
51405141
node.path = p
51415142
}
51425143

internal/compiler/error_reporting.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,9 @@ func WriteLocation(output *strings.Builder, file *ast.SourceFile, pos int, forma
199199
firstLine, firstChar := scanner.GetLineAndCharacterOfPosition(file, pos)
200200
var relativeFileName string
201201
if formatOpts != nil {
202-
relativeFileName = tspath.ConvertToRelativePath(file.Path(), formatOpts.ComparePathsOptions)
202+
relativeFileName = tspath.ConvertToRelativePath(file.FileName(), formatOpts.ComparePathsOptions)
203203
} else {
204-
relativeFileName = file.Path()
204+
relativeFileName = file.FileName()
205205
}
206206

207207
writeWithStyleAndReset(output, relativeFileName, foregroundColorEscapeCyan)
@@ -333,7 +333,7 @@ func prettyPathForFileError(file *ast.SourceFile, fileErrors []*ast.Diagnostic,
333333
line, _ := scanner.GetLineAndCharacterOfPosition(file, fileErrors[0].Loc().Pos())
334334
fileName := file.FileName()
335335
if tspath.PathIsAbsolute(fileName) && tspath.PathIsAbsolute(formatOpts.CurrentDirectory) {
336-
fileName = tspath.ConvertToRelativePath(file.Path(), formatOpts.ComparePathsOptions)
336+
fileName = tspath.ConvertToRelativePath(file.FileName(), formatOpts.ComparePathsOptions)
337337
}
338338
return fmt.Sprintf("%s%s:%d%s",
339339
fileName,

internal/compiler/host.go

Lines changed: 15 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
package compiler
22

33
import (
4-
"bytes"
5-
"encoding/binary"
6-
"fmt"
7-
"io/fs"
8-
"os"
9-
"path/filepath"
10-
"slices"
11-
"strings"
124
"sync"
13-
"unicode/utf16"
145

156
"github.com/microsoft/typescript-go/internal/core"
7+
"github.com/microsoft/typescript-go/internal/vfs"
168
)
179

1810
type CompilerHost interface {
19-
ReadFile(fileName string) (text string, ok bool)
20-
ReadDirectory(rootPath string, extensions []string) []FileInfo
21-
AbsFileName(fileName string) string
11+
FS() vfs.FS
12+
GetCurrentDirectory() string
2213
RunTask(fn func())
2314
WaitForTasks()
2415
}
@@ -29,73 +20,28 @@ type FileInfo struct {
2920
}
3021

3122
type compilerHost struct {
32-
options *core.CompilerOptions
33-
singleThreaded bool
34-
wg sync.WaitGroup
35-
readSema chan struct{}
23+
options *core.CompilerOptions
24+
singleThreaded bool
25+
wg sync.WaitGroup
26+
currentDirectory string
27+
fs vfs.FS
3628
}
3729

38-
func NewCompilerHost(options *core.CompilerOptions, singleThreaded bool) CompilerHost {
30+
func NewCompilerHost(options *core.CompilerOptions, singleThreaded bool, currentDirectory string, fs vfs.FS) CompilerHost {
3931
h := &compilerHost{}
4032
h.options = options
4133
h.singleThreaded = singleThreaded
42-
h.readSema = make(chan struct{}, 128)
34+
h.currentDirectory = currentDirectory
35+
h.fs = fs
4336
return h
4437
}
4538

46-
func (h *compilerHost) ReadFile(fileName string) (text string, ok bool) {
47-
h.readSema <- struct{}{}
48-
b, err := os.ReadFile(fileName)
49-
<-h.readSema
50-
if err != nil {
51-
return "", false
52-
}
53-
var bom [2]byte
54-
if len(b) >= 2 {
55-
bom = [2]byte{b[0], b[1]}
56-
switch bom {
57-
case [2]byte{0xFF, 0xFE}:
58-
return decodeUtf16(b[2:], binary.LittleEndian), true
59-
case [2]byte{0xFE, 0xFF}:
60-
return decodeUtf16(b[2:], binary.BigEndian), true
61-
}
62-
}
63-
if len(b) >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF {
64-
b = b[3:]
65-
}
66-
return string(b), true
39+
func (h *compilerHost) FS() vfs.FS {
40+
return h.fs
6741
}
6842

69-
func decodeUtf16(b []byte, order binary.ByteOrder) string {
70-
ints := make([]uint16, len(b)/2)
71-
if err := binary.Read(bytes.NewReader(b), order, &ints); err != nil {
72-
return ""
73-
}
74-
return string(utf16.Decode(ints))
75-
}
76-
77-
func (h *compilerHost) ReadDirectory(rootDir string, extensions []string) []FileInfo {
78-
var fileInfos []FileInfo
79-
_ = filepath.Walk(rootDir, func(path string, info fs.FileInfo, err error) error {
80-
if err != nil {
81-
fmt.Println(err)
82-
os.Exit(1)
83-
}
84-
if !info.IsDir() && slices.ContainsFunc(extensions, func(ext string) bool { return strings.HasSuffix(path, ext) }) {
85-
fileInfos = append(fileInfos, FileInfo{Name: path, Size: info.Size()})
86-
}
87-
return nil
88-
})
89-
return fileInfos
90-
}
91-
92-
func (h *compilerHost) AbsFileName(fileName string) string {
93-
absFileName, err := filepath.Abs(fileName)
94-
if err != nil {
95-
fmt.Println(err)
96-
os.Exit(1)
97-
}
98-
return absFileName
43+
func (h *compilerHost) GetCurrentDirectory() string {
44+
return h.currentDirectory
9945
}
10046

10147
func (h *compilerHost) RunTask(task func()) {

internal/compiler/module/resolver.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ func (r *resolutionState) loadModuleFromNearestNodeModulesDirectoryWorker(ext ex
357357

358358
func (r *resolutionState) loadModuleFromImmediateNodeModulesDirectory(extensions extensions, directory string, typesScopeOnly bool) *resolved {
359359
nodeModulesFolder := tspath.CombinePaths(directory, "node_modules")
360-
nodeModulesFolderExists := r.resolver.host.DirectoryExists(nodeModulesFolder)
360+
nodeModulesFolderExists := r.resolver.host.FS().DirectoryExists(nodeModulesFolder)
361361
if !nodeModulesFolderExists && r.resolver.traceEnabled() {
362362
r.resolver.host.Trace(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(nodeModulesFolder))
363363
}
@@ -370,7 +370,7 @@ func (r *resolutionState) loadModuleFromImmediateNodeModulesDirectory(extensions
370370

371371
if extensions&ExtensionsDeclaration != 0 {
372372
nodeModulesAtTypes := tspath.CombinePaths(nodeModulesFolder, "@types")
373-
nodeModulesAtTypesExists := nodeModulesFolderExists && r.resolver.host.DirectoryExists(nodeModulesAtTypes)
373+
nodeModulesAtTypesExists := nodeModulesFolderExists && r.resolver.host.FS().DirectoryExists(nodeModulesAtTypes)
374374
if !nodeModulesAtTypesExists && r.resolver.traceEnabled() {
375375
r.resolver.host.Trace(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(nodeModulesAtTypes))
376376
}
@@ -472,7 +472,7 @@ func (r *resolutionState) loadModuleFromSpecificNodeModulesDirectory(ext extensi
472472
if r.resolver.traceEnabled() {
473473
r.resolver.host.Trace(diagnostics.X_package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2.Format(versionPaths.Version, core.Version, rest))
474474
}
475-
packageDirectoryExists := nodeModulesDirectoryExists && r.resolver.host.DirectoryExists(packageDirectory)
475+
packageDirectoryExists := nodeModulesDirectoryExists && r.resolver.host.FS().DirectoryExists(packageDirectory)
476476
pathPatterns := tryParsePatterns(versionPaths.GetPaths())
477477
if fromPaths := r.tryLoadModuleUsingPaths(ext, rest, packageDirectory, versionPaths.GetPaths(), pathPatterns, loader, !packageDirectoryExists); fromPaths.stop {
478478
return fromPaths.value
@@ -561,7 +561,7 @@ func (r *resolutionState) createResolvedModuleWithFailedLookupLocations(resolved
561561
func (r *resolutionState) getOriginalAndResolvedFileName(fileName string) (string, string) {
562562
resolvedFileName := r.realPath(fileName)
563563
comparePathsOptions := tspath.ComparePathsOptions{
564-
UseCaseSensitiveFileNames: r.resolver.host.UseCaseSensitiveFileNames(),
564+
UseCaseSensitiveFileNames: r.resolver.host.FS().UseCaseSensitiveFileNames(),
565565
CurrentDirectory: r.resolver.host.GetCurrentDirectory(),
566566
}
567567
if tspath.ComparePaths(fileName, resolvedFileName, comparePathsOptions) == 0 {
@@ -618,7 +618,7 @@ func (r *resolutionState) tryLoadModuleUsingPaths(extensions extensions, moduleN
618618
})
619619
}
620620
}
621-
if resolved := loader(extensions, candidate, onlyRecordFailures || !r.resolver.host.DirectoryExists(tspath.GetDirectoryPath(candidate))); resolved != nil {
621+
if resolved := loader(extensions, candidate, onlyRecordFailures || !r.resolver.host.FS().DirectoryExists(tspath.GetDirectoryPath(candidate))); resolved != nil {
622622
return newSearchResult(resolved)
623623
}
624624
}
@@ -633,7 +633,7 @@ func (r *resolutionState) nodeLoadModuleByRelativeName(extensions extensions, ca
633633
if !tspath.HasTrailingDirectorySeparator(candidate) {
634634
if !onlyRecordFailures {
635635
parentOfCandidate := tspath.GetDirectoryPath(candidate)
636-
if !r.resolver.host.DirectoryExists(parentOfCandidate) {
636+
if !r.resolver.host.FS().DirectoryExists(parentOfCandidate) {
637637
if r.resolver.traceEnabled() {
638638
r.resolver.host.Trace(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(parentOfCandidate))
639639
}
@@ -651,7 +651,7 @@ func (r *resolutionState) nodeLoadModuleByRelativeName(extensions extensions, ca
651651
}
652652
}
653653
if !onlyRecordFailures {
654-
candidateExists := r.resolver.host.DirectoryExists(candidate)
654+
candidateExists := r.resolver.host.FS().DirectoryExists(candidate)
655655
if !candidateExists {
656656
if r.resolver.traceEnabled() {
657657
r.resolver.host.Trace(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(candidate))
@@ -704,7 +704,7 @@ func (r *resolutionState) loadModuleFromFileNoImplicitExtensions(extensions exte
704704
func (r *resolutionState) tryAddingExtensions(extensionless string, extensions extensions, originalExtension string, onlyRecordFailures bool) *resolved {
705705
if !onlyRecordFailures {
706706
directory := tspath.GetDirectoryPath(extensionless)
707-
onlyRecordFailures = directory != "" && !r.resolver.host.DirectoryExists(directory)
707+
onlyRecordFailures = directory != "" && !r.resolver.host.FS().DirectoryExists(directory)
708708
}
709709

710710
switch originalExtension {
@@ -848,7 +848,7 @@ func (r *resolutionState) tryFile(fileName string, onlyRecordFailures bool) (str
848848

849849
func (r *resolutionState) tryFileLookup(fileName string, onlyRecordFailures bool) bool {
850850
if !onlyRecordFailures {
851-
if r.resolver.host.FileExists(fileName) {
851+
if r.resolver.host.FS().FileExists(fileName) {
852852
if r.resolver.traceEnabled() {
853853
r.resolver.host.Trace(diagnostics.File_0_exists_use_it_as_a_name_resolution_result.Format(fileName))
854854
}
@@ -889,7 +889,7 @@ func (r *resolutionState) loadNodeModuleFromDirectoryWorker(ext extensions, cand
889889
if packageInfo != nil {
890890
if file, ok := r.getPackageFile(ext, packageInfo); ok {
891891
packageFile = file
892-
onlyRecordFailuresForPackageFile = !r.resolver.host.DirectoryExists(tspath.GetDirectoryPath(file))
892+
onlyRecordFailuresForPackageFile = !r.resolver.host.FS().DirectoryExists(tspath.GetDirectoryPath(file))
893893
}
894894
}
895895

@@ -957,7 +957,7 @@ func (r *resolutionState) loadNodeModuleFromDirectoryWorker(ext extensions, cand
957957

958958
// ESM mode resolutions don't do package 'index' lookups
959959
if !r.esmMode {
960-
return r.loadModuleFromFile(ext, indexPath, onlyRecordFailures || !r.resolver.host.DirectoryExists(candidate))
960+
return r.loadModuleFromFile(ext, indexPath, onlyRecordFailures || !r.resolver.host.FS().DirectoryExists(candidate))
961961
}
962962
return nil
963963
}
@@ -1046,10 +1046,11 @@ func (r *resolutionState) getPackageJsonInfo(packageDirectory string, onlyRecord
10461046
}
10471047
}
10481048

1049-
directoryExists := r.resolver.host.DirectoryExists(packageDirectory)
1050-
if directoryExists && r.resolver.host.FileExists(packageJsonPath) {
1049+
directoryExists := r.resolver.host.FS().DirectoryExists(packageDirectory)
1050+
if directoryExists && r.resolver.host.FS().FileExists(packageJsonPath) {
10511051
// Ignore error
1052-
packageJsonContent, _ := packagejson.Parse([]byte(r.resolver.host.ReadFile(packageJsonPath)))
1052+
contents, _ := r.resolver.host.FS().ReadFile(packageJsonPath)
1053+
packageJsonContent, _ := packagejson.Parse([]byte(contents))
10531054
if r.resolver.traceEnabled() {
10541055
r.resolver.host.Trace(diagnostics.Found_package_json_at_0.Format(packageJsonPath))
10551056
}
@@ -1132,7 +1133,7 @@ func (r *resolutionState) readPackageJsonPeerDependencies(packageJsonInfo *packa
11321133
}
11331134

11341135
func (r *resolutionState) realPath(path string) string {
1135-
rp := tspath.NormalizePath(r.resolver.host.Realpath(path))
1136+
rp := tspath.NormalizePath(r.resolver.host.FS().Realpath(path))
11361137
if r.resolver.traceEnabled() {
11371138
r.resolver.host.Trace(diagnostics.Resolving_real_path_for_0_result_1.Format(path, rp))
11381139
}

0 commit comments

Comments
 (0)