Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
454c9e5
Some
jakebailey Nov 14, 2024
c3d62eb
wip
jakebailey Nov 15, 2024
4ff70c6
more
jakebailey Nov 15, 2024
ef25423
Starting testing
jakebailey Nov 15, 2024
e819b4f
Goodbye paths I guess
jakebailey Nov 15, 2024
94109bb
Plumb it in a bit
jakebailey Nov 15, 2024
b11bca8
Fix walk path
jakebailey Nov 16, 2024
b8f999b
lint
jakebailey Nov 16, 2024
8aeeeca
one more filepath gone
jakebailey Nov 16, 2024
e577850
Merge branch 'main' into jabaile/vfs
jakebailey Nov 20, 2024
d92c79b
Port over module resolution
jakebailey Nov 20, 2024
ea0a525
TODO link
jakebailey Nov 20, 2024
caf634c
rename type
jakebailey Nov 20, 2024
c1aa720
Merge branch 'main' into jabaile/vfs
jakebailey Nov 21, 2024
b48a44f
Pass more through
jakebailey Nov 21, 2024
70f4054
realpath maybe
jakebailey Nov 21, 2024
6c207ea
Merge branch 'main' into jabaile/vfs
jakebailey Nov 21, 2024
7011e0a
Rename func
jakebailey Nov 21, 2024
4c7fa6a
Remove GetCurrentDirectory
jakebailey Nov 21, 2024
84e9f30
Fix slashes
jakebailey Nov 22, 2024
dc2b6be
It's a method
jakebailey Nov 22, 2024
5b76069
Remove todos
jakebailey Nov 22, 2024
fe8b237
Remove some todos that will go away later in program construction
jakebailey Nov 22, 2024
c046c29
leave todo
jakebailey Nov 22, 2024
8431b58
Push Getwd to main
jakebailey Nov 22, 2024
af382ea
Panic on unrooted paths
jakebailey Nov 22, 2024
b7cfe66
Remove some incorrect uses of Path
jakebailey Nov 22, 2024
9d05ed0
Eliminate AbsFileName
jakebailey Nov 22, 2024
43f8f6d
Forgot an assignment
jakebailey Nov 22, 2024
76d6460
Reorder for clarity
jakebailey Nov 22, 2024
87f571d
Add tests besides UTF-16
jakebailey Nov 22, 2024
b174bb8
log
jakebailey Nov 22, 2024
4409aa8
Add BOM tests
jakebailey Nov 22, 2024
89f2e74
Don't copy paste expected string
jakebailey Nov 22, 2024
719ee33
Leave comment about iofs case problem
jakebailey Nov 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 17 additions & 33 deletions cmd/tsgo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"runtime"
"strings"
"time"
"unicode"

"github.com/microsoft/typescript-go/internal/ast"
ts "github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
)

var quiet = false
Expand Down Expand Up @@ -54,8 +54,22 @@ func main() {

rootPath := flag.Arg(0)
compilerOptions := &core.CompilerOptions{Strict: core.TSTrue, Target: core.ScriptTargetESNext, ModuleKind: core.ModuleKindNodeNext}
programOptions := ts.ProgramOptions{RootPath: rootPath, Options: compilerOptions, SingleThreaded: singleThreaded}
useCaseSensitiveFileNames := isFileSystemCaseSensitive()
currentDirectory, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting current directory: %v\n", err)
os.Exit(1)
}
fs := vfs.FromOS()
useCaseSensitiveFileNames := fs.UseCaseSensitiveFileNames()
host := ts.NewCompilerHost(compilerOptions, singleThreaded, currentDirectory, fs)

normalizedRootPath := tspath.ResolvePath(currentDirectory, rootPath)
if !fs.DirectoryExists(normalizedRootPath) {
fmt.Fprintf(os.Stderr, "Error: The directory %v does not exist.\n", normalizedRootPath)
os.Exit(1)
}

programOptions := ts.ProgramOptions{RootPath: normalizedRootPath, Options: compilerOptions, SingleThreaded: singleThreaded, Host: host}

startTime := time.Now()
program := ts.NewProgram(programOptions)
Expand All @@ -78,11 +92,6 @@ func main() {
runtime.ReadMemStats(&memStats)
if !quiet && len(diagnostics) != 0 {
if pretty {
currentDirectory, err := os.Getwd()
if err != nil {
panic("no current directory")
}

var output strings.Builder
formatOpts := ts.DiagnosticsFormattingOptions{
NewLine: "\n",
Expand All @@ -107,28 +116,3 @@ func main() {
fmt.Printf("Compile time: %v\n", compileTime)
fmt.Printf("Memory used: %vK\n", memStats.Alloc/1024)
}

func isFileSystemCaseSensitive() bool {
// win32/win64 are case insensitive platforms
if runtime.GOOS == "windows" {
return false
}

// If the current executable exists under a different case, we must be case-insensitve.
if _, err := os.Stat(swapCase(os.Args[0])); os.IsNotExist(err) {
return false
}
return true
}

// Convert all lowercase chars to uppercase, and vice-versa
func swapCase(str string) string {
return strings.Map(func(r rune) rune {
upper := unicode.ToUpper(r)
if upper == r {
return unicode.ToLower(r)
} else {
return upper
}
}, str)
}
7 changes: 4 additions & 3 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ast

import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/tspath"
)

// Visitor
Expand Down Expand Up @@ -5093,7 +5094,7 @@ type SourceFile struct {
LocalsContainerBase
Text string
fileName string
path string
path tspath.Path
Statements *NodeList // NodeList[*Statement]
diagnostics []*Diagnostic
bindDiagnostics []*Diagnostic
Expand Down Expand Up @@ -5132,11 +5133,11 @@ func (node *SourceFile) FileName() string {
return node.fileName
}

func (node *SourceFile) Path() string {
func (node *SourceFile) Path() tspath.Path {
return node.path
}

func (node *SourceFile) SetPath(p string) {
func (node *SourceFile) SetPath(p tspath.Path) {
node.path = p
}

Expand Down
6 changes: 3 additions & 3 deletions internal/compiler/error_reporting.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ func WriteLocation(output *strings.Builder, file *ast.SourceFile, pos int, forma
firstLine, firstChar := scanner.GetLineAndCharacterOfPosition(file, pos)
var relativeFileName string
if formatOpts != nil {
relativeFileName = tspath.ConvertToRelativePath(file.Path(), formatOpts.ComparePathsOptions)
relativeFileName = tspath.ConvertToRelativePath(file.FileName(), formatOpts.ComparePathsOptions)
} else {
relativeFileName = file.Path()
relativeFileName = file.FileName()
}

writeWithStyleAndReset(output, relativeFileName, foregroundColorEscapeCyan)
Expand Down Expand Up @@ -333,7 +333,7 @@ func prettyPathForFileError(file *ast.SourceFile, fileErrors []*ast.Diagnostic,
line, _ := scanner.GetLineAndCharacterOfPosition(file, fileErrors[0].Loc().Pos())
fileName := file.FileName()
if tspath.PathIsAbsolute(fileName) && tspath.PathIsAbsolute(formatOpts.CurrentDirectory) {
fileName = tspath.ConvertToRelativePath(file.Path(), formatOpts.ComparePathsOptions)
fileName = tspath.ConvertToRelativePath(file.FileName(), formatOpts.ComparePathsOptions)
}
return fmt.Sprintf("%s%s:%d%s",
fileName,
Expand Down
84 changes: 15 additions & 69 deletions internal/compiler/host.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
package compiler

import (
"bytes"
"encoding/binary"
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"unicode/utf16"

"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/vfs"
)

type CompilerHost interface {
ReadFile(fileName string) (text string, ok bool)
ReadDirectory(rootPath string, extensions []string) []FileInfo
AbsFileName(fileName string) string
FS() vfs.FS
GetCurrentDirectory() string
RunTask(fn func())
WaitForTasks()
}
Expand All @@ -29,73 +20,28 @@ type FileInfo struct {
}

type compilerHost struct {
options *core.CompilerOptions
singleThreaded bool
wg sync.WaitGroup
readSema chan struct{}
options *core.CompilerOptions
singleThreaded bool
wg sync.WaitGroup
currentDirectory string
fs vfs.FS
}

func NewCompilerHost(options *core.CompilerOptions, singleThreaded bool) CompilerHost {
func NewCompilerHost(options *core.CompilerOptions, singleThreaded bool, currentDirectory string, fs vfs.FS) CompilerHost {
h := &compilerHost{}
h.options = options
h.singleThreaded = singleThreaded
h.readSema = make(chan struct{}, 128)
h.currentDirectory = currentDirectory
h.fs = fs
return h
}

func (h *compilerHost) ReadFile(fileName string) (text string, ok bool) {
h.readSema <- struct{}{}
b, err := os.ReadFile(fileName)
<-h.readSema
if err != nil {
return "", false
}
var bom [2]byte
if len(b) >= 2 {
bom = [2]byte{b[0], b[1]}
switch bom {
case [2]byte{0xFF, 0xFE}:
return decodeUtf16(b[2:], binary.LittleEndian), true
case [2]byte{0xFE, 0xFF}:
return decodeUtf16(b[2:], binary.BigEndian), true
}
}
if len(b) >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF {
b = b[3:]
}
return string(b), true
func (h *compilerHost) FS() vfs.FS {
return h.fs
}

func decodeUtf16(b []byte, order binary.ByteOrder) string {
ints := make([]uint16, len(b)/2)
if err := binary.Read(bytes.NewReader(b), order, &ints); err != nil {
return ""
}
return string(utf16.Decode(ints))
}

func (h *compilerHost) ReadDirectory(rootDir string, extensions []string) []FileInfo {
var fileInfos []FileInfo
_ = filepath.Walk(rootDir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if !info.IsDir() && slices.ContainsFunc(extensions, func(ext string) bool { return strings.HasSuffix(path, ext) }) {
fileInfos = append(fileInfos, FileInfo{Name: path, Size: info.Size()})
}
return nil
})
return fileInfos
}

func (h *compilerHost) AbsFileName(fileName string) string {
absFileName, err := filepath.Abs(fileName)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return absFileName
func (h *compilerHost) GetCurrentDirectory() string {
return h.currentDirectory
}

func (h *compilerHost) RunTask(task func()) {
Expand Down
31 changes: 16 additions & 15 deletions internal/compiler/module/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ func (r *resolutionState) loadModuleFromNearestNodeModulesDirectoryWorker(ext ex

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

if extensions&ExtensionsDeclaration != 0 {
nodeModulesAtTypes := tspath.CombinePaths(nodeModulesFolder, "@types")
nodeModulesAtTypesExists := nodeModulesFolderExists && r.resolver.host.DirectoryExists(nodeModulesAtTypes)
nodeModulesAtTypesExists := nodeModulesFolderExists && r.resolver.host.FS().DirectoryExists(nodeModulesAtTypes)
if !nodeModulesAtTypesExists && r.resolver.traceEnabled() {
r.resolver.host.Trace(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(nodeModulesAtTypes))
}
Expand Down Expand Up @@ -472,7 +472,7 @@ func (r *resolutionState) loadModuleFromSpecificNodeModulesDirectory(ext extensi
if r.resolver.traceEnabled() {
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))
}
packageDirectoryExists := nodeModulesDirectoryExists && r.resolver.host.DirectoryExists(packageDirectory)
packageDirectoryExists := nodeModulesDirectoryExists && r.resolver.host.FS().DirectoryExists(packageDirectory)
pathPatterns := tryParsePatterns(versionPaths.GetPaths())
if fromPaths := r.tryLoadModuleUsingPaths(ext, rest, packageDirectory, versionPaths.GetPaths(), pathPatterns, loader, !packageDirectoryExists); fromPaths.stop {
return fromPaths.value
Expand Down Expand Up @@ -561,7 +561,7 @@ func (r *resolutionState) createResolvedModuleWithFailedLookupLocations(resolved
func (r *resolutionState) getOriginalAndResolvedFileName(fileName string) (string, string) {
resolvedFileName := r.realPath(fileName)
comparePathsOptions := tspath.ComparePathsOptions{
UseCaseSensitiveFileNames: r.resolver.host.UseCaseSensitiveFileNames(),
UseCaseSensitiveFileNames: r.resolver.host.FS().UseCaseSensitiveFileNames(),
CurrentDirectory: r.resolver.host.GetCurrentDirectory(),
}
if tspath.ComparePaths(fileName, resolvedFileName, comparePathsOptions) == 0 {
Expand Down Expand Up @@ -618,7 +618,7 @@ func (r *resolutionState) tryLoadModuleUsingPaths(extensions extensions, moduleN
})
}
}
if resolved := loader(extensions, candidate, onlyRecordFailures || !r.resolver.host.DirectoryExists(tspath.GetDirectoryPath(candidate))); resolved != nil {
if resolved := loader(extensions, candidate, onlyRecordFailures || !r.resolver.host.FS().DirectoryExists(tspath.GetDirectoryPath(candidate))); resolved != nil {
return newSearchResult(resolved)
}
}
Expand All @@ -633,7 +633,7 @@ func (r *resolutionState) nodeLoadModuleByRelativeName(extensions extensions, ca
if !tspath.HasTrailingDirectorySeparator(candidate) {
if !onlyRecordFailures {
parentOfCandidate := tspath.GetDirectoryPath(candidate)
if !r.resolver.host.DirectoryExists(parentOfCandidate) {
if !r.resolver.host.FS().DirectoryExists(parentOfCandidate) {
if r.resolver.traceEnabled() {
r.resolver.host.Trace(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(parentOfCandidate))
}
Expand All @@ -651,7 +651,7 @@ func (r *resolutionState) nodeLoadModuleByRelativeName(extensions extensions, ca
}
}
if !onlyRecordFailures {
candidateExists := r.resolver.host.DirectoryExists(candidate)
candidateExists := r.resolver.host.FS().DirectoryExists(candidate)
if !candidateExists {
if r.resolver.traceEnabled() {
r.resolver.host.Trace(diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it.Format(candidate))
Expand Down Expand Up @@ -704,7 +704,7 @@ func (r *resolutionState) loadModuleFromFileNoImplicitExtensions(extensions exte
func (r *resolutionState) tryAddingExtensions(extensionless string, extensions extensions, originalExtension string, onlyRecordFailures bool) *resolved {
if !onlyRecordFailures {
directory := tspath.GetDirectoryPath(extensionless)
onlyRecordFailures = directory != "" && !r.resolver.host.DirectoryExists(directory)
onlyRecordFailures = directory != "" && !r.resolver.host.FS().DirectoryExists(directory)
}

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

func (r *resolutionState) tryFileLookup(fileName string, onlyRecordFailures bool) bool {
if !onlyRecordFailures {
if r.resolver.host.FileExists(fileName) {
if r.resolver.host.FS().FileExists(fileName) {
if r.resolver.traceEnabled() {
r.resolver.host.Trace(diagnostics.File_0_exists_use_it_as_a_name_resolution_result.Format(fileName))
}
Expand Down Expand Up @@ -889,7 +889,7 @@ func (r *resolutionState) loadNodeModuleFromDirectoryWorker(ext extensions, cand
if packageInfo != nil {
if file, ok := r.getPackageFile(ext, packageInfo); ok {
packageFile = file
onlyRecordFailuresForPackageFile = !r.resolver.host.DirectoryExists(tspath.GetDirectoryPath(file))
onlyRecordFailuresForPackageFile = !r.resolver.host.FS().DirectoryExists(tspath.GetDirectoryPath(file))
}
}

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

// ESM mode resolutions don't do package 'index' lookups
if !r.esmMode {
return r.loadModuleFromFile(ext, indexPath, onlyRecordFailures || !r.resolver.host.DirectoryExists(candidate))
return r.loadModuleFromFile(ext, indexPath, onlyRecordFailures || !r.resolver.host.FS().DirectoryExists(candidate))
}
return nil
}
Expand Down Expand Up @@ -1046,10 +1046,11 @@ func (r *resolutionState) getPackageJsonInfo(packageDirectory string, onlyRecord
}
}

directoryExists := r.resolver.host.DirectoryExists(packageDirectory)
if directoryExists && r.resolver.host.FileExists(packageJsonPath) {
directoryExists := r.resolver.host.FS().DirectoryExists(packageDirectory)
if directoryExists && r.resolver.host.FS().FileExists(packageJsonPath) {
// Ignore error
packageJsonContent, _ := packagejson.Parse([]byte(r.resolver.host.ReadFile(packageJsonPath)))
contents, _ := r.resolver.host.FS().ReadFile(packageJsonPath)
packageJsonContent, _ := packagejson.Parse([]byte(contents))
if r.resolver.traceEnabled() {
r.resolver.host.Trace(diagnostics.Found_package_json_at_0.Format(packageJsonPath))
}
Expand Down Expand Up @@ -1132,7 +1133,7 @@ func (r *resolutionState) readPackageJsonPeerDependencies(packageJsonInfo *packa
}

func (r *resolutionState) realPath(path string) string {
rp := tspath.NormalizePath(r.resolver.host.Realpath(path))
rp := tspath.NormalizePath(r.resolver.host.FS().Realpath(path))
if r.resolver.traceEnabled() {
r.resolver.host.Trace(diagnostics.Resolving_real_path_for_0_result_1.Format(path, rp))
}
Expand Down
Loading