vendor tsgo
This commit is contained in:
167
tools/tsgo/internal/compiler/checkerpool.go
Normal file
167
tools/tsgo/internal/compiler/checkerpool.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/checker"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tracing"
|
||||
)
|
||||
|
||||
// CheckerPool is implemented by the project system to provide checkers with
|
||||
// request-scoped lifetime and reclamation. It returns a checker and a release
|
||||
// function that must be called when the caller is done with the checker.
|
||||
// The returned checker must not be accessed concurrently; each acquisition is exclusive.
|
||||
// If file is non-nil, the pool may use it as an affinity hint to return the same
|
||||
// checker for the same file across calls.
|
||||
type CheckerPool interface {
|
||||
GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func())
|
||||
}
|
||||
|
||||
type checkerPool struct {
|
||||
program *Program
|
||||
tracing *tracing.Tracing
|
||||
|
||||
createCheckersOnce sync.Once
|
||||
checkers []*checker.Checker
|
||||
locks []*sync.Mutex
|
||||
fileAssociations map[*ast.SourceFile]*checker.Checker
|
||||
}
|
||||
|
||||
var _ CheckerPool = (*checkerPool)(nil)
|
||||
|
||||
func newCheckerPool(program *Program) *checkerPool {
|
||||
return newCheckerPoolWithTracing(program, nil)
|
||||
}
|
||||
|
||||
func newCheckerPoolWithTracing(program *Program, tr *tracing.Tracing) *checkerPool {
|
||||
checkerCount := 4
|
||||
if program.SingleThreaded() {
|
||||
checkerCount = 1
|
||||
} else if c := program.Options().Checkers; c != nil {
|
||||
checkerCount = *c
|
||||
}
|
||||
|
||||
checkerCount = max(min(checkerCount, len(program.files), 256), 1)
|
||||
|
||||
pool := &checkerPool{
|
||||
program: program,
|
||||
checkers: make([]*checker.Checker, checkerCount),
|
||||
locks: make([]*sync.Mutex, checkerCount),
|
||||
tracing: tr,
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
// GetChecker implements CheckerPool. When file is non-nil, returns the checker
|
||||
// associated with that file; otherwise returns the first checker.
|
||||
func (p *checkerPool) GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
|
||||
if file != nil {
|
||||
return p.getCheckerForFileExclusive(ctx, file)
|
||||
}
|
||||
p.createCheckers()
|
||||
c := p.checkers[0]
|
||||
p.locks[0].Lock()
|
||||
return c, sync.OnceFunc(func() {
|
||||
p.locks[0].Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// getCheckerForFileNonExclusive returns the checker for the given file without locking.
|
||||
// This is only safe when the caller guarantees no concurrent access to the same checker,
|
||||
// e.g. for read-only operations like obtaining an emit resolver.
|
||||
func (p *checkerPool) getCheckerForFileNonExclusive(file *ast.SourceFile) (*checker.Checker, func()) {
|
||||
p.createCheckers()
|
||||
return p.fileAssociations[file], noop
|
||||
}
|
||||
|
||||
func (p *checkerPool) getCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
|
||||
p.createCheckers()
|
||||
c := p.fileAssociations[file]
|
||||
idx := slices.Index(p.checkers, c)
|
||||
p.locks[idx].Lock()
|
||||
return c, sync.OnceFunc(func() {
|
||||
p.locks[idx].Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// getCheckerNonExclusive returns the first checker without locking.
|
||||
func (p *checkerPool) getCheckerNonExclusive() (*checker.Checker, func()) {
|
||||
p.createCheckers()
|
||||
return p.checkers[0], noop
|
||||
}
|
||||
|
||||
func (p *checkerPool) createCheckers() {
|
||||
p.createCheckersOnce.Do(func() {
|
||||
checkerCount := len(p.checkers)
|
||||
wg := core.NewWorkGroup(p.program.SingleThreaded())
|
||||
for i := range checkerCount {
|
||||
wg.Queue(func() {
|
||||
var tracer *checker.Tracer
|
||||
if p.tracing != nil {
|
||||
tracer = checker.NewTracer(p.tracing, i)
|
||||
}
|
||||
p.checkers[i], p.locks[i] = checker.NewChecker(p.program, tracer)
|
||||
})
|
||||
}
|
||||
|
||||
wg.RunAndWait()
|
||||
|
||||
p.fileAssociations = make(map[*ast.SourceFile]*checker.Checker, len(p.program.files))
|
||||
for i, file := range p.program.files {
|
||||
p.fileAssociations[file] = p.checkers[i%checkerCount]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Runs `cb` for each checker in the pool concurrently, locking and unlocking checker mutexes as it goes,
|
||||
// making it safe to call `forEachCheckerParallel` from many threads simultaneously.
|
||||
func (p *checkerPool) forEachCheckerParallel(cb func(idx int, c *checker.Checker)) {
|
||||
p.createCheckers()
|
||||
wg := core.NewWorkGroup(p.program.SingleThreaded())
|
||||
for idx, checker := range p.checkers {
|
||||
wg.Queue(func() {
|
||||
p.locks[idx].Lock()
|
||||
defer p.locks[idx].Unlock()
|
||||
cb(idx, checker)
|
||||
})
|
||||
}
|
||||
wg.RunAndWait()
|
||||
}
|
||||
|
||||
func (p *checkerPool) GetGlobalDiagnostics() []*ast.Diagnostic {
|
||||
p.createCheckers()
|
||||
globalDiagnostics := make([][]*ast.Diagnostic, len(p.checkers))
|
||||
p.forEachCheckerParallel(func(idx int, checker *checker.Checker) {
|
||||
globalDiagnostics[idx] = checker.GetGlobalDiagnostics()
|
||||
})
|
||||
return SortAndDeduplicateDiagnostics(slices.Concat(globalDiagnostics...))
|
||||
}
|
||||
|
||||
// forEachCheckerGroupDo runs one task per checker in parallel. Each task iterates
|
||||
// the provided files, processing only those assigned to its checker. Within each
|
||||
// checker's set, files are visited in their original order.
|
||||
func (p *checkerPool) forEachCheckerGroupDo(ctx context.Context, files []*ast.SourceFile, singleThreaded bool, cb func(c *checker.Checker, fileIndex int, file *ast.SourceFile)) {
|
||||
p.createCheckers()
|
||||
|
||||
checkerCount := len(p.checkers)
|
||||
wg := core.NewWorkGroup(singleThreaded)
|
||||
for checkerIdx := range checkerCount {
|
||||
wg.Queue(func() {
|
||||
p.locks[checkerIdx].Lock()
|
||||
defer p.locks[checkerIdx].Unlock()
|
||||
for i, file := range files {
|
||||
if checker := p.checkers[checkerIdx]; checker == p.fileAssociations[file] {
|
||||
cb(checker, i, file)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
wg.RunAndWait()
|
||||
}
|
||||
|
||||
func noop() {}
|
||||
139
tools/tsgo/internal/compiler/emitHost.go
Normal file
139
tools/tsgo/internal/compiler/emitHost.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/outputpaths"
|
||||
"github.com/microsoft/typescript-go/internal/packagejson"
|
||||
"github.com/microsoft/typescript-go/internal/printer"
|
||||
"github.com/microsoft/typescript-go/internal/symlinks"
|
||||
"github.com/microsoft/typescript-go/internal/transformers/declarations"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
// NOTE: EmitHost operations must be thread-safe
|
||||
type EmitHost interface {
|
||||
printer.EmitHost
|
||||
declarations.DeclarationEmitHost
|
||||
Options() *core.CompilerOptions
|
||||
SourceFiles() []*ast.SourceFile
|
||||
UseCaseSensitiveFileNames() bool
|
||||
GetCurrentDirectory() string
|
||||
CommonSourceDirectory() string
|
||||
IsEmitBlocked(file string) bool
|
||||
}
|
||||
|
||||
var _ EmitHost = (*emitHost)(nil)
|
||||
|
||||
// NOTE: emitHost operations must be thread-safe
|
||||
type emitHost struct {
|
||||
program *Program
|
||||
emitResolver printer.EmitResolver
|
||||
}
|
||||
|
||||
func newEmitHost(ctx context.Context, program *Program, file *ast.SourceFile) (*emitHost, func()) {
|
||||
checker, done := program.GetTypeCheckerForFile(ctx, file)
|
||||
return &emitHost{
|
||||
program: program,
|
||||
emitResolver: checker.GetEmitResolver(),
|
||||
}, done
|
||||
}
|
||||
|
||||
func (host *emitHost) GetModeForUsageLocation(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) core.ResolutionMode {
|
||||
return host.program.GetModeForUsageLocation(file, moduleSpecifier)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetResolvedModuleFromModuleSpecifier(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) *module.ResolvedModule {
|
||||
return host.program.GetResolvedModuleFromModuleSpecifier(file, moduleSpecifier)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetDefaultResolutionModeForFile(file ast.HasFileName) core.ResolutionMode {
|
||||
return host.program.GetDefaultResolutionModeForFile(file)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetEmitModuleFormatOfFile(file ast.HasFileName) core.ModuleKind {
|
||||
return host.program.GetEmitModuleFormatOfFile(file)
|
||||
}
|
||||
|
||||
func (host *emitHost) FileExists(path string) bool {
|
||||
return host.program.FileExists(path)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetGlobalTypingsCacheLocation() string {
|
||||
return host.program.GetGlobalTypingsCacheLocation()
|
||||
}
|
||||
|
||||
func (host *emitHost) GetNearestAncestorDirectoryWithPackageJson(dirname string) string {
|
||||
return host.program.GetNearestAncestorDirectoryWithPackageJson(dirname)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry {
|
||||
return host.program.GetPackageJsonInfo(pkgJsonPath)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetSourceOfProjectReferenceIfOutputIncluded(file ast.HasFileName) string {
|
||||
return host.program.GetSourceOfProjectReferenceIfOutputIncluded(file)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
|
||||
return host.program.GetProjectReferenceFromSource(path)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetRedirectTargets(path tspath.Path) []string {
|
||||
return host.program.GetRedirectTargets(path)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags {
|
||||
return host.GetEmitResolver().GetEffectiveDeclarationFlags(node, flags)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetOutputPathsFor(file *ast.SourceFile, forceDtsPaths bool) declarations.OutputPaths {
|
||||
// TODO: cache
|
||||
return outputpaths.GetOutputPathsFor(file, host.Options(), host, forceDtsPaths)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetResolutionModeOverride(node *ast.Node) core.ResolutionMode {
|
||||
return host.GetEmitResolver().GetResolutionModeOverride(node)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetSourceFileFromReference(origin *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile {
|
||||
return host.program.GetSourceFileFromReference(origin, ref)
|
||||
}
|
||||
|
||||
func (host *emitHost) Options() *core.CompilerOptions { return host.program.Options() }
|
||||
func (host *emitHost) SourceFiles() []*ast.SourceFile { return host.program.SourceFiles() }
|
||||
func (host *emitHost) GetCurrentDirectory() string { return host.program.GetCurrentDirectory() }
|
||||
func (host *emitHost) CommonSourceDirectory() string { return host.program.CommonSourceDirectory() }
|
||||
|
||||
func (host *emitHost) UseCaseSensitiveFileNames() bool {
|
||||
return host.program.UseCaseSensitiveFileNames()
|
||||
}
|
||||
|
||||
func (host *emitHost) IsEmitBlocked(file string) bool {
|
||||
return host.program.IsEmitBlocked(file)
|
||||
}
|
||||
|
||||
func (host *emitHost) WriteFile(fileName string, text string) error {
|
||||
return host.program.Host().FS().WriteFile(fileName, text)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetEmitResolver() printer.EmitResolver {
|
||||
return host.emitResolver
|
||||
}
|
||||
|
||||
func (host *emitHost) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool {
|
||||
return host.program.IsSourceFileFromExternalLibrary(file)
|
||||
}
|
||||
|
||||
func (host *emitHost) GetSymlinkCache() *symlinks.KnownSymlinks {
|
||||
return host.program.GetSymlinkCache()
|
||||
}
|
||||
|
||||
func (host *emitHost) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
|
||||
resolved, _ := host.program.resolver.ResolveModuleName(moduleName, containingFile, resolutionMode, nil)
|
||||
return resolved
|
||||
}
|
||||
191
tools/tsgo/internal/compiler/emit_test.go
Normal file
191
tools/tsgo/internal/compiler/emit_test.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package compiler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/bundled"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
|
||||
)
|
||||
|
||||
// generateLongLineTS generates TypeScript source code that produces a single very long line.
|
||||
// This simulates generated code (e.g., from code generators) that has no line breaks,
|
||||
// which triggers O(n²) behavior in source map generation due to
|
||||
// GetECMALineAndUTF16CharacterOfPosition scanning from line start for each position.
|
||||
func generateLongLineTS(numProperties int) string {
|
||||
// Build a large object literal all on one line, with no line breaks.
|
||||
var b strings.Builder
|
||||
b.WriteString("export const data: Record<string, number> = {")
|
||||
for i := range numProperties {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&b, "prop_%d: %d", i, i)
|
||||
}
|
||||
b.WriteString("};")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func BenchmarkEmitLongLines(b *testing.B) {
|
||||
if !bundled.Embedded {
|
||||
b.Skip("bundled files are not embedded")
|
||||
}
|
||||
|
||||
for _, numProps := range []int{1000, 5000, 10000} {
|
||||
b.Run(fmt.Sprintf("props_%d", numProps), func(b *testing.B) {
|
||||
source := generateLongLineTS(numProps)
|
||||
|
||||
fs := vfstest.FromMap(map[string]string{
|
||||
"/dev/src/index.ts": source,
|
||||
}, true /*useCaseSensitiveFileNames*/)
|
||||
fs = bundled.WrapFS(fs)
|
||||
|
||||
opts := core.CompilerOptions{
|
||||
Target: core.ScriptTargetES2015,
|
||||
SourceMap: core.TSTrue,
|
||||
OutDir: "/dev/out",
|
||||
}
|
||||
|
||||
host := compiler.NewCompilerHost("/dev/src", fs, bundled.LibPath(), nil, nil)
|
||||
|
||||
p := compiler.NewProgram(compiler.ProgramOptions{
|
||||
Config: &tsoptions.ParsedCommandLine{
|
||||
ParsedConfig: &core.ParsedOptions{
|
||||
FileNames: []string{"/dev/src/index.ts"},
|
||||
CompilerOptions: &opts,
|
||||
},
|
||||
},
|
||||
Host: host,
|
||||
})
|
||||
|
||||
// Discard written files — we only care about emit performance.
|
||||
nopWriteFile := func(fileName string, text string, data *compiler.WriteFileData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for b.Loop() {
|
||||
p.Emit(context.Background(), compiler.EmitOptions{
|
||||
WriteFile: nopWriteFile,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEmitManyFiles(b *testing.B) {
|
||||
if !bundled.Embedded {
|
||||
b.Skip("bundled files are not embedded")
|
||||
}
|
||||
|
||||
// Simulate many files with moderately long single-line content.
|
||||
numFiles := 200
|
||||
numPropsPerFile := 500
|
||||
|
||||
files := make(map[string]string, numFiles)
|
||||
fileNames := make([]string, 0, numFiles)
|
||||
for i := range numFiles {
|
||||
name := fmt.Sprintf("/dev/src/file_%d.ts", i)
|
||||
files[name] = generateLongLineTS(numPropsPerFile)
|
||||
fileNames = append(fileNames, name)
|
||||
}
|
||||
|
||||
fs := vfstest.FromMap(files, true)
|
||||
fs = bundled.WrapFS(fs)
|
||||
|
||||
opts := core.CompilerOptions{
|
||||
Target: core.ScriptTargetES2015,
|
||||
SourceMap: core.TSTrue,
|
||||
OutDir: "/dev/out",
|
||||
}
|
||||
|
||||
host := compiler.NewCompilerHost("/dev/src", fs, bundled.LibPath(), nil, nil)
|
||||
|
||||
p := compiler.NewProgram(compiler.ProgramOptions{
|
||||
Config: &tsoptions.ParsedCommandLine{
|
||||
ParsedConfig: &core.ParsedOptions{
|
||||
FileNames: fileNames,
|
||||
CompilerOptions: &opts,
|
||||
},
|
||||
},
|
||||
Host: host,
|
||||
})
|
||||
|
||||
nopWriteFile := func(fileName string, text string, data *compiler.WriteFileData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for b.Loop() {
|
||||
p.Emit(context.Background(), compiler.EmitOptions{
|
||||
WriteFile: nopWriteFile,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEmitLongLinesWithLineBreaks is a control benchmark that emits the same amount
|
||||
// of code but WITH line breaks, showing that the issue is specific to long lines.
|
||||
func BenchmarkEmitLongLinesWithLineBreaks(b *testing.B) {
|
||||
if !bundled.Embedded {
|
||||
b.Skip("bundled files are not embedded")
|
||||
}
|
||||
|
||||
numProperties := 10000
|
||||
|
||||
// Same content but with newlines between each property.
|
||||
var sb strings.Builder
|
||||
sb.WriteString("export const data: Record<string, number> = {\n")
|
||||
for i := range numProperties {
|
||||
if i > 0 {
|
||||
sb.WriteString(",\n")
|
||||
}
|
||||
fmt.Fprintf(&sb, " prop_%d: %d", i, i)
|
||||
}
|
||||
sb.WriteString("\n};\n")
|
||||
source := sb.String()
|
||||
|
||||
fs := vfstest.FromMap(map[string]string{
|
||||
"/dev/src/index.ts": source,
|
||||
}, true)
|
||||
fs = bundled.WrapFS(fs)
|
||||
|
||||
opts := core.CompilerOptions{
|
||||
Target: core.ScriptTargetES2015,
|
||||
SourceMap: core.TSTrue,
|
||||
OutDir: "/dev/out",
|
||||
}
|
||||
|
||||
host := compiler.NewCompilerHost("/dev/src", fs, bundled.LibPath(), nil, nil)
|
||||
|
||||
p := compiler.NewProgram(compiler.ProgramOptions{
|
||||
Config: &tsoptions.ParsedCommandLine{
|
||||
ParsedConfig: &core.ParsedOptions{
|
||||
FileNames: []string{"/dev/src/index.ts"},
|
||||
CompilerOptions: &opts,
|
||||
},
|
||||
},
|
||||
Host: host,
|
||||
})
|
||||
|
||||
nopWriteFile := func(fileName string, text string, data *compiler.WriteFileData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for b.Loop() {
|
||||
p.Emit(context.Background(), compiler.EmitOptions{
|
||||
WriteFile: nopWriteFile,
|
||||
})
|
||||
}
|
||||
}
|
||||
531
tools/tsgo/internal/compiler/emitter.go
Normal file
531
tools/tsgo/internal/compiler/emitter.go
Normal file
@@ -0,0 +1,531 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/binder"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/outputpaths"
|
||||
"github.com/microsoft/typescript-go/internal/printer"
|
||||
"github.com/microsoft/typescript-go/internal/sourcemap"
|
||||
"github.com/microsoft/typescript-go/internal/stringutil"
|
||||
"github.com/microsoft/typescript-go/internal/tracing"
|
||||
"github.com/microsoft/typescript-go/internal/transformers"
|
||||
"github.com/microsoft/typescript-go/internal/transformers/declarations"
|
||||
"github.com/microsoft/typescript-go/internal/transformers/estransforms"
|
||||
"github.com/microsoft/typescript-go/internal/transformers/inliners"
|
||||
"github.com/microsoft/typescript-go/internal/transformers/jsxtransforms"
|
||||
"github.com/microsoft/typescript-go/internal/transformers/moduletransforms"
|
||||
"github.com/microsoft/typescript-go/internal/transformers/tstransforms"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type EmitOnly byte
|
||||
|
||||
const (
|
||||
EmitAll EmitOnly = iota
|
||||
EmitOnlyJs
|
||||
EmitOnlyDts
|
||||
EmitOnlyForcedDts
|
||||
)
|
||||
|
||||
type emitter struct {
|
||||
host EmitHost
|
||||
emitOnly EmitOnly
|
||||
emitterDiagnostics ast.DiagnosticsCollection
|
||||
writer printer.EmitTextWriter
|
||||
paths *outputpaths.OutputPaths
|
||||
sourceFile *ast.SourceFile
|
||||
emitResult EmitResult
|
||||
writeFile func(fileName string, text string, data *WriteFileData) error
|
||||
tr *tracing.Tracing
|
||||
}
|
||||
|
||||
func (e *emitter) emit() {
|
||||
if e.tr != nil {
|
||||
defer e.tr.Push(tracing.PhaseEmit, "emit", map[string]any{"path": string(e.sourceFile.Path())}, true)()
|
||||
}
|
||||
e.emitJSFile(e.sourceFile, e.paths.JsFilePath(), e.paths.SourceMapFilePath())
|
||||
e.emitDeclarationFile(e.sourceFile, e.paths.DeclarationFilePath(), e.paths.DeclarationMapPath())
|
||||
e.emitResult.Diagnostics = e.emitterDiagnostics.GetDiagnostics()
|
||||
}
|
||||
|
||||
func (e *emitter) getDeclarationTransformers(emitContext *printer.EmitContext, declarationFilePath string, declarationMapPath string) []*declarations.DeclarationTransformer {
|
||||
transform := declarations.NewDeclarationTransformer(e.host, emitContext, e.host.Options(), declarationFilePath, declarationMapPath)
|
||||
return []*declarations.DeclarationTransformer{transform}
|
||||
}
|
||||
|
||||
func (e *emitter) runScriptTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile) *ast.SourceFile {
|
||||
if e.tr != nil {
|
||||
defer e.tr.Push(tracing.PhaseEmit, "transformNodes", map[string]any{"path": string(sourceFile.Path())}, false)()
|
||||
}
|
||||
for _, transformer := range getScriptTransformers(emitContext, e.host, sourceFile) {
|
||||
sourceFile = transformer.TransformSourceFile(sourceFile)
|
||||
}
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
func (e *emitter) runDeclarationTransformers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, declarationFilePath, declarationMapPath string) (*ast.SourceFile, []*ast.Diagnostic) {
|
||||
if e.tr != nil {
|
||||
defer e.tr.Push(tracing.PhaseEmit, "transformNodes", map[string]any{"path": string(sourceFile.Path())}, false)()
|
||||
}
|
||||
var diags []*ast.Diagnostic
|
||||
for _, transformer := range e.getDeclarationTransformers(emitContext, declarationFilePath, declarationMapPath) {
|
||||
sourceFile = transformer.TransformSourceFile(sourceFile)
|
||||
diags = append(diags, transformer.GetDiagnostics()...)
|
||||
}
|
||||
return sourceFile, diags
|
||||
}
|
||||
|
||||
func getModuleTransformer(opts *transformers.TransformOptions) *transformers.Transformer {
|
||||
switch opts.CompilerOptions.GetEmitModuleKind() {
|
||||
case core.ModuleKindPreserve:
|
||||
// `ESModuleTransformer` contains logic for preserving CJS input syntax in `--module preserve`
|
||||
return moduletransforms.NewESModuleTransformer(opts)
|
||||
|
||||
case core.ModuleKindESNext,
|
||||
core.ModuleKindES2022,
|
||||
core.ModuleKindES2020,
|
||||
core.ModuleKindES2015,
|
||||
core.ModuleKindNode20,
|
||||
core.ModuleKindNode18,
|
||||
core.ModuleKindNode16,
|
||||
core.ModuleKindNodeNext,
|
||||
core.ModuleKindCommonJS:
|
||||
return moduletransforms.NewImpliedModuleTransformer(opts)
|
||||
|
||||
default:
|
||||
return moduletransforms.NewCommonJSModuleTransformer(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHost, sourceFile *ast.SourceFile) []*transformers.Transformer {
|
||||
var tx []*transformers.Transformer
|
||||
options := host.Options()
|
||||
|
||||
// JS files don't use reference calculations as they don't do import elision, no need to calculate it
|
||||
importElisionEnabled := !options.VerbatimModuleSyntax.IsTrue() && !ast.IsInJSFile(sourceFile.AsNode())
|
||||
jsxTransformEnabled := options.GetJSXTransformEnabled() && sourceFile.LanguageVariant == core.LanguageVariantJSX
|
||||
|
||||
emitResolver := host.GetEmitResolver()
|
||||
|
||||
var referenceResolver binder.ReferenceResolver
|
||||
if importElisionEnabled || jsxTransformEnabled || !options.GetIsolatedModules() || options.EmitDecoratorMetadata.IsTrue() {
|
||||
referenceResolver = emitResolver
|
||||
} else {
|
||||
referenceResolver = binder.NewReferenceResolver(options, binder.ReferenceResolverHooks{})
|
||||
}
|
||||
|
||||
opts := transformers.TransformOptions{
|
||||
Context: emitContext,
|
||||
CompilerOptions: options,
|
||||
Resolver: referenceResolver,
|
||||
EmitResolver: emitResolver,
|
||||
GetEmitModuleFormatOfFile: host.GetEmitModuleFormatOfFile,
|
||||
}
|
||||
|
||||
// transform TypeScript syntax
|
||||
{
|
||||
// use type nodes to add metadata decorators
|
||||
if options.EmitDecoratorMetadata.IsTrue() {
|
||||
tx = append(tx, tstransforms.NewMetadataTransformer(&opts))
|
||||
}
|
||||
|
||||
// erase types
|
||||
tx = append(tx, tstransforms.NewTypeEraserTransformer(&opts))
|
||||
|
||||
// elide imports
|
||||
if importElisionEnabled {
|
||||
tx = append(tx, tstransforms.NewImportElisionTransformer(&opts))
|
||||
}
|
||||
|
||||
// transform `enum`, `namespace`, and parameter properties
|
||||
tx = append(tx, tstransforms.NewRuntimeSyntaxTransformer(&opts))
|
||||
|
||||
if options.ExperimentalDecorators.IsTrue() {
|
||||
tx = append(tx, tstransforms.NewLegacyDecoratorsTransformer(&opts))
|
||||
}
|
||||
}
|
||||
|
||||
if jsxTransformEnabled {
|
||||
tx = append(tx, jsxtransforms.NewJSXTransformer(&opts))
|
||||
}
|
||||
|
||||
downleveler := estransforms.GetESTransformer(&opts)
|
||||
if downleveler != nil {
|
||||
tx = append(tx, downleveler)
|
||||
}
|
||||
|
||||
tx = append(tx, estransforms.NewUseStrictTransformer(&opts))
|
||||
|
||||
// transform module syntax
|
||||
tx = append(tx, getModuleTransformer(&opts))
|
||||
|
||||
// inlining (formerly done via substitutions)
|
||||
if !options.GetIsolatedModules() {
|
||||
tx = append(tx, inliners.NewConstEnumInliningTransformer(&opts))
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
func (e *emitter) emitJSFile(sourceFile *ast.SourceFile, jsFilePath string, sourceMapFilePath string) {
|
||||
options := e.host.Options()
|
||||
|
||||
if sourceFile == nil || e.emitOnly != EmitAll && e.emitOnly != EmitOnlyJs || len(jsFilePath) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if options.NoEmit == core.TSTrue || e.host.IsEmitBlocked(jsFilePath) {
|
||||
e.emitResult.EmitSkipped = true
|
||||
return
|
||||
}
|
||||
|
||||
if e.tr != nil {
|
||||
defer e.tr.Push(tracing.PhaseEmit, "emitJsFileOrBundle", map[string]any{"jsFilePath": jsFilePath}, true)()
|
||||
}
|
||||
|
||||
emitContext, putEmitContext := printer.GetEmitContext()
|
||||
defer putEmitContext()
|
||||
|
||||
sourceFile = e.runScriptTransformers(emitContext, sourceFile)
|
||||
|
||||
printerOptions := printer.PrinterOptions{
|
||||
RemoveComments: options.RemoveComments.IsTrue(),
|
||||
NewLine: options.NewLine,
|
||||
NoEmitHelpers: options.NoEmitHelpers.IsTrue(),
|
||||
SourceMap: options.SourceMap.IsTrue(),
|
||||
InlineSourceMap: options.InlineSourceMap.IsTrue(),
|
||||
InlineSources: options.InlineSources.IsTrue(),
|
||||
Target: options.Target,
|
||||
// !!!
|
||||
}
|
||||
|
||||
// create a printer to print the nodes
|
||||
printer := printer.NewPrinter(printerOptions, printer.PrintHandlers{
|
||||
// !!!
|
||||
}, emitContext)
|
||||
|
||||
e.printSourceFile(jsFilePath, sourceMapFilePath, sourceFile, printer, options, shouldEmitSourceMaps(options, sourceFile))
|
||||
}
|
||||
|
||||
func (e *emitter) emitDeclarationFile(sourceFile *ast.SourceFile, declarationFilePath string, declarationMapPath string) {
|
||||
options := e.host.Options()
|
||||
|
||||
if sourceFile == nil || e.emitOnly == EmitOnlyJs || len(declarationFilePath) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if e.tr != nil {
|
||||
defer e.tr.Push(tracing.PhaseEmit, "emitDeclarationFileOrBundle", map[string]any{"declarationFilePath": declarationFilePath}, true)()
|
||||
}
|
||||
|
||||
emitContext, putEmitContext := printer.GetEmitContext()
|
||||
defer putEmitContext()
|
||||
sourceFile, diags := e.runDeclarationTransformers(emitContext, sourceFile, declarationFilePath, declarationMapPath)
|
||||
|
||||
for _, elem := range diags {
|
||||
// Add declaration transform diagnostics to emit diagnostics
|
||||
e.emitterDiagnostics.Add(elem)
|
||||
}
|
||||
|
||||
if e.emitOnly != EmitOnlyForcedDts && (options.NoEmit == core.TSTrue || e.host.IsEmitBlocked(declarationFilePath)) {
|
||||
e.emitResult.EmitSkipped = true
|
||||
return
|
||||
}
|
||||
|
||||
declBlocked := len(diags) > 0 && e.emitOnly != EmitOnlyForcedDts
|
||||
if declBlocked {
|
||||
e.emitResult.EmitSkipped = true
|
||||
return
|
||||
}
|
||||
|
||||
printerOptions := printer.PrinterOptions{
|
||||
RemoveComments: options.RemoveComments.IsTrue(),
|
||||
NewLine: options.NewLine,
|
||||
NoEmitHelpers: true,
|
||||
// Module: options.Module, // NYI
|
||||
// ModuleResolution: options.ModuleResolution, // NYI
|
||||
Target: options.GetEmitScriptTarget(),
|
||||
SourceMap: e.emitOnly != EmitOnlyForcedDts && options.DeclarationMap.IsTrue(),
|
||||
InlineSourceMap: options.InlineSourceMap.IsTrue(),
|
||||
// InlineSources: options.InlineSources.IsTrue(), // ignored, per strada
|
||||
// ExtendedDiagnostics: options.ExtendedDiagnostics.IsTrue(), // NYI
|
||||
OnlyPrintJSDocStyle: true,
|
||||
OmitBraceSourceMapPositions: true,
|
||||
}
|
||||
|
||||
// create a printer to print the nodes
|
||||
printer := printer.NewPrinter(printerOptions, printer.PrintHandlers{
|
||||
// !!!
|
||||
}, emitContext)
|
||||
|
||||
declarationMapOptions := &core.CompilerOptions{
|
||||
SourceMap: core.IfElse(e.emitOnly != EmitOnlyForcedDts && options.DeclarationMap.IsTrue(), core.TSTrue, core.TSFalse),
|
||||
SourceRoot: options.SourceRoot,
|
||||
MapRoot: options.MapRoot,
|
||||
// Explicitly do not pass through either inline option.
|
||||
}
|
||||
e.printSourceFile(declarationFilePath, declarationMapPath, sourceFile, printer, declarationMapOptions, shouldEmitSourceMaps(declarationMapOptions, sourceFile))
|
||||
}
|
||||
|
||||
func (e *emitter) printSourceFile(jsFilePath string, sourceMapFilePath string, sourceFile *ast.SourceFile, printer_ *printer.Printer, mapOptions *core.CompilerOptions, shouldEmitSourceMaps bool) {
|
||||
// !!! sourceMapGenerator
|
||||
options := e.host.Options()
|
||||
var sourceMapGenerator *sourcemap.Generator
|
||||
if shouldEmitSourceMaps {
|
||||
sourceMapGenerator = sourcemap.NewGenerator(
|
||||
tspath.GetBaseFileName(tspath.NormalizeSlashes(jsFilePath)),
|
||||
getSourceRoot(mapOptions),
|
||||
e.getSourceMapDirectory(mapOptions, jsFilePath, sourceFile),
|
||||
tspath.ComparePathsOptions{
|
||||
UseCaseSensitiveFileNames: e.host.UseCaseSensitiveFileNames(),
|
||||
CurrentDirectory: e.host.GetCurrentDirectory(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
printer_.Write(sourceFile.AsNode(), sourceFile, e.writer, sourceMapGenerator)
|
||||
|
||||
sourceMapUrlPos := -1
|
||||
if sourceMapGenerator != nil {
|
||||
if mapOptions.SourceMap.IsTrue() || mapOptions.InlineSourceMap.IsTrue() {
|
||||
e.emitResult.SourceMaps = append(e.emitResult.SourceMaps, &SourceMapEmitResult{
|
||||
InputSourceFileNames: sourceMapGenerator.Sources(),
|
||||
SourceMap: sourceMapGenerator.RawSourceMap(),
|
||||
GeneratedFile: jsFilePath,
|
||||
})
|
||||
}
|
||||
|
||||
sourceMappingURL := e.getSourceMappingURL(
|
||||
mapOptions,
|
||||
sourceMapGenerator,
|
||||
jsFilePath,
|
||||
sourceMapFilePath,
|
||||
sourceFile,
|
||||
)
|
||||
|
||||
if len(sourceMappingURL) > 0 {
|
||||
if !e.writer.IsAtStartOfLine() {
|
||||
e.writer.RawWrite(core.IfElse(options.NewLine == core.NewLineKindCRLF, "\r\n", "\n"))
|
||||
}
|
||||
sourceMapUrlPos = e.writer.GetTextPos()
|
||||
e.writer.WriteComment("//# sourceMappingURL=")
|
||||
e.writer.WriteComment(sourceMappingURL)
|
||||
}
|
||||
|
||||
// Write the source map
|
||||
if len(sourceMapFilePath) > 0 {
|
||||
sourceMap := sourceMapGenerator.String()
|
||||
err := e.writeText(sourceMapFilePath, sourceMap, nil)
|
||||
if err != nil {
|
||||
e.emitterDiagnostics.Add(ast.NewCompilerDiagnostic(diagnostics.Could_not_write_file_0_Colon_1, jsFilePath, err.Error()))
|
||||
} else {
|
||||
e.emitResult.EmittedFiles = append(e.emitResult.EmittedFiles, sourceMapFilePath)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
e.writer.WriteLine()
|
||||
}
|
||||
|
||||
// Write the output file
|
||||
text := e.writer.String()
|
||||
if options.EmitBOM.IsTrue() {
|
||||
text = stringutil.AddUTF8ByteOrderMark(text)
|
||||
}
|
||||
data := &WriteFileData{
|
||||
SourceMapUrlPos: sourceMapUrlPos,
|
||||
Diagnostics: e.emitterDiagnostics.GetDiagnostics(),
|
||||
}
|
||||
err := e.writeText(jsFilePath, text, data)
|
||||
skippedDtsWrite := data.SkippedDtsWrite
|
||||
if err != nil {
|
||||
e.emitterDiagnostics.Add(ast.NewCompilerDiagnostic(diagnostics.Could_not_write_file_0_Colon_1, jsFilePath, err.Error()))
|
||||
} else if !skippedDtsWrite {
|
||||
e.emitResult.EmittedFiles = append(e.emitResult.EmittedFiles, jsFilePath)
|
||||
}
|
||||
|
||||
// Reset state
|
||||
e.writer.Clear()
|
||||
}
|
||||
|
||||
func (e *emitter) writeText(fileName string, text string, data *WriteFileData) error {
|
||||
if e.writeFile != nil {
|
||||
return e.writeFile(fileName, text, data)
|
||||
}
|
||||
return e.host.WriteFile(fileName, text)
|
||||
}
|
||||
|
||||
func shouldEmitSourceMaps(mapOptions *core.CompilerOptions, sourceFile *ast.SourceFile) bool {
|
||||
return (mapOptions.SourceMap.IsTrue() || mapOptions.InlineSourceMap.IsTrue()) &&
|
||||
!tspath.FileExtensionIs(sourceFile.FileName(), tspath.ExtensionJson)
|
||||
}
|
||||
|
||||
func getSourceRoot(mapOptions *core.CompilerOptions) string {
|
||||
// Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the
|
||||
// relative paths of the sources list in the sourcemap
|
||||
sourceRoot := tspath.NormalizeSlashes(mapOptions.SourceRoot)
|
||||
if len(sourceRoot) > 0 {
|
||||
sourceRoot = tspath.EnsureTrailingDirectorySeparator(sourceRoot)
|
||||
}
|
||||
return sourceRoot
|
||||
}
|
||||
|
||||
func (e *emitter) getSourceMapDirectory(mapOptions *core.CompilerOptions, filePath string, sourceFile *ast.SourceFile) string {
|
||||
if len(mapOptions.SourceRoot) > 0 {
|
||||
return e.host.CommonSourceDirectory()
|
||||
}
|
||||
if len(mapOptions.MapRoot) > 0 {
|
||||
sourceMapDir := tspath.NormalizeSlashes(mapOptions.MapRoot)
|
||||
if sourceFile != nil {
|
||||
// For modules or multiple emit files the mapRoot will have directory structure like the sources
|
||||
// So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map
|
||||
sourceMapDir = tspath.GetDirectoryPath(outputpaths.GetSourceFilePathInNewDir(
|
||||
sourceFile.FileName(),
|
||||
sourceMapDir,
|
||||
e.host.GetCurrentDirectory(),
|
||||
e.host.CommonSourceDirectory(),
|
||||
e.host.UseCaseSensitiveFileNames(),
|
||||
))
|
||||
}
|
||||
if tspath.GetRootLength(sourceMapDir) == 0 {
|
||||
// The relative paths are relative to the common directory
|
||||
sourceMapDir = tspath.CombinePaths(e.host.CommonSourceDirectory(), sourceMapDir)
|
||||
}
|
||||
return sourceMapDir
|
||||
}
|
||||
return tspath.GetDirectoryPath(tspath.NormalizePath(filePath))
|
||||
}
|
||||
|
||||
func (e *emitter) getSourceMappingURL(mapOptions *core.CompilerOptions, sourceMapGenerator *sourcemap.Generator, filePath string, sourceMapFilePath string, sourceFile *ast.SourceFile) string {
|
||||
if mapOptions.InlineSourceMap.IsTrue() {
|
||||
// Encode the sourceMap into the sourceMap url
|
||||
return sourceMapGenerator.Base64DataURL()
|
||||
}
|
||||
|
||||
sourceMapFile := tspath.GetBaseFileName(tspath.NormalizeSlashes(sourceMapFilePath))
|
||||
if len(mapOptions.MapRoot) > 0 {
|
||||
sourceMapDir := tspath.NormalizeSlashes(mapOptions.MapRoot)
|
||||
if sourceFile != nil {
|
||||
// For modules or multiple emit files the mapRoot will have directory structure like the sources
|
||||
// So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map
|
||||
sourceMapDir = tspath.GetDirectoryPath(outputpaths.GetSourceFilePathInNewDir(
|
||||
sourceFile.FileName(),
|
||||
sourceMapDir,
|
||||
e.host.GetCurrentDirectory(),
|
||||
e.host.CommonSourceDirectory(),
|
||||
e.host.UseCaseSensitiveFileNames(),
|
||||
))
|
||||
}
|
||||
if tspath.GetRootLength(sourceMapDir) == 0 {
|
||||
// The relative paths are relative to the common directory
|
||||
sourceMapDir = tspath.CombinePaths(e.host.CommonSourceDirectory(), sourceMapDir)
|
||||
return stringutil.EncodeURI(
|
||||
tspath.GetRelativePathToDirectoryOrUrl(
|
||||
tspath.GetDirectoryPath(tspath.NormalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath
|
||||
tspath.CombinePaths(sourceMapDir, sourceMapFile), // this is where user expects to see sourceMap
|
||||
/*isAbsolutePathAnUrl*/ true,
|
||||
tspath.ComparePathsOptions{
|
||||
UseCaseSensitiveFileNames: e.host.UseCaseSensitiveFileNames(),
|
||||
CurrentDirectory: e.host.GetCurrentDirectory(),
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
return stringutil.EncodeURI(tspath.CombinePaths(sourceMapDir, sourceMapFile))
|
||||
}
|
||||
}
|
||||
return stringutil.EncodeURI(sourceMapFile)
|
||||
}
|
||||
|
||||
type SourceFileMayBeEmittedHost interface {
|
||||
Options() *core.CompilerOptions
|
||||
GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference
|
||||
IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool
|
||||
GetCurrentDirectory() string
|
||||
UseCaseSensitiveFileNames() bool
|
||||
SourceFiles() []*ast.SourceFile
|
||||
}
|
||||
|
||||
func sourceFileMayBeEmitted(sourceFile *ast.SourceFile, host SourceFileMayBeEmittedHost, forceDtsEmit bool) bool {
|
||||
// TODO: move this to outputpaths?
|
||||
|
||||
options := host.Options()
|
||||
// Js files are emitted only if option is enabled
|
||||
if options.NoEmitForJsFiles.IsTrue() && ast.IsSourceFileJS(sourceFile) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Declaration files are not emitted
|
||||
if sourceFile.IsDeclarationFile {
|
||||
return false
|
||||
}
|
||||
|
||||
// Source file from node_modules are not emitted
|
||||
if host.IsSourceFileFromExternalLibrary(sourceFile) {
|
||||
return false
|
||||
}
|
||||
|
||||
// forcing dts emit => file needs to be emitted
|
||||
if forceDtsEmit {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check other conditions for file emit
|
||||
// Source files from referenced projects are not emitted
|
||||
if host.GetProjectReferenceFromSource(sourceFile.Path()) != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Any non json file should be emitted
|
||||
if !ast.IsJsonSourceFile(sourceFile) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Json file is not emitted if outDir is not specified
|
||||
if options.OutDir == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Otherwise, if rootDir is specified or a config file exists, we know the common source directory and can check if the file would be emitted in the same location
|
||||
if options.RootDir != "" || options.ConfigFilePath != "" {
|
||||
commonDir := tspath.GetNormalizedAbsolutePath(outputpaths.GetCommonSourceDirectory(options, func() []string { return nil }, host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames(), nil), host.GetCurrentDirectory())
|
||||
outputPath := outputpaths.GetSourceFilePathInNewDirWorker(sourceFile.FileName(), options.OutDir, host.GetCurrentDirectory(), commonDir, host.UseCaseSensitiveFileNames())
|
||||
if tspath.ComparePaths(sourceFile.FileName(), outputPath, tspath.ComparePathsOptions{
|
||||
UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(),
|
||||
CurrentDirectory: host.GetCurrentDirectory(),
|
||||
}) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getSourceFilesToEmit(host SourceFileMayBeEmittedHost, targetSourceFile *ast.SourceFile, forceDtsEmit bool) []*ast.SourceFile {
|
||||
var sourceFiles []*ast.SourceFile
|
||||
if targetSourceFile != nil {
|
||||
sourceFiles = []*ast.SourceFile{targetSourceFile}
|
||||
} else {
|
||||
sourceFiles = host.SourceFiles()
|
||||
}
|
||||
return core.Filter(sourceFiles, func(sourceFile *ast.SourceFile) bool {
|
||||
return sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
|
||||
})
|
||||
}
|
||||
|
||||
func isSourceFileNotJson(file *ast.SourceFile) bool {
|
||||
return !ast.IsJsonSourceFile(file)
|
||||
}
|
||||
|
||||
func getDeclarationDiagnostics(host EmitHost, file *ast.SourceFile) []*ast.Diagnostic {
|
||||
// TODO: use p.getSourceFilesToEmit cache
|
||||
fullFiles := core.Filter(getSourceFilesToEmit(host, file, false), isSourceFileNotJson)
|
||||
if !core.Some(fullFiles, func(f *ast.SourceFile) bool { return f == file }) {
|
||||
return []*ast.Diagnostic{}
|
||||
}
|
||||
options := host.Options()
|
||||
transform := declarations.NewDeclarationTransformer(host, nil, options, "", "")
|
||||
transform.TransformSourceFile(file)
|
||||
return transform.GetDiagnostics()
|
||||
}
|
||||
319
tools/tsgo/internal/compiler/fileInclude.go
Normal file
319
tools/tsgo/internal/compiler/fileInclude.go
Normal file
@@ -0,0 +1,319 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/scanner"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type fileIncludeKind int
|
||||
|
||||
const (
|
||||
// References from file
|
||||
fileIncludeKindImport = iota
|
||||
fileIncludeKindReferenceFile
|
||||
fileIncludeKindTypeReferenceDirective
|
||||
fileIncludeKindLibReferenceDirective
|
||||
|
||||
fileIncludeKindRootFile
|
||||
fileIncludeKindLibFile
|
||||
fileIncludeKindAutomaticTypeDirectiveFile
|
||||
)
|
||||
|
||||
type FileIncludeReason struct {
|
||||
kind fileIncludeKind
|
||||
data any
|
||||
|
||||
// Uses relative file name
|
||||
relativeFileNameDiag *ast.Diagnostic
|
||||
relativeFileNameDiagOnce sync.Once
|
||||
|
||||
// Uses file name as is
|
||||
diag *ast.Diagnostic
|
||||
diagOnce sync.Once
|
||||
}
|
||||
|
||||
type referencedFileData struct {
|
||||
file tspath.Path
|
||||
index int
|
||||
synthetic *ast.Node
|
||||
}
|
||||
|
||||
type referenceFileLocation struct {
|
||||
file *ast.SourceFile
|
||||
node *ast.Node
|
||||
ref *ast.FileReference
|
||||
packageId module.PackageId
|
||||
isSynthetic bool
|
||||
}
|
||||
|
||||
func (r *referenceFileLocation) text() string {
|
||||
if r.node != nil {
|
||||
if !ast.NodeIsSynthesized(r.node) {
|
||||
return r.file.Text()[scanner.SkipTrivia(r.file.Text(), r.node.Loc.Pos()):r.node.End()]
|
||||
} else {
|
||||
return fmt.Sprintf(`"%s"`, r.node.Text())
|
||||
}
|
||||
} else {
|
||||
return r.file.Text()[r.ref.Pos():r.ref.End()]
|
||||
}
|
||||
}
|
||||
|
||||
func (r *referenceFileLocation) diagnosticAt(message *diagnostics.Message, args ...any) *ast.Diagnostic {
|
||||
if r.node != nil {
|
||||
return tsoptions.CreateDiagnosticForNodeInSourceFile(r.file, r.node, message, args...)
|
||||
} else {
|
||||
return ast.NewDiagnostic(r.file, r.ref.TextRange, message, args...)
|
||||
}
|
||||
}
|
||||
|
||||
type automaticTypeDirectiveFileData struct {
|
||||
typeReference string
|
||||
packageId module.PackageId
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) asIndex() int {
|
||||
return r.data.(int)
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) asLibFileIndex() (int, bool) {
|
||||
index, ok := r.data.(int)
|
||||
return index, ok
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) isReferencedFile() bool {
|
||||
return r != nil && r.kind <= fileIncludeKindLibReferenceDirective
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) asReferencedFileData() *referencedFileData {
|
||||
return r.data.(*referencedFileData)
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) asAutomaticTypeDirectiveFileData() *automaticTypeDirectiveFileData {
|
||||
return r.data.(*automaticTypeDirectiveFileData)
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) getReferencedLocation(program *Program) *referenceFileLocation {
|
||||
ref := r.asReferencedFileData()
|
||||
file := program.GetSourceFileByPath(ref.file)
|
||||
switch r.kind {
|
||||
case fileIncludeKindImport:
|
||||
var specifier *ast.Node
|
||||
var isSynthetic bool
|
||||
if ref.synthetic != nil {
|
||||
specifier = ref.synthetic
|
||||
isSynthetic = true
|
||||
} else if ref.index < len(file.Imports()) {
|
||||
specifier = file.Imports()[ref.index]
|
||||
} else {
|
||||
augIndex := len(file.Imports())
|
||||
for _, imp := range file.ModuleAugmentations {
|
||||
if imp.Kind == ast.KindStringLiteral {
|
||||
if augIndex == ref.index {
|
||||
specifier = imp
|
||||
break
|
||||
}
|
||||
augIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
resolution := program.GetResolvedModuleFromModuleSpecifier(file, specifier)
|
||||
return &referenceFileLocation{
|
||||
file: file,
|
||||
node: specifier,
|
||||
packageId: resolution.PackageId,
|
||||
isSynthetic: isSynthetic,
|
||||
}
|
||||
case fileIncludeKindReferenceFile:
|
||||
return &referenceFileLocation{
|
||||
file: file,
|
||||
ref: file.ReferencedFiles[ref.index],
|
||||
}
|
||||
case fileIncludeKindTypeReferenceDirective:
|
||||
return &referenceFileLocation{
|
||||
file: file,
|
||||
ref: file.TypeReferenceDirectives[ref.index],
|
||||
}
|
||||
case fileIncludeKindLibReferenceDirective:
|
||||
return &referenceFileLocation{
|
||||
file: file,
|
||||
ref: file.LibReferenceDirectives[ref.index],
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown reason: %v", r.kind))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) toDiagnostic(program *Program, relativeFileName bool) *ast.Diagnostic {
|
||||
if relativeFileName {
|
||||
r.relativeFileNameDiagOnce.Do(func() {
|
||||
r.relativeFileNameDiag = r.computeDiagnostic(program, func(fileName string) string {
|
||||
return tspath.GetRelativePathFromDirectory(program.GetCurrentDirectory(), fileName, program.comparePathsOptions)
|
||||
})
|
||||
})
|
||||
return r.relativeFileNameDiag
|
||||
} else {
|
||||
r.diagOnce.Do(func() {
|
||||
r.diag = r.computeDiagnostic(program, func(fileName string) string { return fileName })
|
||||
})
|
||||
return r.diag
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) computeDiagnostic(program *Program, toFileName func(string) string) *ast.Diagnostic {
|
||||
if r.isReferencedFile() {
|
||||
return r.computeReferenceFileDiagnostic(program, toFileName)
|
||||
}
|
||||
switch r.kind {
|
||||
case fileIncludeKindRootFile:
|
||||
if program.opts.Config.ConfigFile != nil {
|
||||
config := program.opts.Config
|
||||
fileName := tspath.GetNormalizedAbsolutePath(config.FileNames()[r.asIndex()], program.GetCurrentDirectory())
|
||||
if matchedFileSpec := config.GetMatchedFileSpec(fileName); matchedFileSpec != "" {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Part_of_files_list_in_tsconfig_json, matchedFileSpec, toFileName(fileName))
|
||||
} else if matchedIncludeSpec, isDefaultIncludeSpec := config.GetMatchedIncludeSpec(fileName); matchedIncludeSpec != "" {
|
||||
if isDefaultIncludeSpec {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Matched_by_default_include_pattern_Asterisk_Asterisk_Slash_Asterisk)
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Matched_by_include_pattern_0_in_1, matchedIncludeSpec, toFileName(config.ConfigName()))
|
||||
}
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Root_file_specified_for_compilation)
|
||||
}
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Root_file_specified_for_compilation)
|
||||
}
|
||||
case fileIncludeKindAutomaticTypeDirectiveFile:
|
||||
data := r.asAutomaticTypeDirectiveFileData()
|
||||
if !program.Options().UsesWildcardTypes() {
|
||||
if data.packageId.Name != "" {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Entry_point_of_type_library_0_specified_in_compilerOptions_with_packageId_1, data.typeReference, data.packageId.String())
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Entry_point_of_type_library_0_specified_in_compilerOptions, data.typeReference)
|
||||
}
|
||||
} else {
|
||||
if data.packageId.Name != "" {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Entry_point_for_implicit_type_library_0_with_packageId_1, data.typeReference, data.packageId.String())
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Entry_point_for_implicit_type_library_0, data.typeReference)
|
||||
}
|
||||
}
|
||||
case fileIncludeKindLibFile:
|
||||
if index, ok := r.asLibFileIndex(); ok {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Library_0_specified_in_compilerOptions, program.Options().Lib[index])
|
||||
} else if target := program.Options().GetEmitScriptTarget().String(); target != "" {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Default_library_for_target_0, target)
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Default_library)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown reason: %v", r.kind))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) computeReferenceFileDiagnostic(program *Program, toFileName func(string) string) *ast.Diagnostic {
|
||||
referenceLocation := program.includeProcessor.getReferenceLocation(r, program)
|
||||
referenceText := referenceLocation.text()
|
||||
switch r.kind {
|
||||
case fileIncludeKindImport:
|
||||
if !referenceLocation.isSynthetic {
|
||||
if referenceLocation.packageId.Name != "" {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Imported_via_0_from_file_1_with_packageId_2, referenceText, toFileName(referenceLocation.file.FileName()), referenceLocation.packageId.String())
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Imported_via_0_from_file_1, referenceText, toFileName(referenceLocation.file.FileName()))
|
||||
}
|
||||
} else if specifier, ok := program.importHelpersImportSpecifiers[referenceLocation.file.Path()]; ok && specifier == referenceLocation.node {
|
||||
if referenceLocation.packageId.Name != "" {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Imported_via_0_from_file_1_with_packageId_2_to_import_importHelpers_as_specified_in_compilerOptions, referenceText, toFileName(referenceLocation.file.FileName()), referenceLocation.packageId.String())
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Imported_via_0_from_file_1_to_import_importHelpers_as_specified_in_compilerOptions, referenceText, toFileName(referenceLocation.file.FileName()))
|
||||
}
|
||||
} else {
|
||||
if referenceLocation.packageId.Name != "" {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Imported_via_0_from_file_1_with_packageId_2_to_import_jsx_and_jsxs_factory_functions, referenceText, toFileName(referenceLocation.file.FileName()), referenceLocation.packageId.String())
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Imported_via_0_from_file_1_to_import_jsx_and_jsxs_factory_functions, referenceText, toFileName(referenceLocation.file.FileName()))
|
||||
}
|
||||
}
|
||||
case fileIncludeKindReferenceFile:
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Referenced_via_0_from_file_1, referenceText, toFileName(referenceLocation.file.FileName()))
|
||||
case fileIncludeKindTypeReferenceDirective:
|
||||
if referenceLocation.packageId.Name != "" {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Type_library_referenced_via_0_from_file_1_with_packageId_2, referenceText, toFileName(referenceLocation.file.FileName()), referenceLocation.packageId.String())
|
||||
} else {
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Type_library_referenced_via_0_from_file_1, referenceText, toFileName(referenceLocation.file.FileName()))
|
||||
}
|
||||
case fileIncludeKindLibReferenceDirective:
|
||||
return ast.NewCompilerDiagnostic(diagnostics.Library_referenced_via_0_from_file_1, referenceText, toFileName(referenceLocation.file.FileName()))
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown reason: %v", r.kind))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) toRelatedInfo(program *Program) *ast.Diagnostic {
|
||||
if r.isReferencedFile() {
|
||||
return r.computeReferenceFileRelatedInfo(program)
|
||||
}
|
||||
if program.opts.Config.ConfigFile == nil {
|
||||
return nil
|
||||
}
|
||||
config := program.opts.Config
|
||||
switch r.kind {
|
||||
case fileIncludeKindRootFile:
|
||||
fileName := tspath.GetNormalizedAbsolutePath(config.FileNames()[r.asIndex()], program.GetCurrentDirectory())
|
||||
if matchedFileSpec := config.GetMatchedFileSpec(fileName); matchedFileSpec != "" {
|
||||
if filesNode := tsoptions.GetTsConfigPropArrayElementValue(config.ConfigFile.SourceFile, "files", matchedFileSpec); filesNode != nil {
|
||||
return tsoptions.CreateDiagnosticForNodeInSourceFile(config.ConfigFile.SourceFile, filesNode.AsNode(), diagnostics.File_is_matched_by_files_list_specified_here)
|
||||
}
|
||||
} else if matchedIncludeSpec, isDefaultIncludeSpec := config.GetMatchedIncludeSpec(fileName); matchedIncludeSpec != "" && !isDefaultIncludeSpec {
|
||||
if includeNode := tsoptions.GetTsConfigPropArrayElementValue(config.ConfigFile.SourceFile, "include", matchedIncludeSpec); includeNode != nil {
|
||||
return tsoptions.CreateDiagnosticForNodeInSourceFile(config.ConfigFile.SourceFile, includeNode.AsNode(), diagnostics.File_is_matched_by_include_pattern_specified_here)
|
||||
}
|
||||
}
|
||||
case fileIncludeKindAutomaticTypeDirectiveFile:
|
||||
if !program.Options().UsesWildcardTypes() {
|
||||
data := r.asAutomaticTypeDirectiveFileData()
|
||||
if typesSyntax := tsoptions.GetOptionsSyntaxByArrayElementValue(program.includeProcessor.getCompilerOptionsObjectLiteralSyntax(program), "types", data.typeReference); typesSyntax != nil {
|
||||
return tsoptions.CreateDiagnosticForNodeInSourceFile(config.ConfigFile.SourceFile, typesSyntax.AsNode(), diagnostics.File_is_entry_point_of_type_library_specified_here)
|
||||
}
|
||||
}
|
||||
case fileIncludeKindLibFile:
|
||||
if index, ok := r.asLibFileIndex(); ok {
|
||||
if libSyntax := tsoptions.GetOptionsSyntaxByArrayElementValue(program.includeProcessor.getCompilerOptionsObjectLiteralSyntax(program), "lib", program.Options().Lib[index]); libSyntax != nil {
|
||||
return tsoptions.CreateDiagnosticForNodeInSourceFile(config.ConfigFile.SourceFile, libSyntax.AsNode(), diagnostics.File_is_library_specified_here)
|
||||
}
|
||||
} else if target := program.Options().GetEmitScriptTarget().String(); target != "" {
|
||||
if targetValueSyntax := tsoptions.ForEachPropertyAssignment(program.includeProcessor.getCompilerOptionsObjectLiteralSyntax(program), "target", tsoptions.GetCallbackForFindingPropertyAssignmentByValue(target)); targetValueSyntax != nil {
|
||||
return tsoptions.CreateDiagnosticForNodeInSourceFile(config.ConfigFile.SourceFile, targetValueSyntax.AsNode(), diagnostics.File_is_default_library_for_target_specified_here)
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown reason: %v", r.kind))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FileIncludeReason) computeReferenceFileRelatedInfo(program *Program) *ast.Diagnostic {
|
||||
referenceLocation := program.includeProcessor.getReferenceLocation(r, program)
|
||||
if referenceLocation.isSynthetic {
|
||||
return nil
|
||||
}
|
||||
switch r.kind {
|
||||
case fileIncludeKindImport:
|
||||
return referenceLocation.diagnosticAt(diagnostics.File_is_included_via_import_here)
|
||||
case fileIncludeKindReferenceFile:
|
||||
return referenceLocation.diagnosticAt(diagnostics.File_is_included_via_reference_here)
|
||||
case fileIncludeKindTypeReferenceDirective:
|
||||
return referenceLocation.diagnosticAt(diagnostics.File_is_included_via_type_library_reference_here)
|
||||
case fileIncludeKindLibReferenceDirective:
|
||||
return referenceLocation.diagnosticAt(diagnostics.File_is_included_via_library_reference_here)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown reason: %v", r.kind))
|
||||
}
|
||||
}
|
||||
791
tools/tsgo/internal/compiler/fileloader.go
Normal file
791
tools/tsgo/internal/compiler/fileloader.go
Normal file
@@ -0,0 +1,791 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/tracing"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
type libResolution struct {
|
||||
libraryName string
|
||||
resolution *module.ResolvedModule
|
||||
trace []module.DiagAndArgs
|
||||
}
|
||||
|
||||
type LibFile struct {
|
||||
Name string
|
||||
path string
|
||||
Replaced bool
|
||||
}
|
||||
|
||||
type sourceFileFromReferenceDiagnostic struct {
|
||||
message *diagnostics.Message
|
||||
args []any
|
||||
}
|
||||
|
||||
type fileLoader struct {
|
||||
opts ProgramOptions
|
||||
resolver *module.Resolver
|
||||
defaultLibraryPath string
|
||||
comparePathsOptions tspath.ComparePathsOptions
|
||||
supportedExtensions [][]string
|
||||
supportedExtensionsWithJsonIfResolveJsonModule [][]string
|
||||
|
||||
filesParser *filesParser
|
||||
rootTasks []*parseTask
|
||||
|
||||
totalFileCount atomic.Int32
|
||||
libFileCount atomic.Int32
|
||||
|
||||
factoryMu sync.Mutex
|
||||
factory ast.NodeFactory
|
||||
|
||||
projectReferenceFileMapper *projectReferenceFileMapper
|
||||
dtsDirectories collections.Set[tspath.Path]
|
||||
|
||||
pathForLibFileCache collections.SyncMap[string, *LibFile]
|
||||
pathForLibFileResolutions collections.SyncMap[tspath.Path, *libResolution]
|
||||
}
|
||||
|
||||
type redirectsFile struct {
|
||||
// Index of file at which this redirect file needs to be iterated
|
||||
index int
|
||||
fileName string
|
||||
path tspath.Path
|
||||
target tspath.Path
|
||||
}
|
||||
|
||||
type DuplicateSourceFile struct {
|
||||
ParseOptions ast.SourceFileParseOptions
|
||||
Hash xxh3.Uint128
|
||||
ScriptKind core.ScriptKind
|
||||
}
|
||||
|
||||
var _ ast.HasFileName = (*redirectsFile)(nil)
|
||||
|
||||
func (r *redirectsFile) FileName() string {
|
||||
return r.fileName
|
||||
}
|
||||
|
||||
func (r *redirectsFile) Path() tspath.Path {
|
||||
return r.path
|
||||
}
|
||||
|
||||
type processedFiles struct {
|
||||
resolver *module.Resolver
|
||||
files []*ast.SourceFile
|
||||
// duplicateSourceFiles tracks parsed files loaded during program construction
|
||||
// that were later dropped from the final program, such as losing filename
|
||||
// casing variants for the same path or files hidden behind package redirect
|
||||
// deduplication. Their parse-cache acquires still need to be balanced when
|
||||
// the program is disposed.
|
||||
duplicateSourceFiles []*DuplicateSourceFile
|
||||
filesByPath map[tspath.Path]*ast.SourceFile
|
||||
projectReferenceFileMapper *projectReferenceFileMapper
|
||||
missingFiles []string
|
||||
resolvedModules map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule]
|
||||
typeResolutionsInFile map[tspath.Path]module.ModeAwareCache[*module.ResolvedTypeReferenceDirective]
|
||||
sourceFileMetaDatas map[tspath.Path]ast.SourceFileMetaData
|
||||
jsxRuntimeImportSpecifiers map[tspath.Path]*jsxRuntimeImportSpecifier
|
||||
importHelpersImportSpecifiers map[tspath.Path]*ast.StringLiteralNode
|
||||
libFiles map[tspath.Path]*LibFile
|
||||
// List of present unsupported extensions
|
||||
sourceFilesFoundSearchingNodeModules collections.Set[tspath.Path]
|
||||
includeProcessor *includeProcessor
|
||||
// if file was included using source file and its output is actually part of program
|
||||
// this contains mapping from output to source file
|
||||
outputFileToProjectReferenceSource map[tspath.Path]string
|
||||
// Key is a file path. Value is the list of files that redirect to it (same package, different install location)
|
||||
redirectTargetsMap map[tspath.Path][]string
|
||||
// filesByPath for redirect files
|
||||
redirectFilesByPath map[tspath.Path]*redirectsFile
|
||||
finishedProcessing bool
|
||||
}
|
||||
|
||||
type jsxRuntimeImportSpecifier struct {
|
||||
moduleReference string
|
||||
specifier *ast.StringLiteralNode
|
||||
}
|
||||
|
||||
func processAllProgramFiles(
|
||||
opts ProgramOptions,
|
||||
singleThreaded bool,
|
||||
) processedFiles {
|
||||
compilerOptions := opts.Config.CompilerOptions()
|
||||
rootFiles := opts.Config.FileNames()
|
||||
supportedExtensions := tsoptions.GetSupportedExtensions(compilerOptions, nil /*extraFileExtensions*/)
|
||||
supportedExtensionsWithJsonIfResolveJsonModule := tsoptions.GetSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions, supportedExtensions)
|
||||
var maxNodeModuleJsDepth int
|
||||
if p := opts.Config.CompilerOptions().MaxNodeModuleJsDepth; p != nil {
|
||||
maxNodeModuleJsDepth = *p
|
||||
}
|
||||
loader := fileLoader{
|
||||
opts: opts,
|
||||
defaultLibraryPath: tspath.GetNormalizedAbsolutePath(opts.Host.DefaultLibraryPath(), opts.Host.GetCurrentDirectory()),
|
||||
comparePathsOptions: tspath.ComparePathsOptions{
|
||||
UseCaseSensitiveFileNames: opts.Host.FS().UseCaseSensitiveFileNames(),
|
||||
CurrentDirectory: opts.Host.GetCurrentDirectory(),
|
||||
},
|
||||
filesParser: &filesParser{
|
||||
wg: core.NewWorkGroup(singleThreaded),
|
||||
maxDepth: maxNodeModuleJsDepth,
|
||||
},
|
||||
rootTasks: make([]*parseTask, 0, len(rootFiles)+len(compilerOptions.Lib)),
|
||||
supportedExtensions: supportedExtensions,
|
||||
supportedExtensionsWithJsonIfResolveJsonModule: supportedExtensionsWithJsonIfResolveJsonModule,
|
||||
}
|
||||
loader.addProjectReferenceTasks(singleThreaded)
|
||||
loader.resolver = module.NewResolver(loader.projectReferenceFileMapper.host, compilerOptions, opts.TypingsLocation, opts.ProjectName)
|
||||
if opts.Tracing != nil {
|
||||
defer opts.Tracing.Push(tracing.PhaseProgram, "processRootFiles", map[string]any{"count": len(rootFiles)}, false)()
|
||||
}
|
||||
for index, rootFile := range rootFiles {
|
||||
loader.addRootFileTask(rootFile, nil, &FileIncludeReason{kind: fileIncludeKindRootFile, data: index})
|
||||
}
|
||||
if len(rootFiles) > 0 && compilerOptions.NoLib.IsFalseOrUnknown() {
|
||||
if compilerOptions.Lib == nil {
|
||||
name := tsoptions.GetDefaultLibFileName(compilerOptions)
|
||||
libFile := loader.pathForLibFile(name)
|
||||
loader.addRootTask(libFile.path, libFile, &FileIncludeReason{kind: fileIncludeKindLibFile})
|
||||
|
||||
} else {
|
||||
for index, lib := range compilerOptions.Lib {
|
||||
if name, ok := tsoptions.GetLibFileName(lib); ok {
|
||||
libFile := loader.pathForLibFile(name)
|
||||
loader.addRootTask(libFile.path, libFile, &FileIncludeReason{kind: fileIncludeKindLibFile, data: index})
|
||||
}
|
||||
// !!! error on unknown name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(rootFiles) > 0 {
|
||||
loader.addAutomaticTypeDirectiveTasks()
|
||||
}
|
||||
|
||||
loader.filesParser.parse(&loader, loader.rootTasks)
|
||||
|
||||
// Clear out loader and host to ensure its not used post program creation
|
||||
loader.projectReferenceFileMapper.loader = nil
|
||||
loader.projectReferenceFileMapper.host = nil
|
||||
|
||||
return loader.filesParser.getProcessedFiles(&loader)
|
||||
}
|
||||
|
||||
func (p *fileLoader) toPath(file string) tspath.Path {
|
||||
return tspath.ToPath(file, p.opts.Host.GetCurrentDirectory(), p.opts.Host.FS().UseCaseSensitiveFileNames())
|
||||
}
|
||||
|
||||
func (p *fileLoader) addRootTask(fileName string, libFile *LibFile, includeReason *FileIncludeReason) {
|
||||
absPath := tspath.GetNormalizedAbsolutePath(fileName, p.opts.Host.GetCurrentDirectory())
|
||||
if p.opts.Config.CompilerOptions().AllowNonTsExtensions.IsTrue() || tspath.HasExtension(absPath) {
|
||||
p.rootTasks = append(p.rootTasks, &parseTask{
|
||||
normalizedFilePath: absPath,
|
||||
libFile: libFile,
|
||||
includeReason: includeReason,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *fileLoader) addRootFileTask(fileName string, libFile *LibFile, includeReason *FileIncludeReason) {
|
||||
currDir := p.opts.Host.GetCurrentDirectory()
|
||||
absPath := tspath.GetNormalizedAbsolutePath(fileName, currDir)
|
||||
containingFile := currDir
|
||||
if p.opts.Config.ConfigFile != nil {
|
||||
containingFile = tspath.GetNormalizedAbsolutePath(p.opts.Config.ConfigFile.SourceFile.FileName(), currDir)
|
||||
}
|
||||
resolvedFile, diagnostic := p.getSourceFileFromReference(absPath, fileName, containingFile, includeReason)
|
||||
rootTask := &parseTask{
|
||||
normalizedFilePath: resolvedFile,
|
||||
libFile: libFile,
|
||||
includeReason: includeReason,
|
||||
}
|
||||
if diagnostic != nil {
|
||||
rootTask.normalizedFilePath = absPath
|
||||
rootTask.processingDiagnostics = []*processingDiagnostic{{
|
||||
kind: processingDiagnosticKindExplainingFileInclude,
|
||||
data: &includeExplainingDiagnostic{
|
||||
diagnosticReason: includeReason,
|
||||
message: diagnostic.message,
|
||||
args: diagnostic.args,
|
||||
},
|
||||
}}
|
||||
}
|
||||
p.rootTasks = append(p.rootTasks, rootTask)
|
||||
}
|
||||
|
||||
func (p *fileLoader) addAutomaticTypeDirectiveTasks() {
|
||||
var containingDirectory string
|
||||
compilerOptions := p.opts.Config.CompilerOptions()
|
||||
if compilerOptions.ConfigFilePath != "" {
|
||||
containingDirectory = tspath.GetDirectoryPath(compilerOptions.ConfigFilePath)
|
||||
} else {
|
||||
containingDirectory = p.opts.Host.GetCurrentDirectory()
|
||||
}
|
||||
containingFileName := tspath.CombinePaths(containingDirectory, module.InferredTypesContainingFile)
|
||||
p.rootTasks = append(p.rootTasks, &parseTask{
|
||||
normalizedFilePath: containingFileName,
|
||||
isForAutomaticTypeDirective: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *fileLoader) resolveAutomaticTypeDirectives(containingFileName string) (
|
||||
toParse []resolvedRef,
|
||||
typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective],
|
||||
typeResolutionsTrace []module.DiagAndArgs,
|
||||
pDiagnostics []*processingDiagnostic,
|
||||
) {
|
||||
automaticTypeDirectiveNames := module.GetAutomaticTypeDirectiveNames(p.opts.Config.CompilerOptions(), p.opts.Host)
|
||||
if len(automaticTypeDirectiveNames) != 0 {
|
||||
toParse = make([]resolvedRef, 0, len(automaticTypeDirectiveNames))
|
||||
typeResolutionsInFile = make(module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], len(automaticTypeDirectiveNames))
|
||||
for _, name := range automaticTypeDirectiveNames {
|
||||
// Under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode.
|
||||
// Under bundler module resolution, this also triggers the "import" condition to be used.
|
||||
resolutionMode := core.ResolutionModeNone
|
||||
resolved, trace := p.resolver.ResolveTypeReferenceDirective(name, containingFileName, resolutionMode, nil)
|
||||
var traceDone func()
|
||||
if p.opts.Tracing != nil {
|
||||
traceDone = p.opts.Tracing.Push(tracing.PhaseProgram, "processTypeReferenceDirective", map[string]any{"directive": name, "hasResolved": resolved.IsResolved(), "refKind": int(fileIncludeKindAutomaticTypeDirectiveFile)}, false)
|
||||
}
|
||||
typeResolutionsInFile[module.ModeAwareCacheKey{Name: name, Mode: resolutionMode}] = resolved
|
||||
typeResolutionsTrace = append(typeResolutionsTrace, trace...)
|
||||
if resolved.IsResolved() {
|
||||
toParse = append(toParse, resolvedRef{
|
||||
fileName: resolved.ResolvedFileName,
|
||||
increaseDepth: resolved.IsExternalLibraryImport,
|
||||
elideOnDepth: false,
|
||||
includeReason: &FileIncludeReason{
|
||||
kind: fileIncludeKindAutomaticTypeDirectiveFile,
|
||||
data: &automaticTypeDirectiveFileData{name, resolved.PackageId},
|
||||
},
|
||||
packageId: resolved.PackageId,
|
||||
})
|
||||
} else {
|
||||
pDiagnostics = append(pDiagnostics, &processingDiagnostic{
|
||||
kind: processingDiagnosticKindExplainingFileInclude,
|
||||
data: &includeExplainingDiagnostic{
|
||||
diagnosticReason: &FileIncludeReason{
|
||||
kind: fileIncludeKindAutomaticTypeDirectiveFile,
|
||||
data: &automaticTypeDirectiveFileData{typeReference: name},
|
||||
},
|
||||
message: diagnostics.Cannot_find_type_definition_file_for_0,
|
||||
args: []any{name},
|
||||
},
|
||||
})
|
||||
}
|
||||
if traceDone != nil {
|
||||
traceDone()
|
||||
}
|
||||
}
|
||||
}
|
||||
return toParse, typeResolutionsInFile, typeResolutionsTrace, pDiagnostics
|
||||
}
|
||||
|
||||
func (p *fileLoader) addProjectReferenceTasks(singleThreaded bool) {
|
||||
p.projectReferenceFileMapper = &projectReferenceFileMapper{
|
||||
opts: p.opts,
|
||||
host: p.opts.Host,
|
||||
}
|
||||
projectReferences := p.opts.Config.ResolvedProjectReferencePaths()
|
||||
if len(projectReferences) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
parser := &projectReferenceParser{
|
||||
loader: p,
|
||||
wg: core.NewWorkGroup(singleThreaded),
|
||||
}
|
||||
rootTasks := createProjectReferenceParseTasks(projectReferences)
|
||||
parser.parse(rootTasks)
|
||||
}
|
||||
|
||||
func (p *fileLoader) sortLibs(libFiles []*ast.SourceFile) {
|
||||
slices.SortFunc(libFiles, func(f1 *ast.SourceFile, f2 *ast.SourceFile) int {
|
||||
return cmp.Compare(p.getDefaultLibFilePriority(f1), p.getDefaultLibFilePriority(f2))
|
||||
})
|
||||
}
|
||||
|
||||
func (p *fileLoader) getDefaultLibFilePriority(a *ast.SourceFile) int {
|
||||
// defaultLibraryPath and a.FileName() are absolute and normalized; a prefix check should suffice.
|
||||
defaultLibraryPath := tspath.RemoveTrailingDirectorySeparator(p.defaultLibraryPath)
|
||||
aFileName := a.FileName()
|
||||
|
||||
if strings.HasPrefix(aFileName, defaultLibraryPath) && len(aFileName) > len(defaultLibraryPath) && aFileName[len(defaultLibraryPath)] == tspath.DirectorySeparator {
|
||||
// avoid tspath.GetBaseFileName; we know these paths are already absolute and normalized.
|
||||
basename := aFileName[strings.LastIndexByte(aFileName, tspath.DirectorySeparator)+1:]
|
||||
if basename == "lib.d.ts" || basename == "lib.es6.d.ts" {
|
||||
return 0
|
||||
}
|
||||
name := strings.TrimSuffix(strings.TrimPrefix(basename, "lib."), ".d.ts")
|
||||
index := slices.Index(tsoptions.Libs, name)
|
||||
if index != -1 {
|
||||
return index + 1
|
||||
}
|
||||
}
|
||||
return len(tsoptions.Libs) + 2
|
||||
}
|
||||
|
||||
func (p *fileLoader) loadSourceFileMetaData(fileName string) ast.SourceFileMetaData {
|
||||
packageJsonScope := p.resolver.GetPackageScopeForPath(tspath.GetDirectoryPath(fileName))
|
||||
moduleResolutionKind := p.opts.Config.CompilerOptions().GetModuleResolutionKind()
|
||||
|
||||
var packageJsonType, packageJsonDirectory string
|
||||
if packageJsonScope.Exists() {
|
||||
packageJsonDirectory = packageJsonScope.PackageDirectory
|
||||
if value, ok := packageJsonScope.Contents.Type.GetValue(); ok {
|
||||
if !tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMts, tspath.ExtensionCts, tspath.ExtensionMjs, tspath.ExtensionCjs}) &&
|
||||
core.ModuleResolutionKindNode16 <= moduleResolutionKind && moduleResolutionKind <= core.ModuleResolutionKindNodeNext || strings.Contains(fileName, "/node_modules/") {
|
||||
packageJsonType = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impliedNodeFormat := ast.GetImpliedNodeFormatForFile(fileName, packageJsonType)
|
||||
return ast.SourceFileMetaData{
|
||||
PackageJsonType: packageJsonType,
|
||||
PackageJsonDirectory: packageJsonDirectory,
|
||||
ImpliedNodeFormat: impliedNodeFormat,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *fileLoader) parseSourceFile(t *parseTask) *ast.SourceFile {
|
||||
if p.opts.Tracing != nil {
|
||||
defer p.opts.Tracing.Push(tracing.PhaseParse, "createSourceFile", map[string]any{"path": t.normalizedFilePath}, true)()
|
||||
}
|
||||
path := p.toPath(t.normalizedFilePath)
|
||||
options := p.projectReferenceFileMapper.getCompilerOptionsForFile(t)
|
||||
sourceFile := p.opts.Host.GetSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: t.normalizedFilePath,
|
||||
Path: path,
|
||||
ExternalModuleIndicatorOptions: ast.GetExternalModuleIndicatorOptions(t.normalizedFilePath, options, t.metadata),
|
||||
})
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
func (p *fileLoader) isSupportedExtension(canonicalFileName string) bool {
|
||||
for _, group := range p.supportedExtensionsWithJsonIfResolveJsonModule {
|
||||
if tspath.FileExtensionIsOneOf(canonicalFileName, group) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *fileLoader) getSourceFileFromReference(
|
||||
fileName string,
|
||||
referenceText string,
|
||||
containingFile string,
|
||||
includeReason *FileIncludeReason,
|
||||
) (string, *sourceFileFromReferenceDiagnostic) {
|
||||
options := p.opts.Config.CompilerOptions()
|
||||
allowNonTsExtensions := options.AllowNonTsExtensions.IsTrue()
|
||||
diagnosticFileName := tspath.NormalizeSlashes(referenceText)
|
||||
|
||||
if tspath.HasExtension(fileName) {
|
||||
canonicalFileName := tspath.GetCanonicalFileName(fileName, p.opts.Host.FS().UseCaseSensitiveFileNames())
|
||||
if !allowNonTsExtensions && !p.isSupportedExtension(canonicalFileName) {
|
||||
if tspath.HasJSFileExtension(canonicalFileName) {
|
||||
return "", &sourceFileFromReferenceDiagnostic{message: diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, args: []any{diagnosticFileName}}
|
||||
}
|
||||
return "", &sourceFileFromReferenceDiagnostic{message: diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, args: []any{diagnosticFileName, "'" + strings.Join(core.Flatten(p.supportedExtensions), "', '") + "'"}}
|
||||
}
|
||||
|
||||
if !p.opts.Host.FS().FileExists(fileName) {
|
||||
return "", &sourceFileFromReferenceDiagnostic{message: diagnostics.File_0_not_found, args: []any{diagnosticFileName}}
|
||||
}
|
||||
|
||||
if includeReason.isReferencedFile() && tspath.GetCanonicalFileName(containingFile, p.opts.Host.FS().UseCaseSensitiveFileNames()) == canonicalFileName {
|
||||
return "", &sourceFileFromReferenceDiagnostic{message: diagnostics.A_file_cannot_have_a_reference_to_itself}
|
||||
}
|
||||
return fileName, nil
|
||||
}
|
||||
|
||||
if allowNonTsExtensions && p.opts.Host.FS().FileExists(fileName) {
|
||||
return fileName, nil
|
||||
}
|
||||
|
||||
if allowNonTsExtensions {
|
||||
return "", &sourceFileFromReferenceDiagnostic{message: diagnostics.File_0_not_found, args: []any{diagnosticFileName}}
|
||||
}
|
||||
|
||||
for _, ext := range p.supportedExtensions[0] {
|
||||
candidate := fileName + ext
|
||||
if p.opts.Host.FS().FileExists(candidate) {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", &sourceFileFromReferenceDiagnostic{message: diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, args: []any{diagnosticFileName, "'" + strings.Join(core.Flatten(p.supportedExtensions), "', '") + "'"}}
|
||||
}
|
||||
|
||||
func (p *fileLoader) resolveTripleslashPathReference(moduleName string, containingFile string, index int) (*resolvedRef, *processingDiagnostic) {
|
||||
basePath := tspath.GetDirectoryPath(containingFile)
|
||||
referencedFileName := moduleName
|
||||
|
||||
if !tspath.IsRootedDiskPath(moduleName) {
|
||||
referencedFileName = tspath.CombinePaths(basePath, moduleName)
|
||||
}
|
||||
normalizedFileName := tspath.NormalizePath(referencedFileName)
|
||||
includeReason := &FileIncludeReason{
|
||||
kind: fileIncludeKindReferenceFile,
|
||||
data: &referencedFileData{
|
||||
file: p.toPath(containingFile),
|
||||
index: index,
|
||||
},
|
||||
}
|
||||
|
||||
resolvedFileName, diagnostic := p.getSourceFileFromReference(
|
||||
normalizedFileName,
|
||||
moduleName,
|
||||
containingFile,
|
||||
includeReason,
|
||||
)
|
||||
if diagnostic != nil {
|
||||
return nil, &processingDiagnostic{
|
||||
kind: processingDiagnosticKindExplainingFileInclude,
|
||||
data: &includeExplainingDiagnostic{
|
||||
diagnosticReason: includeReason,
|
||||
message: diagnostic.message,
|
||||
args: diagnostic.args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &resolvedRef{
|
||||
fileName: resolvedFileName,
|
||||
includeReason: includeReason,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *fileLoader) resolveTypeReferenceDirectives(t *parseTask) {
|
||||
file := t.file
|
||||
if len(file.TypeReferenceDirectives) == 0 {
|
||||
return
|
||||
}
|
||||
if p.opts.Tracing != nil {
|
||||
defer p.opts.Tracing.Push(tracing.PhaseProgram, "resolveTypeReferenceDirectiveNamesWorker", map[string]any{"containingFileName": file.FileName()}, false)()
|
||||
}
|
||||
meta := t.metadata
|
||||
|
||||
typeResolutionsInFile := make(module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], len(file.TypeReferenceDirectives))
|
||||
var typeResolutionsTrace []module.DiagAndArgs
|
||||
for index, ref := range file.TypeReferenceDirectives {
|
||||
redirect, fileName := p.projectReferenceFileMapper.getRedirectForResolution(file)
|
||||
resolutionMode := getModeForTypeReferenceDirectiveInFile(ref, file, meta, module.GetCompilerOptionsWithRedirect(p.opts.Config.CompilerOptions(), redirect))
|
||||
resolved, trace := p.resolver.ResolveTypeReferenceDirective(ref.FileName, fileName, resolutionMode, redirect)
|
||||
var traceDone func()
|
||||
if p.opts.Tracing != nil {
|
||||
traceDone = p.opts.Tracing.Push(tracing.PhaseProgram, "processTypeReferenceDirective", map[string]any{"directive": ref.FileName, "hasResolved": resolved.IsResolved(), "refKind": int(fileIncludeKindTypeReferenceDirective), "refPath": string(t.path)}, false)
|
||||
}
|
||||
typeResolutionsInFile[module.ModeAwareCacheKey{Name: ref.FileName, Mode: resolutionMode}] = resolved
|
||||
includeReason := &FileIncludeReason{
|
||||
kind: fileIncludeKindTypeReferenceDirective,
|
||||
data: &referencedFileData{
|
||||
file: t.path,
|
||||
index: index,
|
||||
},
|
||||
}
|
||||
typeResolutionsTrace = append(typeResolutionsTrace, trace...)
|
||||
|
||||
if resolved.IsResolved() {
|
||||
t.addSubTask(resolvedRef{
|
||||
fileName: resolved.ResolvedFileName,
|
||||
increaseDepth: resolved.IsExternalLibraryImport,
|
||||
elideOnDepth: false,
|
||||
includeReason: includeReason,
|
||||
packageId: resolved.PackageId,
|
||||
}, nil)
|
||||
} else {
|
||||
t.processingDiagnostics = append(t.processingDiagnostics, &processingDiagnostic{
|
||||
kind: processingDiagnosticKindUnknownReference,
|
||||
data: includeReason,
|
||||
})
|
||||
}
|
||||
if traceDone != nil {
|
||||
traceDone()
|
||||
}
|
||||
}
|
||||
|
||||
t.typeResolutionsInFile = typeResolutionsInFile
|
||||
t.typeResolutionsTrace = typeResolutionsTrace
|
||||
}
|
||||
|
||||
const externalHelpersModuleNameText = "tslib" // TODO(jakebailey): dedupe
|
||||
|
||||
func (p *fileLoader) resolveImportsAndModuleAugmentations(t *parseTask) {
|
||||
if p.opts.Tracing != nil {
|
||||
defer p.opts.Tracing.Push(tracing.PhaseProgram, "resolveModuleNamesWorker", map[string]any{"containingFileName": t.file.FileName()}, false)()
|
||||
}
|
||||
file := t.file
|
||||
meta := t.metadata
|
||||
|
||||
moduleNames := make([]*ast.Node, 0, len(file.Imports())+len(file.ModuleAugmentations)+2)
|
||||
|
||||
isJavaScriptFile := ast.IsSourceFileJS(file)
|
||||
isExternalModuleFile := ast.IsExternalModule(file)
|
||||
|
||||
redirect, fileName := p.projectReferenceFileMapper.getRedirectForResolution(file)
|
||||
optionsForFile := module.GetCompilerOptionsWithRedirect(p.opts.Config.CompilerOptions(), redirect)
|
||||
if isJavaScriptFile || (!file.IsDeclarationFile && (optionsForFile.GetIsolatedModules() || isExternalModuleFile)) {
|
||||
if optionsForFile.ImportHelpers.IsTrue() {
|
||||
specifier := p.createSyntheticImport(externalHelpersModuleNameText, file)
|
||||
moduleNames = append(moduleNames, specifier)
|
||||
t.importHelpersImportSpecifier = specifier
|
||||
}
|
||||
}
|
||||
|
||||
if file.ScriptKind == core.ScriptKindJSX || file.ScriptKind == core.ScriptKindTSX {
|
||||
jsxImport := ast.GetJSXRuntimeImport(ast.GetJSXImplicitImportBase(optionsForFile, file), optionsForFile)
|
||||
if jsxImport != "" {
|
||||
specifier := p.createSyntheticImport(jsxImport, file)
|
||||
moduleNames = append(moduleNames, specifier)
|
||||
t.jsxRuntimeImportSpecifier = &jsxRuntimeImportSpecifier{
|
||||
moduleReference: jsxImport,
|
||||
specifier: specifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
importsStart := len(moduleNames)
|
||||
|
||||
moduleNames = append(moduleNames, file.Imports()...)
|
||||
for _, imp := range file.ModuleAugmentations {
|
||||
if imp.Kind == ast.KindStringLiteral {
|
||||
moduleNames = append(moduleNames, imp)
|
||||
}
|
||||
// Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`.
|
||||
}
|
||||
|
||||
if len(moduleNames) != 0 {
|
||||
resolutionsInFile := make(module.ModeAwareCache[*module.ResolvedModule], len(moduleNames))
|
||||
var resolutionsTrace []module.DiagAndArgs
|
||||
|
||||
for index, entry := range moduleNames {
|
||||
moduleName := entry.Text()
|
||||
if moduleName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
mode := getModeForUsageLocation(file.FileName(), meta, entry, optionsForFile)
|
||||
resolvedModule, trace := p.resolver.ResolveModuleName(moduleName, fileName, mode, redirect)
|
||||
resolutionsInFile[module.ModeAwareCacheKey{Name: moduleName, Mode: mode}] = resolvedModule
|
||||
resolutionsTrace = append(resolutionsTrace, trace...)
|
||||
|
||||
if !resolvedModule.IsResolved() {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvedFileName := resolvedModule.ResolvedFileName
|
||||
isFromNodeModulesSearch := resolvedModule.IsExternalLibraryImport
|
||||
// Don't treat redirected files as JS files.
|
||||
isJsFile := !tspath.FileExtensionIsOneOf(resolvedFileName, tspath.SupportedTSExtensionsWithJsonFlat) && p.projectReferenceFileMapper.getRedirectParsedCommandLineForResolution(ast.NewHasFileName(resolvedFileName, p.toPath(resolvedFileName))) == nil
|
||||
isJsFileFromNodeModules := isFromNodeModulesSearch && isJsFile && strings.Contains(resolvedFileName, "/node_modules/")
|
||||
|
||||
// add file to program only if:
|
||||
// - resolution was successful
|
||||
// - noResolve is falsy
|
||||
// - module name comes from the list of imports
|
||||
// - it's not a top level JavaScript module that exceeded the search max
|
||||
|
||||
importIndex := index - importsStart
|
||||
|
||||
shouldAddFile := moduleName != "" &&
|
||||
module.GetResolutionDiagnostic(optionsForFile, resolvedModule, file) == nil &&
|
||||
!optionsForFile.NoResolve.IsTrue() &&
|
||||
!(isJsFile && !optionsForFile.GetAllowJS()) &&
|
||||
(importIndex < 0 || (importIndex < len(file.Imports()) && (ast.IsInJSFile(file.Imports()[importIndex]) || file.Imports()[importIndex].Flags&ast.NodeFlagsJSDoc == 0)))
|
||||
|
||||
if shouldAddFile {
|
||||
t.addSubTask(resolvedRef{
|
||||
fileName: resolvedFileName,
|
||||
increaseDepth: resolvedModule.IsExternalLibraryImport,
|
||||
elideOnDepth: isJsFileFromNodeModules,
|
||||
includeReason: &FileIncludeReason{
|
||||
kind: fileIncludeKindImport,
|
||||
data: &referencedFileData{
|
||||
file: t.path,
|
||||
index: importIndex,
|
||||
synthetic: core.IfElse(importIndex < 0, entry, nil),
|
||||
},
|
||||
},
|
||||
packageId: resolvedModule.PackageId,
|
||||
}, nil)
|
||||
}
|
||||
}
|
||||
|
||||
t.resolutionsInFile = resolutionsInFile
|
||||
t.resolutionsTrace = resolutionsTrace
|
||||
}
|
||||
}
|
||||
|
||||
func (p *fileLoader) createSyntheticImport(text string, file *ast.SourceFile) *ast.StringLiteralNode {
|
||||
p.factoryMu.Lock()
|
||||
defer p.factoryMu.Unlock()
|
||||
externalHelpersModuleReference := p.factory.NewStringLiteral(text, ast.TokenFlagsNone)
|
||||
importDecl := p.factory.NewImportDeclaration(nil, nil, externalHelpersModuleReference, nil)
|
||||
externalHelpersModuleReference.Parent = importDecl
|
||||
importDecl.Parent = file.AsNode()
|
||||
return externalHelpersModuleReference
|
||||
}
|
||||
|
||||
func (p *fileLoader) pathForLibFile(name string) *LibFile {
|
||||
if cached, ok := p.pathForLibFileCache.Load(name); ok {
|
||||
return cached
|
||||
}
|
||||
|
||||
path := tspath.CombinePaths(p.defaultLibraryPath, name)
|
||||
replaced := false
|
||||
if p.opts.Config.CompilerOptions().LibReplacement.IsTrue() && name != "lib.d.ts" {
|
||||
libraryName := getLibraryNameFromLibFileName(name)
|
||||
resolveFrom := getInferredLibraryNameResolveFrom(p.opts.Config.CompilerOptions(), p.opts.Host.GetCurrentDirectory(), name)
|
||||
resolution, trace := p.resolveLibrary(libraryName, resolveFrom)
|
||||
if resolution.IsResolved() {
|
||||
path = resolution.ResolvedFileName
|
||||
replaced = true
|
||||
}
|
||||
p.pathForLibFileResolutions.LoadOrStore(p.toPath(resolveFrom), &libResolution{
|
||||
libraryName: libraryName,
|
||||
resolution: resolution,
|
||||
trace: trace,
|
||||
})
|
||||
}
|
||||
|
||||
libPath, _ := p.pathForLibFileCache.LoadOrStore(name, &LibFile{name, path, replaced})
|
||||
return libPath
|
||||
}
|
||||
|
||||
func (p *fileLoader) resolveLibrary(libraryName, resolveFrom string) (*module.ResolvedModule, []module.DiagAndArgs) {
|
||||
if tr := p.opts.Tracing; tr != nil {
|
||||
defer tr.Push(tracing.PhaseProgram, "resolveLibrary", map[string]any{"resolveFrom": resolveFrom}, false)()
|
||||
}
|
||||
return p.resolver.ResolveModuleName(libraryName, resolveFrom, core.ModuleKindCommonJS, nil)
|
||||
}
|
||||
|
||||
func getLibraryNameFromLibFileName(libFileName string) string {
|
||||
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
|
||||
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
|
||||
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
|
||||
components := strings.Split(libFileName, ".")
|
||||
var path strings.Builder
|
||||
path.WriteString("@typescript/lib-")
|
||||
if len(components) > 1 {
|
||||
path.WriteString(components[1])
|
||||
}
|
||||
i := 2
|
||||
for i < len(components) && components[i] != "" && components[i] != "d" {
|
||||
if i == 2 {
|
||||
path.WriteByte('/')
|
||||
} else {
|
||||
path.WriteByte('-')
|
||||
}
|
||||
path.WriteString(components[i])
|
||||
i++
|
||||
}
|
||||
return path.String()
|
||||
}
|
||||
|
||||
func getInferredLibraryNameResolveFrom(options *core.CompilerOptions, currentDirectory string, libFileName string) string {
|
||||
var containingDirectory string
|
||||
if options.ConfigFilePath != "" {
|
||||
containingDirectory = tspath.GetDirectoryPath(options.ConfigFilePath)
|
||||
} else {
|
||||
containingDirectory = currentDirectory
|
||||
}
|
||||
return tspath.CombinePaths(containingDirectory, "__lib_node_modules_lookup_"+libFileName+"__.ts")
|
||||
}
|
||||
|
||||
func getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, file *ast.SourceFile, meta ast.SourceFileMetaData, options *core.CompilerOptions) core.ResolutionMode {
|
||||
if ref.ResolutionMode != core.ResolutionModeNone {
|
||||
return ref.ResolutionMode
|
||||
} else {
|
||||
return getDefaultResolutionModeForFile(file.FileName(), meta, options)
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultResolutionModeForFile(fileName string, meta ast.SourceFileMetaData, options *core.CompilerOptions) core.ResolutionMode {
|
||||
if importSyntaxAffectsModuleResolution(options) {
|
||||
return ast.GetImpliedNodeFormatForEmitWorker(fileName, options.GetEmitModuleKind(), meta)
|
||||
} else {
|
||||
return core.ResolutionModeNone
|
||||
}
|
||||
}
|
||||
|
||||
func getModeForUsageLocation(fileName string, meta ast.SourceFileMetaData, usage *ast.StringLiteralLike, options *core.CompilerOptions) core.ResolutionMode {
|
||||
if ast.IsImportDeclaration(usage.Parent) || usage.Parent.Kind == ast.KindJSImportDeclaration || ast.IsExportDeclaration(usage.Parent) || ast.IsJSDocImportTag(usage.Parent) {
|
||||
isTypeOnly := ast.IsExclusivelyTypeOnlyImportOrExport(usage.Parent)
|
||||
if isTypeOnly {
|
||||
var override core.ResolutionMode
|
||||
var ok bool
|
||||
switch usage.Parent.Kind {
|
||||
case ast.KindImportDeclaration, ast.KindJSImportDeclaration:
|
||||
override, ok = usage.Parent.AsImportDeclaration().Attributes.GetResolutionModeOverride()
|
||||
case ast.KindExportDeclaration:
|
||||
override, ok = usage.Parent.AsExportDeclaration().Attributes.GetResolutionModeOverride()
|
||||
case ast.KindJSDocImportTag:
|
||||
override, ok = usage.Parent.AsJSDocImportTag().Attributes.GetResolutionModeOverride()
|
||||
}
|
||||
if ok {
|
||||
return override
|
||||
}
|
||||
}
|
||||
}
|
||||
if ast.IsLiteralTypeNode(usage.Parent) && ast.IsImportTypeNode(usage.Parent.Parent) {
|
||||
if override, ok := usage.Parent.Parent.AsImportTypeNode().Attributes.GetResolutionModeOverride(); ok {
|
||||
return override
|
||||
}
|
||||
}
|
||||
|
||||
if options != nil && importSyntaxAffectsModuleResolution(options) {
|
||||
return getEmitSyntaxForUsageLocationWorker(fileName, meta, usage, options)
|
||||
}
|
||||
|
||||
return core.ResolutionModeNone
|
||||
}
|
||||
|
||||
func importSyntaxAffectsModuleResolution(options *core.CompilerOptions) bool {
|
||||
moduleResolution := options.GetModuleResolutionKind()
|
||||
return core.ModuleResolutionKindNode16 <= moduleResolution && moduleResolution <= core.ModuleResolutionKindNodeNext ||
|
||||
options.GetResolvePackageJsonExports() || options.GetResolvePackageJsonImports()
|
||||
}
|
||||
|
||||
func getEmitSyntaxForUsageLocationWorker(fileName string, meta ast.SourceFileMetaData, usage *ast.Node, options *core.CompilerOptions) core.ResolutionMode {
|
||||
if ast.IsRequireCall(usage.Parent, false /*requireStringLiteralLikeArgument*/) || ast.IsExternalModuleReference(usage.Parent) && ast.IsImportEqualsDeclaration(usage.Parent.Parent) {
|
||||
return core.ModuleKindCommonJS
|
||||
}
|
||||
fileEmitMode := ast.GetEmitModuleFormatOfFileWorker(fileName, options, meta)
|
||||
if ast.IsImportCall(ast.WalkUpParenthesizedExpressions(usage.Parent)) {
|
||||
if ast.ShouldTransformImportCall(fileName, options, fileEmitMode) {
|
||||
return core.ModuleKindCommonJS
|
||||
} else {
|
||||
return core.ModuleKindESNext
|
||||
}
|
||||
}
|
||||
// If we're in --module preserve on an input file, we know that an import
|
||||
// is an import. But if this is a declaration file, we'd prefer to use the
|
||||
// impliedNodeFormat. Since we want things to be consistent between the two,
|
||||
// we need to issue errors when the user writes ESM syntax in a definitely-CJS
|
||||
// file, until/unless declaration emit can indicate a true ESM import. On the
|
||||
// other hand, writing CJS syntax in a definitely-ESM file is fine, since declaration
|
||||
// emit preserves the CJS syntax.
|
||||
if fileEmitMode == core.ModuleKindCommonJS {
|
||||
return core.ModuleKindCommonJS
|
||||
} else {
|
||||
if fileEmitMode.IsNonNodeESM() || fileEmitMode == core.ModuleKindPreserve {
|
||||
return core.ModuleKindESNext
|
||||
}
|
||||
}
|
||||
return core.ModuleKindNone
|
||||
}
|
||||
567
tools/tsgo/internal/compiler/filesparser.go
Normal file
567
tools/tsgo/internal/compiler/filesparser.go
Normal file
@@ -0,0 +1,567 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"math"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/tracing"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type parseTask struct {
|
||||
normalizedFilePath string
|
||||
path tspath.Path
|
||||
file *ast.SourceFile
|
||||
libFile *LibFile
|
||||
redirectedParseTask *parseTask
|
||||
subTasks []*parseTask
|
||||
loaded bool
|
||||
startedSubTasks bool
|
||||
isForAutomaticTypeDirective bool
|
||||
includeReason *FileIncludeReason
|
||||
packageId module.PackageId
|
||||
|
||||
metadata ast.SourceFileMetaData
|
||||
resolutionsInFile module.ModeAwareCache[*module.ResolvedModule]
|
||||
resolutionsTrace []module.DiagAndArgs
|
||||
typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective]
|
||||
typeResolutionsTrace []module.DiagAndArgs
|
||||
resolutionDiagnostics []*ast.Diagnostic
|
||||
processingDiagnostics []*processingDiagnostic
|
||||
importHelpersImportSpecifier *ast.StringLiteralNode
|
||||
jsxRuntimeImportSpecifier *jsxRuntimeImportSpecifier
|
||||
|
||||
increaseDepth bool
|
||||
elideOnDepth bool
|
||||
|
||||
loadedTask *parseTask
|
||||
allIncludeReasons []*FileIncludeReason
|
||||
}
|
||||
|
||||
func (t *parseTask) FileName() string {
|
||||
return t.normalizedFilePath
|
||||
}
|
||||
|
||||
func (t *parseTask) Path() tspath.Path {
|
||||
return t.path
|
||||
}
|
||||
|
||||
func (t *parseTask) load(loader *fileLoader) {
|
||||
t.loaded = true
|
||||
if t.isForAutomaticTypeDirective {
|
||||
t.loadAutomaticTypeDirectives(loader)
|
||||
return
|
||||
}
|
||||
if loader.opts.Tracing != nil {
|
||||
defer loader.opts.Tracing.Push(tracing.PhaseProgram, "findSourceFile", map[string]any{"fileName": t.normalizedFilePath}, false)()
|
||||
}
|
||||
redirect := loader.projectReferenceFileMapper.getParseFileRedirect(t)
|
||||
if redirect != "" {
|
||||
t.redirect(loader, redirect)
|
||||
return
|
||||
}
|
||||
|
||||
if tspath.HasExtension(t.normalizedFilePath) {
|
||||
compilerOptions := loader.opts.Config.CompilerOptions()
|
||||
allowNonTsExtensions := compilerOptions.AllowNonTsExtensions.IsTrue()
|
||||
if !allowNonTsExtensions {
|
||||
canonicalFileName := tspath.GetCanonicalFileName(t.normalizedFilePath, loader.opts.Host.FS().UseCaseSensitiveFileNames())
|
||||
if !loader.isSupportedExtension(canonicalFileName) {
|
||||
if tspath.HasJSFileExtension(canonicalFileName) {
|
||||
t.processingDiagnostics = append(t.processingDiagnostics, &processingDiagnostic{
|
||||
kind: processingDiagnosticKindExplainingFileInclude,
|
||||
data: &includeExplainingDiagnostic{
|
||||
diagnosticReason: t.includeReason,
|
||||
message: diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option,
|
||||
args: []any{t.normalizedFilePath},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
t.processingDiagnostics = append(t.processingDiagnostics, &processingDiagnostic{
|
||||
kind: processingDiagnosticKindExplainingFileInclude,
|
||||
data: &includeExplainingDiagnostic{
|
||||
diagnosticReason: t.includeReason,
|
||||
message: diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1,
|
||||
args: []any{t.normalizedFilePath, "'" + strings.Join(core.Flatten(loader.supportedExtensions), "', '") + "'"},
|
||||
},
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loader.totalFileCount.Add(1)
|
||||
if t.libFile != nil {
|
||||
loader.libFileCount.Add(1)
|
||||
// Default lib files are all scripts; we can safely skip looking up their package.json
|
||||
// to avoid adding spurious lookups to file watcher tracking.
|
||||
t.metadata = ast.SourceFileMetaData{ImpliedNodeFormat: core.ResolutionModeCommonJS}
|
||||
} else {
|
||||
t.metadata = loader.loadSourceFileMetaData(t.normalizedFilePath)
|
||||
}
|
||||
|
||||
file := loader.parseSourceFile(t)
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.file = file
|
||||
t.subTasks = make([]*parseTask, 0, len(file.ReferencedFiles)+len(file.Imports())+len(file.ModuleAugmentations))
|
||||
|
||||
compilerOptions := loader.opts.Config.CompilerOptions()
|
||||
if !compilerOptions.NoResolve.IsTrue() {
|
||||
for index, ref := range file.ReferencedFiles {
|
||||
resolvedRef, processingDiagnostic := loader.resolveTripleslashPathReference(ref.FileName, file.FileName(), index)
|
||||
if processingDiagnostic != nil {
|
||||
t.processingDiagnostics = append(t.processingDiagnostics, processingDiagnostic)
|
||||
continue
|
||||
}
|
||||
t.addSubTask(*resolvedRef, nil)
|
||||
}
|
||||
|
||||
loader.resolveTypeReferenceDirectives(t)
|
||||
}
|
||||
|
||||
if compilerOptions.NoLib != core.TSTrue {
|
||||
for index, lib := range file.LibReferenceDirectives {
|
||||
includeReason := &FileIncludeReason{
|
||||
kind: fileIncludeKindLibReferenceDirective,
|
||||
data: &referencedFileData{
|
||||
file: t.path,
|
||||
index: index,
|
||||
},
|
||||
}
|
||||
if name, ok := tsoptions.GetLibFileName(lib.FileName); ok {
|
||||
libFile := loader.pathForLibFile(name)
|
||||
t.addSubTask(resolvedRef{
|
||||
fileName: libFile.path,
|
||||
includeReason: includeReason,
|
||||
}, libFile)
|
||||
} else {
|
||||
t.processingDiagnostics = append(t.processingDiagnostics, &processingDiagnostic{
|
||||
kind: processingDiagnosticKindUnknownReference,
|
||||
data: includeReason,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loader.resolveImportsAndModuleAugmentations(t)
|
||||
}
|
||||
|
||||
func (t *parseTask) redirect(loader *fileLoader, fileName string) {
|
||||
t.redirectedParseTask = &parseTask{
|
||||
normalizedFilePath: tspath.NormalizePath(fileName),
|
||||
libFile: t.libFile,
|
||||
includeReason: t.includeReason,
|
||||
}
|
||||
// increaseDepth and elideOnDepth are not copied to redirects, otherwise their depth would be double counted.
|
||||
t.subTasks = []*parseTask{t.redirectedParseTask}
|
||||
}
|
||||
|
||||
func (t *parseTask) loadAutomaticTypeDirectives(loader *fileLoader) {
|
||||
if loader.opts.Tracing != nil {
|
||||
defer loader.opts.Tracing.Push(tracing.PhaseProgram, "processTypeReferences", nil, false)()
|
||||
}
|
||||
toParseTypeRefs, typeResolutionsInFile, typeResolutionsTrace, pDiagnostics := loader.resolveAutomaticTypeDirectives(t.normalizedFilePath)
|
||||
t.typeResolutionsInFile = typeResolutionsInFile
|
||||
t.typeResolutionsTrace = typeResolutionsTrace
|
||||
t.processingDiagnostics = append(t.processingDiagnostics, pDiagnostics...)
|
||||
for _, typeResolution := range toParseTypeRefs {
|
||||
t.addSubTask(typeResolution, nil)
|
||||
}
|
||||
}
|
||||
|
||||
type resolvedRef struct {
|
||||
fileName string
|
||||
increaseDepth bool
|
||||
elideOnDepth bool
|
||||
includeReason *FileIncludeReason
|
||||
packageId module.PackageId
|
||||
}
|
||||
|
||||
func (t *parseTask) addSubTask(ref resolvedRef, libFile *LibFile) {
|
||||
normalizedFilePath := tspath.NormalizePath(ref.fileName)
|
||||
subTask := &parseTask{
|
||||
normalizedFilePath: normalizedFilePath,
|
||||
libFile: libFile,
|
||||
increaseDepth: ref.increaseDepth,
|
||||
elideOnDepth: ref.elideOnDepth,
|
||||
includeReason: ref.includeReason,
|
||||
packageId: ref.packageId,
|
||||
}
|
||||
t.subTasks = append(t.subTasks, subTask)
|
||||
}
|
||||
|
||||
type filesParser struct {
|
||||
wg core.WorkGroup
|
||||
taskDataByPath collections.SyncMap[tspath.Path, *parseTaskData]
|
||||
maxDepth int
|
||||
}
|
||||
|
||||
var parseTaskDataPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &parseTaskData{
|
||||
tasks: make(map[string]*parseTask, 1),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func getParseTaskData(task *parseTask) *parseTaskData {
|
||||
td := parseTaskDataPool.Get().(*parseTaskData)
|
||||
td.tasks[task.normalizedFilePath] = task
|
||||
td.lowestDepth = math.MaxInt
|
||||
return td
|
||||
}
|
||||
|
||||
func putParseTaskData(td *parseTaskData) {
|
||||
clear(td.tasks)
|
||||
parseTaskDataPool.Put(td)
|
||||
}
|
||||
|
||||
type parseTaskData struct {
|
||||
// map of tasks by file casing
|
||||
tasks map[string]*parseTask
|
||||
mu sync.Mutex
|
||||
lowestDepth int
|
||||
startedSubTasks bool
|
||||
packageId module.PackageId
|
||||
}
|
||||
|
||||
func (w *filesParser) parse(loader *fileLoader, tasks []*parseTask) {
|
||||
w.start(loader, tasks, 0)
|
||||
w.wg.RunAndWait()
|
||||
}
|
||||
|
||||
func (w *filesParser) start(loader *fileLoader, tasks []*parseTask, depth int) {
|
||||
for i, task := range tasks {
|
||||
task.path = loader.toPath(task.normalizedFilePath)
|
||||
candidate := getParseTaskData(task)
|
||||
data, loaded := w.taskDataByPath.LoadOrStore(task.path, candidate)
|
||||
if loaded {
|
||||
putParseTaskData(candidate)
|
||||
}
|
||||
|
||||
w.wg.Queue(func() {
|
||||
data.mu.Lock()
|
||||
defer data.mu.Unlock()
|
||||
|
||||
startSubtasks := false
|
||||
if loaded {
|
||||
if existingTask, ok := data.tasks[task.normalizedFilePath]; ok {
|
||||
tasks[i].loadedTask = existingTask
|
||||
} else {
|
||||
data.tasks[task.normalizedFilePath] = task
|
||||
// This is new task for file name - so load subtasks if there was loading for any other casing
|
||||
startSubtasks = data.startedSubTasks
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate packageId to data if we have one and data doesn't yet
|
||||
if data.packageId.Name == "" && task.packageId.Name != "" {
|
||||
data.packageId = task.packageId
|
||||
}
|
||||
|
||||
currentDepth := core.IfElse(task.increaseDepth, depth+1, depth)
|
||||
if currentDepth < data.lowestDepth {
|
||||
// If we're seeing this task at a lower depth than before,
|
||||
// reprocess its subtasks to ensure they are loaded.
|
||||
data.lowestDepth = currentDepth
|
||||
startSubtasks = true
|
||||
data.startedSubTasks = true
|
||||
}
|
||||
|
||||
if task.elideOnDepth && currentDepth > w.maxDepth {
|
||||
return
|
||||
}
|
||||
|
||||
for _, taskByFileName := range data.tasks {
|
||||
loadSubTasks := startSubtasks
|
||||
if !taskByFileName.loaded {
|
||||
taskByFileName.load(loader)
|
||||
if taskByFileName.redirectedParseTask != nil {
|
||||
// Always load redirected task
|
||||
loadSubTasks = true
|
||||
data.startedSubTasks = true
|
||||
}
|
||||
}
|
||||
if !taskByFileName.startedSubTasks && loadSubTasks {
|
||||
taskByFileName.startedSubTasks = true
|
||||
w.start(loader, taskByFileName.subTasks, data.lowestDepth)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *filesParser) getProcessedFiles(loader *fileLoader) processedFiles {
|
||||
totalFileCount := int(loader.totalFileCount.Load())
|
||||
libFileCount := int(loader.libFileCount.Load())
|
||||
|
||||
var missingFiles []string
|
||||
var duplicateSourceFiles []*DuplicateSourceFile
|
||||
files := make([]*ast.SourceFile, 0, totalFileCount-libFileCount)
|
||||
libFiles := make([]*ast.SourceFile, 0, totalFileCount) // totalFileCount here since we append files to it later to construct the final list
|
||||
|
||||
filesByPath := make(map[tspath.Path]*ast.SourceFile, totalFileCount)
|
||||
// stores 'filename -> file association' ignoring case
|
||||
// used to track cases when two file names differ only in casing
|
||||
var tasksSeenByNameIgnoreCase map[string]*parseTask
|
||||
if loader.comparePathsOptions.UseCaseSensitiveFileNames {
|
||||
tasksSeenByNameIgnoreCase = make(map[string]*parseTask, totalFileCount)
|
||||
}
|
||||
|
||||
includeProcessor := &includeProcessor{
|
||||
fileIncludeReasons: make(map[tspath.Path][]*FileIncludeReason, totalFileCount),
|
||||
}
|
||||
var outputFileToProjectReferenceSource map[tspath.Path]string
|
||||
if !loader.opts.canUseProjectReferenceSource() {
|
||||
outputFileToProjectReferenceSource = make(map[tspath.Path]string, totalFileCount)
|
||||
}
|
||||
resolvedModules := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule], totalFileCount+1)
|
||||
typeResolutionsInFile := make(map[tspath.Path]module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], totalFileCount)
|
||||
sourceFileMetaDatas := make(map[tspath.Path]ast.SourceFileMetaData, totalFileCount)
|
||||
var jsxRuntimeImportSpecifiers map[tspath.Path]*jsxRuntimeImportSpecifier
|
||||
var importHelpersImportSpecifiers map[tspath.Path]*ast.StringLiteralNode
|
||||
var sourceFilesFoundSearchingNodeModules collections.Set[tspath.Path]
|
||||
libFilesMap := make(map[tspath.Path]*LibFile, libFileCount)
|
||||
|
||||
var redirectTargetsMap map[tspath.Path][]string
|
||||
var redirectFilesByPath map[tspath.Path]*redirectsFile
|
||||
var packageIdToSourceFile map[module.PackageId]*ast.SourceFile
|
||||
if !loader.opts.Config.CompilerOptions().DeduplicatePackages.IsFalse() {
|
||||
redirectTargetsMap = make(map[tspath.Path][]string)
|
||||
packageIdToSourceFile = make(map[module.PackageId]*ast.SourceFile)
|
||||
}
|
||||
|
||||
var collectFiles func(tasks []*parseTask, seen map[*parseTaskData]string)
|
||||
// recordedDuplicates tracks, per task data, the set of file-name casings that
|
||||
// have already been recorded in duplicateSourceFiles. A file that is reached
|
||||
// from multiple import sites is walked once per site, but each distinct casing
|
||||
// is only parsed and acquired in the parse cache once. Recording the same casing
|
||||
// as a duplicate more than once would cause it to be released more times than it
|
||||
// was acquired when the snapshot is disposed, leaving a dangling cache entry that
|
||||
// panics the next time it is referenced.
|
||||
var recordedDuplicates map[*parseTaskData]*collections.Set[string]
|
||||
collectFiles = func(tasks []*parseTask, seen map[*parseTaskData]string) {
|
||||
for _, task := range tasks {
|
||||
includeReason := task.includeReason
|
||||
// Exclude automatic type directive tasks from include reason processing,
|
||||
// as these are internal implementation details and should not contribute
|
||||
// to the reasons for including files.
|
||||
if task.redirectedParseTask == nil && !task.isForAutomaticTypeDirective {
|
||||
if task.loadedTask != nil {
|
||||
task = task.loadedTask
|
||||
}
|
||||
w.addIncludeReason(includeProcessor, task, includeReason)
|
||||
}
|
||||
data, _ := w.taskDataByPath.Load(task.path)
|
||||
if !task.loaded {
|
||||
continue
|
||||
}
|
||||
|
||||
// ensure we only walk each task once
|
||||
if checkedName, ok := seen[data]; ok {
|
||||
if task.file != nil && checkedName != task.normalizedFilePath {
|
||||
if recordedDuplicates == nil {
|
||||
recordedDuplicates = make(map[*parseTaskData]*collections.Set[string])
|
||||
}
|
||||
dups := recordedDuplicates[data]
|
||||
if dups == nil {
|
||||
dups = &collections.Set[string]{}
|
||||
recordedDuplicates[data] = dups
|
||||
}
|
||||
if dups.AddIfAbsent(task.normalizedFilePath) {
|
||||
duplicateSourceFiles = append(duplicateSourceFiles, &DuplicateSourceFile{
|
||||
ParseOptions: task.file.ParseOptions(),
|
||||
Hash: task.file.Hash,
|
||||
ScriptKind: task.file.ScriptKind,
|
||||
})
|
||||
}
|
||||
}
|
||||
if !loader.opts.Config.CompilerOptions().ForceConsistentCasingInFileNames.IsFalse() {
|
||||
// Check if it differs only in drive letters its ok to ignore that error:
|
||||
checkedAbsolutePath := tspath.GetNormalizedAbsolutePathWithoutRoot(checkedName, loader.comparePathsOptions.CurrentDirectory)
|
||||
inputAbsolutePath := tspath.GetNormalizedAbsolutePathWithoutRoot(task.normalizedFilePath, loader.comparePathsOptions.CurrentDirectory)
|
||||
if checkedAbsolutePath != inputAbsolutePath {
|
||||
includeProcessor.addProcessingDiagnosticsForFileCasing(task.path, checkedName, task.normalizedFilePath, includeReason)
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
seen[data] = task.normalizedFilePath
|
||||
}
|
||||
|
||||
if tasksSeenByNameIgnoreCase != nil {
|
||||
pathLowerCase := tspath.ToFileNameLowerCase(string(task.path))
|
||||
if taskByIgnoreCase, ok := tasksSeenByNameIgnoreCase[pathLowerCase]; ok {
|
||||
includeProcessor.addProcessingDiagnosticsForFileCasing(taskByIgnoreCase.path, taskByIgnoreCase.normalizedFilePath, task.normalizedFilePath, includeReason)
|
||||
} else {
|
||||
tasksSeenByNameIgnoreCase[pathLowerCase] = task
|
||||
}
|
||||
}
|
||||
|
||||
for _, trace := range task.typeResolutionsTrace {
|
||||
loader.opts.Host.Trace(trace.Message, trace.Args...)
|
||||
}
|
||||
for _, trace := range task.resolutionsTrace {
|
||||
loader.opts.Host.Trace(trace.Message, trace.Args...)
|
||||
}
|
||||
|
||||
file := task.file
|
||||
if packageIdToSourceFile != nil && data.packageId.Name != "" {
|
||||
if packageIdFile, exists := packageIdToSourceFile[data.packageId]; exists {
|
||||
if file != nil {
|
||||
// Package deduplication keeps the first package instance in the
|
||||
// program, but we still parsed this file and acquired it through
|
||||
// the host, so snapshot disposal must release that extra owner.
|
||||
duplicateSourceFiles = append(duplicateSourceFiles, &DuplicateSourceFile{
|
||||
ParseOptions: file.ParseOptions(),
|
||||
Hash: file.Hash,
|
||||
ScriptKind: file.ScriptKind,
|
||||
})
|
||||
}
|
||||
redirectTargetsMap[packageIdFile.Path()] = append(redirectTargetsMap[packageIdFile.Path()], task.normalizedFilePath)
|
||||
if redirectFilesByPath == nil {
|
||||
redirectFilesByPath = make(map[tspath.Path]*redirectsFile, totalFileCount)
|
||||
}
|
||||
redirectFilesByPath[task.path] = &redirectsFile{
|
||||
index: len(files) + len(redirectFilesByPath),
|
||||
fileName: task.normalizedFilePath,
|
||||
path: task.path,
|
||||
target: packageIdFile.Path(),
|
||||
}
|
||||
filesByPath[task.path] = packageIdFile
|
||||
if data.lowestDepth > 0 {
|
||||
sourceFilesFoundSearchingNodeModules.Add(task.path)
|
||||
}
|
||||
continue
|
||||
} else if file != nil {
|
||||
packageIdToSourceFile[data.packageId] = file
|
||||
}
|
||||
}
|
||||
|
||||
if subTasks := task.subTasks; len(subTasks) > 0 {
|
||||
collectFiles(subTasks, seen)
|
||||
}
|
||||
|
||||
// Exclude automatic type directive tasks from include reason processing,
|
||||
// as these are internal implementation details and should not contribute
|
||||
// to the reasons for including files.
|
||||
if task.redirectedParseTask != nil {
|
||||
if !loader.opts.canUseProjectReferenceSource() {
|
||||
outputFileToProjectReferenceSource[task.redirectedParseTask.path] = task.FileName()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if task.isForAutomaticTypeDirective {
|
||||
typeResolutionsInFile[task.path] = task.typeResolutionsInFile
|
||||
if len(task.processingDiagnostics) > 0 {
|
||||
includeProcessor.processingDiagnostics = append(includeProcessor.processingDiagnostics, task.processingDiagnostics...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
path := task.path
|
||||
|
||||
if len(task.processingDiagnostics) > 0 {
|
||||
includeProcessor.processingDiagnostics = append(includeProcessor.processingDiagnostics, task.processingDiagnostics...)
|
||||
}
|
||||
|
||||
if file == nil {
|
||||
missingFiles = append(missingFiles, task.normalizedFilePath)
|
||||
continue
|
||||
}
|
||||
|
||||
if task.libFile != nil {
|
||||
libFiles = append(libFiles, file)
|
||||
libFilesMap[path] = task.libFile
|
||||
} else {
|
||||
files = append(files, file)
|
||||
}
|
||||
filesByPath[path] = file
|
||||
resolvedModules[path] = task.resolutionsInFile
|
||||
typeResolutionsInFile[path] = task.typeResolutionsInFile
|
||||
sourceFileMetaDatas[path] = task.metadata
|
||||
|
||||
if task.jsxRuntimeImportSpecifier != nil {
|
||||
if jsxRuntimeImportSpecifiers == nil {
|
||||
jsxRuntimeImportSpecifiers = make(map[tspath.Path]*jsxRuntimeImportSpecifier, totalFileCount)
|
||||
}
|
||||
jsxRuntimeImportSpecifiers[path] = task.jsxRuntimeImportSpecifier
|
||||
}
|
||||
if task.importHelpersImportSpecifier != nil {
|
||||
if importHelpersImportSpecifiers == nil {
|
||||
importHelpersImportSpecifiers = make(map[tspath.Path]*ast.StringLiteralNode, totalFileCount)
|
||||
}
|
||||
importHelpersImportSpecifiers[path] = task.importHelpersImportSpecifier
|
||||
}
|
||||
if data.lowestDepth > 0 {
|
||||
sourceFilesFoundSearchingNodeModules.Add(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectFiles(loader.rootTasks, make(map[*parseTaskData]string, totalFileCount))
|
||||
loader.sortLibs(libFiles)
|
||||
|
||||
allFiles := append(libFiles, files...)
|
||||
for _, redirectFile := range redirectFilesByPath {
|
||||
redirectFile.index += len(libFiles)
|
||||
}
|
||||
|
||||
keys := slices.Collect(loader.pathForLibFileResolutions.Keys())
|
||||
slices.Sort(keys)
|
||||
for _, key := range keys {
|
||||
value, _ := loader.pathForLibFileResolutions.Load(key)
|
||||
resolvedModules[key] = module.ModeAwareCache[*module.ResolvedModule]{
|
||||
module.ModeAwareCacheKey{Name: value.libraryName, Mode: core.ModuleKindCommonJS}: value.resolution,
|
||||
}
|
||||
for _, trace := range value.trace {
|
||||
loader.opts.Host.Trace(trace.Message, trace.Args...)
|
||||
}
|
||||
}
|
||||
|
||||
return processedFiles{
|
||||
finishedProcessing: true,
|
||||
resolver: loader.resolver,
|
||||
files: allFiles,
|
||||
duplicateSourceFiles: duplicateSourceFiles,
|
||||
filesByPath: filesByPath,
|
||||
projectReferenceFileMapper: loader.projectReferenceFileMapper,
|
||||
resolvedModules: resolvedModules,
|
||||
typeResolutionsInFile: typeResolutionsInFile,
|
||||
sourceFileMetaDatas: sourceFileMetaDatas,
|
||||
jsxRuntimeImportSpecifiers: jsxRuntimeImportSpecifiers,
|
||||
importHelpersImportSpecifiers: importHelpersImportSpecifiers,
|
||||
sourceFilesFoundSearchingNodeModules: sourceFilesFoundSearchingNodeModules,
|
||||
libFiles: libFilesMap,
|
||||
missingFiles: missingFiles,
|
||||
includeProcessor: includeProcessor,
|
||||
outputFileToProjectReferenceSource: outputFileToProjectReferenceSource,
|
||||
redirectTargetsMap: redirectTargetsMap,
|
||||
redirectFilesByPath: redirectFilesByPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *filesParser) addIncludeReason(includeProcessor *includeProcessor, task *parseTask, reason *FileIncludeReason) {
|
||||
if task.redirectedParseTask != nil {
|
||||
w.addIncludeReason(includeProcessor, task.redirectedParseTask, reason)
|
||||
} else if task.loaded {
|
||||
if existing, ok := includeProcessor.fileIncludeReasons[task.path]; ok {
|
||||
includeProcessor.fileIncludeReasons[task.path] = append(existing, reason)
|
||||
} else {
|
||||
includeProcessor.fileIncludeReasons[task.path] = []*FileIncludeReason{reason}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
tools/tsgo/internal/compiler/host.go
Normal file
89
tools/tsgo/internal/compiler/host.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/parser"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/cachedvfs"
|
||||
)
|
||||
|
||||
type CompilerHost interface {
|
||||
FS() vfs.FS
|
||||
DefaultLibraryPath() string
|
||||
GetCurrentDirectory() string
|
||||
Trace(msg *diagnostics.Message, args ...any)
|
||||
GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile
|
||||
GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine
|
||||
}
|
||||
|
||||
var _ CompilerHost = (*compilerHost)(nil)
|
||||
|
||||
type compilerHost struct {
|
||||
currentDirectory string
|
||||
fs vfs.FS
|
||||
defaultLibraryPath string
|
||||
extendedConfigCache tsoptions.ExtendedConfigCache
|
||||
trace func(msg *diagnostics.Message, args ...any)
|
||||
}
|
||||
|
||||
func NewCachedFSCompilerHost(
|
||||
currentDirectory string,
|
||||
fs vfs.FS,
|
||||
defaultLibraryPath string,
|
||||
extendedConfigCache tsoptions.ExtendedConfigCache,
|
||||
trace func(msg *diagnostics.Message, args ...any),
|
||||
) CompilerHost {
|
||||
return NewCompilerHost(currentDirectory, cachedvfs.From(fs), defaultLibraryPath, extendedConfigCache, trace)
|
||||
}
|
||||
|
||||
func NewCompilerHost(
|
||||
currentDirectory string,
|
||||
fs vfs.FS,
|
||||
defaultLibraryPath string,
|
||||
extendedConfigCache tsoptions.ExtendedConfigCache,
|
||||
trace func(msg *diagnostics.Message, args ...any),
|
||||
) CompilerHost {
|
||||
if trace == nil {
|
||||
trace = func(msg *diagnostics.Message, args ...any) {}
|
||||
}
|
||||
return &compilerHost{
|
||||
currentDirectory: currentDirectory,
|
||||
fs: fs,
|
||||
defaultLibraryPath: defaultLibraryPath,
|
||||
extendedConfigCache: extendedConfigCache,
|
||||
trace: trace,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *compilerHost) FS() vfs.FS {
|
||||
return h.fs
|
||||
}
|
||||
|
||||
func (h *compilerHost) DefaultLibraryPath() string {
|
||||
return h.defaultLibraryPath
|
||||
}
|
||||
|
||||
func (h *compilerHost) GetCurrentDirectory() string {
|
||||
return h.currentDirectory
|
||||
}
|
||||
|
||||
func (h *compilerHost) Trace(msg *diagnostics.Message, args ...any) {
|
||||
h.trace(msg, args...)
|
||||
}
|
||||
|
||||
func (h *compilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile {
|
||||
text, ok := h.FS().ReadFile(opts.FileName)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return parser.ParseSourceFile(opts, text, core.GetScriptKindFromFileName(opts.FileName))
|
||||
}
|
||||
|
||||
func (h *compilerHost) GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine {
|
||||
commandLine, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, nil, nil /*optionsRaw*/, h, h.extendedConfigCache)
|
||||
return commandLine
|
||||
}
|
||||
184
tools/tsgo/internal/compiler/includeprocessor.go
Normal file
184
tools/tsgo/internal/compiler/includeprocessor.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type includeProcessor struct {
|
||||
fileIncludeReasons map[tspath.Path][]*FileIncludeReason
|
||||
processingDiagnostics []*processingDiagnostic
|
||||
|
||||
reasonToReferenceLocation collections.SyncMap[*FileIncludeReason, *referenceFileLocation]
|
||||
includeReasonToRelatedInfo collections.SyncMap[*FileIncludeReason, *ast.Diagnostic]
|
||||
redirectAndFileFormat collections.SyncMap[tspath.Path, []*ast.Diagnostic]
|
||||
computedDiagnostics *ast.DiagnosticsCollection
|
||||
computedDiagnosticsOnce sync.Once
|
||||
compilerOptionsSyntax *ast.ObjectLiteralExpression
|
||||
compilerOptionsSyntaxOnce sync.Once
|
||||
}
|
||||
|
||||
func updateFileIncludeProcessor(p *Program) {
|
||||
p.includeProcessor = &includeProcessor{
|
||||
fileIncludeReasons: p.includeProcessor.fileIncludeReasons,
|
||||
processingDiagnostics: p.includeProcessor.processingDiagnostics,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *includeProcessor) getDiagnostics(p *Program) *ast.DiagnosticsCollection {
|
||||
i.computedDiagnosticsOnce.Do(func() {
|
||||
i.computedDiagnostics = &ast.DiagnosticsCollection{}
|
||||
for _, d := range i.processingDiagnostics {
|
||||
i.computedDiagnostics.Add(d.toDiagnostic(p))
|
||||
}
|
||||
for _, resolutions := range p.resolvedModules {
|
||||
for _, resolvedModule := range resolutions {
|
||||
for _, diag := range resolvedModule.ResolutionDiagnostics {
|
||||
i.computedDiagnostics.Add(diag)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, typeResolutions := range p.typeResolutionsInFile {
|
||||
for _, resolvedTypeRef := range typeResolutions {
|
||||
for _, diag := range resolvedTypeRef.ResolutionDiagnostics {
|
||||
i.computedDiagnostics.Add(diag)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return i.computedDiagnostics
|
||||
}
|
||||
|
||||
func (i *includeProcessor) addProcessingDiagnostic(d ...*processingDiagnostic) {
|
||||
i.processingDiagnostics = append(i.processingDiagnostics, d...)
|
||||
}
|
||||
|
||||
func (i *includeProcessor) addProcessingDiagnosticsForFileCasing(file tspath.Path, existingCasing string, currentCasing string, reason *FileIncludeReason) {
|
||||
if !reason.isReferencedFile() && slices.ContainsFunc(i.fileIncludeReasons[file], func(r *FileIncludeReason) bool {
|
||||
return r.isReferencedFile()
|
||||
}) {
|
||||
i.addProcessingDiagnostic(&processingDiagnostic{
|
||||
kind: processingDiagnosticKindExplainingFileInclude,
|
||||
data: &includeExplainingDiagnostic{
|
||||
file: file,
|
||||
diagnosticReason: reason,
|
||||
message: diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing,
|
||||
args: []any{existingCasing, currentCasing},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
i.addProcessingDiagnostic(&processingDiagnostic{
|
||||
kind: processingDiagnosticKindExplainingFileInclude,
|
||||
data: &includeExplainingDiagnostic{
|
||||
file: file,
|
||||
diagnosticReason: reason,
|
||||
message: diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing,
|
||||
args: []any{currentCasing, existingCasing},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (i *includeProcessor) getReferenceLocation(r *FileIncludeReason, program *Program) *referenceFileLocation {
|
||||
if existing, ok := i.reasonToReferenceLocation.Load(r); ok {
|
||||
return existing
|
||||
}
|
||||
|
||||
loc, _ := i.reasonToReferenceLocation.LoadOrStore(r, r.getReferencedLocation(program))
|
||||
return loc
|
||||
}
|
||||
|
||||
func (i *includeProcessor) getCompilerOptionsObjectLiteralSyntax(program *Program) *ast.ObjectLiteralExpression {
|
||||
i.compilerOptionsSyntaxOnce.Do(func() {
|
||||
configFile := program.opts.Config.ConfigFile
|
||||
if configFile != nil {
|
||||
if compilerOptionsProperty := tsoptions.ForEachTsConfigPropArray(configFile.SourceFile, "compilerOptions", core.Identity); compilerOptionsProperty != nil &&
|
||||
compilerOptionsProperty.Initializer != nil &&
|
||||
ast.IsObjectLiteralExpression(compilerOptionsProperty.Initializer) {
|
||||
i.compilerOptionsSyntax = compilerOptionsProperty.Initializer.AsObjectLiteralExpression()
|
||||
}
|
||||
} else {
|
||||
i.compilerOptionsSyntax = nil
|
||||
}
|
||||
})
|
||||
return i.compilerOptionsSyntax
|
||||
}
|
||||
|
||||
func (i *includeProcessor) getRelatedInfo(r *FileIncludeReason, program *Program) *ast.Diagnostic {
|
||||
if existing, ok := i.includeReasonToRelatedInfo.Load(r); ok {
|
||||
return existing
|
||||
}
|
||||
|
||||
relatedInfo, _ := i.includeReasonToRelatedInfo.LoadOrStore(r, r.toRelatedInfo(program))
|
||||
return relatedInfo
|
||||
}
|
||||
|
||||
func (i *includeProcessor) explainRedirectAndImpliedFormat(
|
||||
program *Program,
|
||||
filePath tspath.Path,
|
||||
toFileName func(fileName string) string,
|
||||
) []*ast.Diagnostic {
|
||||
if existing, ok := i.redirectAndFileFormat.Load(filePath); ok {
|
||||
return existing
|
||||
}
|
||||
var file ast.HasFileName
|
||||
var sourceFile *ast.SourceFile
|
||||
redirectsFile := program.redirectFilesByPath[filePath]
|
||||
if redirectsFile != nil {
|
||||
file = redirectsFile
|
||||
} else {
|
||||
sourceFile = program.GetSourceFileByPath(filePath)
|
||||
if sourceFile == nil {
|
||||
return nil
|
||||
}
|
||||
file = sourceFile
|
||||
}
|
||||
var result []*ast.Diagnostic
|
||||
if source := program.GetSourceOfProjectReferenceIfOutputIncluded(file); source != file.FileName() {
|
||||
result = append(result, ast.NewCompilerDiagnostic(
|
||||
diagnostics.File_is_output_of_project_reference_source_0,
|
||||
toFileName(source),
|
||||
))
|
||||
}
|
||||
|
||||
if redirectsFile != nil {
|
||||
targetFile := program.GetSourceFileByPath(redirectsFile.target)
|
||||
result = append(result, ast.NewCompilerDiagnostic(
|
||||
diagnostics.File_redirects_to_file_0,
|
||||
toFileName(targetFile.FileName()),
|
||||
))
|
||||
}
|
||||
|
||||
if sourceFile != nil && ast.IsExternalOrCommonJSModule(sourceFile) {
|
||||
metaData := program.GetSourceFileMetaData(file.Path())
|
||||
switch program.GetImpliedNodeFormatForEmit(file) {
|
||||
case core.ModuleKindESNext:
|
||||
if metaData.PackageJsonType == "module" {
|
||||
result = append(result, ast.NewCompilerDiagnostic(
|
||||
diagnostics.File_is_ECMAScript_module_because_0_has_field_type_with_value_module,
|
||||
toFileName(metaData.PackageJsonDirectory+"/package.json"),
|
||||
))
|
||||
}
|
||||
case core.ModuleKindCommonJS:
|
||||
if metaData.PackageJsonType != "" {
|
||||
result = append(result, ast.NewCompilerDiagnostic(diagnostics.File_is_CommonJS_module_because_0_has_field_type_whose_value_is_not_module, toFileName(metaData.PackageJsonDirectory+"/package.json")))
|
||||
} else if metaData.PackageJsonDirectory != "" {
|
||||
if metaData.PackageJsonType == "" {
|
||||
result = append(result, ast.NewCompilerDiagnostic(diagnostics.File_is_CommonJS_module_because_0_does_not_have_field_type, toFileName(metaData.PackageJsonDirectory+"/package.json")))
|
||||
}
|
||||
} else {
|
||||
result = append(result, ast.NewCompilerDiagnostic(diagnostics.File_is_CommonJS_module_because_package_json_was_not_found))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result, _ = i.redirectAndFileFormat.LoadOrStore(filePath, result)
|
||||
return result
|
||||
}
|
||||
2
tools/tsgo/internal/compiler/pkg.go
Normal file
2
tools/tsgo/internal/compiler/pkg.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package compiler implements the TypeScript compiler.
|
||||
package compiler
|
||||
135
tools/tsgo/internal/compiler/processingDiagnostic.go
Normal file
135
tools/tsgo/internal/compiler/processingDiagnostic.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type processingDiagnosticKind int
|
||||
|
||||
const (
|
||||
processingDiagnosticKindUnknownReference processingDiagnosticKind = iota
|
||||
processingDiagnosticKindExplainingFileInclude
|
||||
)
|
||||
|
||||
type processingDiagnostic struct {
|
||||
kind processingDiagnosticKind
|
||||
data any
|
||||
}
|
||||
|
||||
func (d *processingDiagnostic) asFileIncludeReason() *FileIncludeReason {
|
||||
return d.data.(*FileIncludeReason)
|
||||
}
|
||||
|
||||
type includeExplainingDiagnostic struct {
|
||||
file tspath.Path
|
||||
diagnosticReason *FileIncludeReason
|
||||
message *diagnostics.Message
|
||||
args []any
|
||||
}
|
||||
|
||||
func (d *processingDiagnostic) asIncludeExplainingDiagnostic() *includeExplainingDiagnostic {
|
||||
return d.data.(*includeExplainingDiagnostic)
|
||||
}
|
||||
|
||||
func (d *processingDiagnostic) toDiagnostic(program *Program) *ast.Diagnostic {
|
||||
switch d.kind {
|
||||
case processingDiagnosticKindUnknownReference:
|
||||
ref := d.asFileIncludeReason()
|
||||
loc := ref.getReferencedLocation(program)
|
||||
switch ref.kind {
|
||||
case fileIncludeKindTypeReferenceDirective:
|
||||
return loc.diagnosticAt(diagnostics.Cannot_find_type_definition_file_for_0, loc.ref.FileName)
|
||||
case fileIncludeKindLibReferenceDirective:
|
||||
libName := tspath.ToFileNameLowerCase(loc.ref.FileName)
|
||||
unqualifiedLibName := strings.TrimSuffix(strings.TrimPrefix(libName, "lib."), ".d.ts")
|
||||
suggestion := core.GetSpellingSuggestionForStrings(unqualifiedLibName, slices.Values(tsoptions.Libs))
|
||||
return loc.diagnosticAt(core.IfElse(
|
||||
suggestion != "",
|
||||
diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1,
|
||||
diagnostics.Cannot_find_lib_definition_for_0,
|
||||
), libName, suggestion)
|
||||
default:
|
||||
panic("unknown include kind")
|
||||
}
|
||||
case processingDiagnosticKindExplainingFileInclude:
|
||||
return d.createDiagnosticExplainingFile(program)
|
||||
default:
|
||||
panic("unknown processingDiagnosticKind")
|
||||
}
|
||||
}
|
||||
|
||||
func (d *processingDiagnostic) createDiagnosticExplainingFile(program *Program) *ast.Diagnostic {
|
||||
diag := d.asIncludeExplainingDiagnostic()
|
||||
var includeDetails []*ast.Diagnostic
|
||||
var relatedInfo []*ast.Diagnostic
|
||||
var redirectInfo []*ast.Diagnostic
|
||||
var preferredLocation *FileIncludeReason
|
||||
var seenReasons collections.Set[*FileIncludeReason]
|
||||
if diag.diagnosticReason.isReferencedFile() && !program.includeProcessor.getReferenceLocation(diag.diagnosticReason, program).isSynthetic {
|
||||
preferredLocation = diag.diagnosticReason
|
||||
}
|
||||
|
||||
processRelatedInfo := func(includeReason *FileIncludeReason) {
|
||||
if preferredLocation == nil && includeReason.isReferencedFile() && !program.includeProcessor.getReferenceLocation(includeReason, program).isSynthetic {
|
||||
preferredLocation = includeReason
|
||||
} else if preferredLocation != includeReason {
|
||||
info := program.includeProcessor.getRelatedInfo(includeReason, program)
|
||||
if info != nil {
|
||||
relatedInfo = append(relatedInfo, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
processInclude := func(includeReason *FileIncludeReason) {
|
||||
if !seenReasons.AddIfAbsent(includeReason) {
|
||||
return
|
||||
}
|
||||
includeDetails = append(includeDetails, includeReason.toDiagnostic(program, false))
|
||||
processRelatedInfo(includeReason)
|
||||
}
|
||||
|
||||
// !!! todo sheetal caching
|
||||
|
||||
if diag.file != "" {
|
||||
reasons := program.includeProcessor.fileIncludeReasons[diag.file]
|
||||
includeDetails = make([]*ast.Diagnostic, 0, len(reasons))
|
||||
for _, reason := range reasons {
|
||||
processInclude(reason)
|
||||
}
|
||||
redirectInfo = program.includeProcessor.explainRedirectAndImpliedFormat(program, diag.file, func(fileName string) string { return fileName })
|
||||
}
|
||||
if diag.diagnosticReason != nil {
|
||||
processInclude(diag.diagnosticReason)
|
||||
}
|
||||
var chain []*ast.Diagnostic
|
||||
if includeDetails != nil && (preferredLocation == nil || seenReasons.Len() != 1) {
|
||||
fileReason := ast.NewCompilerDiagnostic(diagnostics.The_file_is_in_the_program_because_Colon)
|
||||
fileReason.SetMessageChain(includeDetails)
|
||||
chain = []*ast.Diagnostic{fileReason}
|
||||
}
|
||||
if redirectInfo != nil {
|
||||
chain = append(chain, redirectInfo...)
|
||||
}
|
||||
|
||||
var result *ast.Diagnostic
|
||||
if preferredLocation != nil {
|
||||
result = program.includeProcessor.getReferenceLocation(preferredLocation, program).diagnosticAt(diag.message, diag.args...)
|
||||
}
|
||||
if result == nil {
|
||||
result = ast.NewCompilerDiagnostic(diag.message, diag.args...)
|
||||
}
|
||||
if chain != nil {
|
||||
result.SetMessageChain(chain)
|
||||
}
|
||||
if relatedInfo != nil {
|
||||
result.SetRelatedInfo(relatedInfo)
|
||||
}
|
||||
return result
|
||||
}
|
||||
2195
tools/tsgo/internal/compiler/program.go
Normal file
2195
tools/tsgo/internal/compiler/program.go
Normal file
File diff suppressed because it is too large
Load Diff
367
tools/tsgo/internal/compiler/program_test.go
Normal file
367
tools/tsgo/internal/compiler/program_test.go
Normal file
@@ -0,0 +1,367 @@
|
||||
package compiler_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/bundled"
|
||||
"github.com/microsoft/typescript-go/internal/compiler"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/repo"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
type testFile struct {
|
||||
fileName string
|
||||
contents string
|
||||
}
|
||||
|
||||
type programTest struct {
|
||||
testName string
|
||||
files []testFile
|
||||
expectedFiles []string
|
||||
target core.ScriptTarget
|
||||
}
|
||||
|
||||
var esnextLibs = []string{
|
||||
"lib.es5.d.ts",
|
||||
"lib.es2015.d.ts",
|
||||
"lib.es2016.d.ts",
|
||||
"lib.es2017.d.ts",
|
||||
"lib.es2018.d.ts",
|
||||
"lib.es2019.d.ts",
|
||||
"lib.es2020.d.ts",
|
||||
"lib.es2021.d.ts",
|
||||
"lib.es2022.d.ts",
|
||||
"lib.es2023.d.ts",
|
||||
"lib.es2024.d.ts",
|
||||
"lib.es2025.d.ts",
|
||||
"lib.esnext.d.ts",
|
||||
"lib.dom.d.ts",
|
||||
"lib.dom.iterable.d.ts",
|
||||
"lib.dom.asynciterable.d.ts",
|
||||
"lib.webworker.importscripts.d.ts",
|
||||
"lib.scripthost.d.ts",
|
||||
"lib.es2015.core.d.ts",
|
||||
"lib.es2015.collection.d.ts",
|
||||
"lib.es2015.generator.d.ts",
|
||||
"lib.es2015.iterable.d.ts",
|
||||
"lib.es2015.promise.d.ts",
|
||||
"lib.es2015.proxy.d.ts",
|
||||
"lib.es2015.reflect.d.ts",
|
||||
"lib.es2015.symbol.d.ts",
|
||||
"lib.es2015.symbol.wellknown.d.ts",
|
||||
"lib.es2016.array.include.d.ts",
|
||||
"lib.es2016.intl.d.ts",
|
||||
"lib.es2017.arraybuffer.d.ts",
|
||||
"lib.es2017.date.d.ts",
|
||||
"lib.es2017.object.d.ts",
|
||||
"lib.es2017.sharedmemory.d.ts",
|
||||
"lib.es2017.string.d.ts",
|
||||
"lib.es2017.intl.d.ts",
|
||||
"lib.es2017.typedarrays.d.ts",
|
||||
"lib.es2018.asyncgenerator.d.ts",
|
||||
"lib.es2018.asynciterable.d.ts",
|
||||
"lib.es2018.intl.d.ts",
|
||||
"lib.es2018.promise.d.ts",
|
||||
"lib.es2018.regexp.d.ts",
|
||||
"lib.es2019.array.d.ts",
|
||||
"lib.es2019.object.d.ts",
|
||||
"lib.es2019.string.d.ts",
|
||||
"lib.es2019.symbol.d.ts",
|
||||
"lib.es2019.intl.d.ts",
|
||||
"lib.es2020.bigint.d.ts",
|
||||
"lib.es2020.date.d.ts",
|
||||
"lib.es2020.promise.d.ts",
|
||||
"lib.es2020.sharedmemory.d.ts",
|
||||
"lib.es2020.string.d.ts",
|
||||
"lib.es2020.symbol.wellknown.d.ts",
|
||||
"lib.es2020.intl.d.ts",
|
||||
"lib.es2020.number.d.ts",
|
||||
"lib.es2021.promise.d.ts",
|
||||
"lib.es2021.string.d.ts",
|
||||
"lib.es2021.weakref.d.ts",
|
||||
"lib.es2021.intl.d.ts",
|
||||
"lib.es2022.array.d.ts",
|
||||
"lib.es2022.error.d.ts",
|
||||
"lib.es2022.intl.d.ts",
|
||||
"lib.es2022.object.d.ts",
|
||||
"lib.es2022.string.d.ts",
|
||||
"lib.es2022.regexp.d.ts",
|
||||
"lib.es2023.array.d.ts",
|
||||
"lib.es2023.collection.d.ts",
|
||||
"lib.es2023.intl.d.ts",
|
||||
"lib.es2024.arraybuffer.d.ts",
|
||||
"lib.es2024.collection.d.ts",
|
||||
"lib.es2024.object.d.ts",
|
||||
"lib.es2024.promise.d.ts",
|
||||
"lib.es2024.regexp.d.ts",
|
||||
"lib.es2024.sharedmemory.d.ts",
|
||||
"lib.es2024.string.d.ts",
|
||||
"lib.es2025.collection.d.ts",
|
||||
"lib.es2025.float16.d.ts",
|
||||
"lib.es2025.intl.d.ts",
|
||||
"lib.es2025.iterator.d.ts",
|
||||
"lib.es2025.promise.d.ts",
|
||||
"lib.es2025.regexp.d.ts",
|
||||
"lib.esnext.array.d.ts",
|
||||
"lib.esnext.collection.d.ts",
|
||||
"lib.esnext.date.d.ts",
|
||||
"lib.esnext.decorators.d.ts",
|
||||
"lib.esnext.disposable.d.ts",
|
||||
"lib.esnext.error.d.ts",
|
||||
"lib.esnext.intl.d.ts",
|
||||
"lib.esnext.sharedmemory.d.ts",
|
||||
"lib.esnext.temporal.d.ts",
|
||||
"lib.esnext.typedarrays.d.ts",
|
||||
"lib.decorators.d.ts",
|
||||
"lib.decorators.legacy.d.ts",
|
||||
"lib.esnext.full.d.ts",
|
||||
}
|
||||
|
||||
var programTestCases = []programTest{
|
||||
{
|
||||
testName: "BasicFileOrdering",
|
||||
files: []testFile{
|
||||
{fileName: "c:/dev/src/index.ts", contents: "/// <reference path='c:/dev/src2/a/5.ts' />\n/// <reference path='c:/dev/src2/a/10.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/5.ts", contents: "/// <reference path='4.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/4.ts", contents: "/// <reference path='b/3.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/b/3.ts", contents: "/// <reference path='2.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/b/2.ts", contents: "/// <reference path='c/1.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/b/c/1.ts", contents: "console.log('hello');"},
|
||||
{fileName: "c:/dev/src2/a/10.ts", contents: "/// <reference path='b/c/d/9.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/9.ts", contents: "/// <reference path='e/8.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/8.ts", contents: "/// <reference path='7.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/7.ts", contents: "/// <reference path='f/6.ts' />"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/f/6.ts", contents: "console.log('world!');"},
|
||||
},
|
||||
expectedFiles: slices.Concat(esnextLibs,
|
||||
[]string{
|
||||
"c:/dev/src2/a/b/c/1.ts",
|
||||
"c:/dev/src2/a/b/2.ts",
|
||||
"c:/dev/src2/a/b/3.ts",
|
||||
"c:/dev/src2/a/4.ts",
|
||||
"c:/dev/src2/a/5.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/f/6.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/7.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/8.ts",
|
||||
"c:/dev/src2/a/b/c/d/9.ts",
|
||||
"c:/dev/src2/a/10.ts",
|
||||
"c:/dev/src/index.ts",
|
||||
}),
|
||||
target: core.ScriptTargetESNext,
|
||||
},
|
||||
{
|
||||
testName: "FileOrderingImports",
|
||||
files: []testFile{
|
||||
{fileName: "c:/dev/src/index.ts", contents: "import * as five from '../src2/a/5.ts';\nimport * as ten from '../src2/a/10.ts';"},
|
||||
{fileName: "c:/dev/src2/a/5.ts", contents: "import * as four from './4.ts';"},
|
||||
{fileName: "c:/dev/src2/a/4.ts", contents: "import * as three from './b/3.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/3.ts", contents: "import * as two from './2.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/2.ts", contents: "import * as one from './c/1.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/1.ts", contents: "console.log('hello');"},
|
||||
{fileName: "c:/dev/src2/a/10.ts", contents: "import * as nine from './b/c/d/9.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/9.ts", contents: "import * as eight from './e/8.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/8.ts", contents: "import * as seven from './7.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/7.ts", contents: "import * as six from './f/6.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/f/6.ts", contents: "console.log('world!');"},
|
||||
},
|
||||
expectedFiles: slices.Concat(esnextLibs,
|
||||
[]string{
|
||||
"c:/dev/src2/a/b/c/1.ts",
|
||||
"c:/dev/src2/a/b/2.ts",
|
||||
"c:/dev/src2/a/b/3.ts",
|
||||
"c:/dev/src2/a/4.ts",
|
||||
"c:/dev/src2/a/5.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/f/6.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/7.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/8.ts",
|
||||
"c:/dev/src2/a/b/c/d/9.ts",
|
||||
"c:/dev/src2/a/10.ts",
|
||||
"c:/dev/src/index.ts",
|
||||
}),
|
||||
target: core.ScriptTargetESNext,
|
||||
},
|
||||
{
|
||||
testName: "FileOrderingCycles",
|
||||
files: []testFile{
|
||||
{fileName: "c:/dev/src/index.ts", contents: "import * as five from '../src2/a/5.ts';\nimport * as ten from '../src2/a/10.ts';"},
|
||||
{fileName: "c:/dev/src2/a/5.ts", contents: "import * as four from './4.ts';"},
|
||||
{fileName: "c:/dev/src2/a/4.ts", contents: "import * as three from './b/3.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/3.ts", contents: "import * as two from './2.ts';\nimport * as cycle from 'c:/dev/src/index.ts'; "},
|
||||
{fileName: "c:/dev/src2/a/b/2.ts", contents: "import * as one from './c/1.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/1.ts", contents: "console.log('hello');"},
|
||||
{fileName: "c:/dev/src2/a/10.ts", contents: "import * as nine from './b/c/d/9.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/9.ts", contents: "import * as eight from './e/8.ts';\nimport * as cycle from 'c:/dev/src/index.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/8.ts", contents: "import * as seven from './7.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/7.ts", contents: "import * as six from './f/6.ts';"},
|
||||
{fileName: "c:/dev/src2/a/b/c/d/e/f/6.ts", contents: "console.log('world!');"},
|
||||
},
|
||||
expectedFiles: slices.Concat(esnextLibs,
|
||||
[]string{
|
||||
"c:/dev/src2/a/b/c/1.ts",
|
||||
"c:/dev/src2/a/b/2.ts",
|
||||
"c:/dev/src2/a/b/3.ts",
|
||||
"c:/dev/src2/a/4.ts",
|
||||
"c:/dev/src2/a/5.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/f/6.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/7.ts",
|
||||
"c:/dev/src2/a/b/c/d/e/8.ts",
|
||||
"c:/dev/src2/a/b/c/d/9.ts",
|
||||
"c:/dev/src2/a/10.ts",
|
||||
"c:/dev/src/index.ts",
|
||||
}),
|
||||
target: core.ScriptTargetESNext,
|
||||
},
|
||||
}
|
||||
|
||||
func TestProgram(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if !bundled.Embedded {
|
||||
// Without embedding, we'd need to read all of the lib files out from disk into the MapFS.
|
||||
// Just skip this for now.
|
||||
t.Skip("bundled files are not embedded")
|
||||
}
|
||||
|
||||
for _, testCase := range programTestCases {
|
||||
t.Run(testCase.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
libPrefix := bundled.LibPath() + "/"
|
||||
fs := vfstest.FromMap[any](nil, false /*useCaseSensitiveFileNames*/)
|
||||
fs = bundled.WrapFS(fs)
|
||||
|
||||
for _, testFile := range testCase.files {
|
||||
_ = fs.WriteFile(testFile.fileName, testFile.contents)
|
||||
}
|
||||
|
||||
opts := core.CompilerOptions{Target: testCase.target}
|
||||
|
||||
program := compiler.NewProgram(compiler.ProgramOptions{
|
||||
Config: &tsoptions.ParsedCommandLine{
|
||||
ParsedConfig: &core.ParsedOptions{
|
||||
FileNames: []string{"c:/dev/src/index.ts"},
|
||||
CompilerOptions: &opts,
|
||||
},
|
||||
},
|
||||
Host: compiler.NewCompilerHost("c:/dev/src", fs, bundled.LibPath(), nil, nil),
|
||||
})
|
||||
|
||||
actualFiles := []string{}
|
||||
for _, file := range program.GetSourceFiles() {
|
||||
actualFiles = append(actualFiles, strings.TrimPrefix(file.FileName(), libPrefix))
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, testCase.expectedFiles, actualFiles)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncludeProcessorDiagnosticsWithMissingFileCasing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if !bundled.Embedded {
|
||||
t.Skip("bundled files are not embedded")
|
||||
}
|
||||
|
||||
// Use case-sensitive file names so that /src/MyFile.ts and /src/myFile.ts
|
||||
// have different canonical paths but the same lower-case path, triggering
|
||||
// file casing diagnostics in the include processor.
|
||||
fs := vfstest.FromMap[any](nil, true /*useCaseSensitiveFileNames*/)
|
||||
fs = bundled.WrapFS(fs)
|
||||
|
||||
// Only create the lowercase version; /src/MyFile.ts does not exist.
|
||||
_ = fs.WriteFile("/src/myFile.ts", `export const y = 2;`)
|
||||
|
||||
opts := core.CompilerOptions{SkipDefaultLibCheck: core.TSTrue}
|
||||
|
||||
// List both casings as root files. The first one (/src/MyFile.ts) will fail
|
||||
// to load because it does not exist on the case-sensitive filesystem.
|
||||
program := compiler.NewProgram(compiler.ProgramOptions{
|
||||
Config: &tsoptions.ParsedCommandLine{
|
||||
ParsedConfig: &core.ParsedOptions{
|
||||
FileNames: []string{"/src/MyFile.ts", "/src/myFile.ts"},
|
||||
CompilerOptions: &opts,
|
||||
},
|
||||
},
|
||||
Host: compiler.NewCompilerHost("/", fs, bundled.LibPath(), nil, nil),
|
||||
})
|
||||
|
||||
// GetProgramDiagnostics triggers getDiagnostics which processes all
|
||||
// include processor diagnostics including the casing diagnostic whose
|
||||
// file path points to the missing /src/MyFile.ts. Before the fix this
|
||||
// panicked with a nil pointer dereference.
|
||||
assert.NilError(t, func() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("panic: %v", r)
|
||||
}
|
||||
}()
|
||||
program.GetProgramDiagnostics()
|
||||
return nil
|
||||
}())
|
||||
}
|
||||
|
||||
func BenchmarkNewProgram(b *testing.B) {
|
||||
if !bundled.Embedded {
|
||||
// Without embedding, we'd need to read all of the lib files out from disk into the MapFS.
|
||||
// Just skip this for now.
|
||||
b.Skip("bundled files are not embedded")
|
||||
}
|
||||
|
||||
for _, testCase := range programTestCases {
|
||||
b.Run(testCase.testName, func(b *testing.B) {
|
||||
fs := vfstest.FromMap[any](nil, false /*useCaseSensitiveFileNames*/)
|
||||
fs = bundled.WrapFS(fs)
|
||||
|
||||
for _, testFile := range testCase.files {
|
||||
_ = fs.WriteFile(testFile.fileName, testFile.contents)
|
||||
}
|
||||
|
||||
opts := core.CompilerOptions{Target: testCase.target}
|
||||
programOpts := compiler.ProgramOptions{
|
||||
Config: &tsoptions.ParsedCommandLine{
|
||||
ParsedConfig: &core.ParsedOptions{
|
||||
FileNames: []string{"c:/dev/src/index.ts"},
|
||||
CompilerOptions: &opts,
|
||||
},
|
||||
},
|
||||
Host: compiler.NewCompilerHost("c:/dev/src", fs, bundled.LibPath(), nil, nil),
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
compiler.NewProgram(programOpts)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
b.Run("compiler", func(b *testing.B) {
|
||||
repo.SkipIfNoTypeScriptSubmodule(b)
|
||||
|
||||
rootPath := tspath.NormalizeSlashes(filepath.Join(repo.TypeScriptSubmodulePath(), "src", "compiler"))
|
||||
|
||||
fs := osvfs.FS()
|
||||
fs = bundled.WrapFS(fs)
|
||||
|
||||
host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath(), nil, nil)
|
||||
|
||||
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), nil, nil, host, nil)
|
||||
assert.Equal(b, len(errors), 0, "Expected no errors in parsed command line")
|
||||
|
||||
opts := compiler.ProgramOptions{
|
||||
Config: parsed,
|
||||
Host: host,
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
compiler.NewProgram(opts)
|
||||
}
|
||||
})
|
||||
}
|
||||
237
tools/tsgo/internal/compiler/projectreferencedtsfakinghost.go
Normal file
237
tools/tsgo/internal/compiler/projectreferencedtsfakinghost.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/symlinks"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
"github.com/microsoft/typescript-go/internal/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: symlinks.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 symlinks.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) error {
|
||||
panic("should not be called by resolver")
|
||||
}
|
||||
|
||||
// AppendFile implements vfs.FS.
|
||||
func (fs *projectReferenceDtsFakingVfs) AppendFile(path string, data string) 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, &symlinks.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
|
||||
}
|
||||
|
||||
fileOrDirectoryPath := fs.toPath(fileOrDirectory)
|
||||
if !strings.Contains(string(fileOrDirectoryPath), "/node_modules/") {
|
||||
return false
|
||||
}
|
||||
// Check if the directory or file is a symlinked package
|
||||
if packageRoot := module.ParseNodeModuleFromPath(fileOrDirectory, true /*isFolder*/); packageRoot != "" {
|
||||
fs.handleDirectoryCouldBeSymlink(packageRoot)
|
||||
}
|
||||
knownDirectoryLinks := fs.knownSymlinks.Directories()
|
||||
if knownDirectoryLinks.Size() == 0 {
|
||||
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 *symlinks.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(
|
||||
absolutePath,
|
||||
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
|
||||
}
|
||||
187
tools/tsgo/internal/compiler/projectreferencefilemapper.go
Normal file
187
tools/tsgo/internal/compiler/projectreferencefilemapper.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type projectReferenceFileMapper struct {
|
||||
opts ProgramOptions
|
||||
host module.ResolutionHost
|
||||
loader *fileLoader // Only present during populating the mapper and parsing, released after that
|
||||
|
||||
configToProjectReference map[tspath.Path]*tsoptions.ParsedCommandLine // All the resolved references needed
|
||||
referencesInConfigFile map[tspath.Path][]tspath.Path // Map of config file to its references
|
||||
sourceToProjectReference map[tspath.Path]*tsoptions.SourceOutputAndProjectReference
|
||||
outputDtsToProjectReference map[tspath.Path]*tsoptions.SourceOutputAndProjectReference
|
||||
|
||||
// Store all the realpath from dts in node_modules to source file from project reference needed during parsing so it can be used later
|
||||
realpathDtsToSource collections.SyncMap[tspath.Path, *tsoptions.SourceOutputAndProjectReference]
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getParseFileRedirect(file ast.HasFileName) string {
|
||||
if mapper.opts.canUseProjectReferenceSource() {
|
||||
// Map to source file from project reference
|
||||
source := mapper.getProjectReferenceFromOutputDts(file.Path())
|
||||
if source == nil {
|
||||
source = mapper.getSourceToDtsIfSymlink(file)
|
||||
}
|
||||
if source != nil {
|
||||
return source.Source
|
||||
}
|
||||
} else {
|
||||
// Map to dts file from project reference
|
||||
output := mapper.getProjectReferenceFromSource(file.Path())
|
||||
if output != nil && output.OutputDts != "" {
|
||||
return output.OutputDts
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getResolvedProjectReferences() []*tsoptions.ParsedCommandLine {
|
||||
if mapper.opts.Config.ConfigFile == nil {
|
||||
return nil
|
||||
}
|
||||
refs, ok := mapper.referencesInConfigFile[mapper.opts.Config.ConfigFile.SourceFile.Path()]
|
||||
var result []*tsoptions.ParsedCommandLine
|
||||
if ok {
|
||||
result = make([]*tsoptions.ParsedCommandLine, 0, len(refs))
|
||||
for _, refPath := range refs {
|
||||
refConfig, _ := mapper.configToProjectReference[refPath]
|
||||
result = append(result, refConfig)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
|
||||
return mapper.sourceToProjectReference[path]
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getProjectReferenceFromOutputDts(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
|
||||
return mapper.outputDtsToProjectReference[path]
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) isSourceFromProjectReference(path tspath.Path) bool {
|
||||
return mapper.opts.canUseProjectReferenceSource() && mapper.getProjectReferenceFromSource(path) != nil
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getCompilerOptionsForFile(file ast.HasFileName) *core.CompilerOptions {
|
||||
redirect := mapper.getRedirectParsedCommandLineForResolution(file)
|
||||
return module.GetCompilerOptionsWithRedirect(mapper.opts.Config.CompilerOptions(), redirect)
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getRedirectParsedCommandLineForResolution(file ast.HasFileName) *tsoptions.ParsedCommandLine {
|
||||
redirect, _ := mapper.getRedirectForResolution(file)
|
||||
return redirect
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getRedirectForResolution(file ast.HasFileName) (*tsoptions.ParsedCommandLine, string) {
|
||||
path := file.Path()
|
||||
// Check if outputdts of source file from project reference
|
||||
output := mapper.getProjectReferenceFromSource(path)
|
||||
if output != nil {
|
||||
return output.Resolved, output.Source
|
||||
}
|
||||
|
||||
// Source file from project reference
|
||||
resultFromDts := mapper.getProjectReferenceFromOutputDts(path)
|
||||
if resultFromDts != nil {
|
||||
return resultFromDts.Resolved, resultFromDts.Source
|
||||
}
|
||||
|
||||
realpathDtsToSource := mapper.getSourceToDtsIfSymlink(file)
|
||||
if realpathDtsToSource != nil {
|
||||
return realpathDtsToSource.Resolved, realpathDtsToSource.Source
|
||||
}
|
||||
return nil, file.FileName()
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getResolvedReferenceFor(path tspath.Path) (*tsoptions.ParsedCommandLine, bool) {
|
||||
config, ok := mapper.configToProjectReference[path]
|
||||
return config, ok
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) rangeResolvedProjectReference(
|
||||
f func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool,
|
||||
) bool {
|
||||
if mapper.opts.Config.ConfigFile == nil {
|
||||
return false
|
||||
}
|
||||
seenRef := collections.NewSetWithSizeHint[tspath.Path](len(mapper.referencesInConfigFile))
|
||||
seenRef.Add(mapper.opts.Config.ConfigFile.SourceFile.Path())
|
||||
refs := mapper.referencesInConfigFile[mapper.opts.Config.ConfigFile.SourceFile.Path()]
|
||||
return mapper.rangeResolvedReferenceWorker(refs, f, mapper.opts.Config, seenRef)
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) rangeResolvedReferenceWorker(
|
||||
references []tspath.Path,
|
||||
f func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool,
|
||||
parent *tsoptions.ParsedCommandLine,
|
||||
seenRef *collections.Set[tspath.Path],
|
||||
) bool {
|
||||
for index, path := range references {
|
||||
if !seenRef.AddIfAbsent(path) {
|
||||
continue
|
||||
}
|
||||
config, _ := mapper.configToProjectReference[path]
|
||||
if !f(path, config, parent, index) {
|
||||
return false
|
||||
}
|
||||
if !mapper.rangeResolvedReferenceWorker(mapper.referencesInConfigFile[path], f, config, seenRef) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) rangeResolvedProjectReferenceInChildConfig(
|
||||
childConfig *tsoptions.ParsedCommandLine,
|
||||
f func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool,
|
||||
) bool {
|
||||
if childConfig == nil || childConfig.ConfigFile == nil {
|
||||
return false
|
||||
}
|
||||
seenRef := collections.NewSetWithSizeHint[tspath.Path](len(mapper.referencesInConfigFile))
|
||||
seenRef.Add(childConfig.ConfigFile.SourceFile.Path())
|
||||
refs := mapper.referencesInConfigFile[childConfig.ConfigFile.SourceFile.Path()]
|
||||
return mapper.rangeResolvedReferenceWorker(refs, f, mapper.opts.Config, seenRef)
|
||||
}
|
||||
|
||||
func (mapper *projectReferenceFileMapper) getSourceToDtsIfSymlink(file ast.HasFileName) *tsoptions.SourceOutputAndProjectReference {
|
||||
// If preserveSymlinks is true, module resolution wont jump the symlink
|
||||
// but the resolved real path may be the .d.ts from project reference
|
||||
// Note:: Currently we try the real path only if the
|
||||
// file is from node_modules to avoid having to run real path on all file paths
|
||||
path := file.Path()
|
||||
realpathDtsToSource, ok := mapper.realpathDtsToSource.Load(path)
|
||||
if ok {
|
||||
return realpathDtsToSource
|
||||
}
|
||||
if mapper.loader != nil && mapper.opts.Config.CompilerOptions().PreserveSymlinks == core.TSTrue {
|
||||
fileName := file.FileName()
|
||||
if !strings.Contains(fileName, "/node_modules/") {
|
||||
mapper.realpathDtsToSource.Store(path, nil)
|
||||
} else {
|
||||
realDeclarationPath := mapper.loader.toPath(mapper.host.FS().Realpath(fileName))
|
||||
if realDeclarationPath == path {
|
||||
mapper.realpathDtsToSource.Store(path, nil)
|
||||
} else {
|
||||
realpathDtsToSource := mapper.getProjectReferenceFromOutputDts(realDeclarationPath)
|
||||
if realpathDtsToSource != nil {
|
||||
mapper.realpathDtsToSource.Store(path, realpathDtsToSource)
|
||||
return realpathDtsToSource
|
||||
}
|
||||
mapper.realpathDtsToSource.Store(path, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
115
tools/tsgo/internal/compiler/projectreferenceparser.go
Normal file
115
tools/tsgo/internal/compiler/projectreferenceparser.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"maps"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tracing"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type projectReferenceParseTask struct {
|
||||
configName string
|
||||
resolved *tsoptions.ParsedCommandLine
|
||||
subTasks []*projectReferenceParseTask
|
||||
}
|
||||
|
||||
func (t *projectReferenceParseTask) parse(projectReferenceParser *projectReferenceParser) {
|
||||
loader := projectReferenceParser.loader
|
||||
if tr := loader.opts.Tracing; tr != nil {
|
||||
defer tr.Push(tracing.PhaseParse, "parseJsonSourceFileConfigFileContent", map[string]any{"path": t.configName}, false)()
|
||||
}
|
||||
t.resolved = loader.opts.Host.GetResolvedProjectReference(t.configName, loader.toPath(t.configName))
|
||||
if t.resolved == nil {
|
||||
return
|
||||
}
|
||||
t.resolved.ParseInputOutputNames()
|
||||
if subReferences := t.resolved.ResolvedProjectReferencePaths(); len(subReferences) > 0 {
|
||||
t.subTasks = createProjectReferenceParseTasks(subReferences)
|
||||
}
|
||||
}
|
||||
|
||||
func createProjectReferenceParseTasks(projectReferences []string) []*projectReferenceParseTask {
|
||||
return core.Map(projectReferences, func(configName string) *projectReferenceParseTask {
|
||||
return &projectReferenceParseTask{
|
||||
configName: configName,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type projectReferenceParser struct {
|
||||
loader *fileLoader
|
||||
wg core.WorkGroup
|
||||
tasksByFileName collections.SyncMap[tspath.Path, *projectReferenceParseTask]
|
||||
}
|
||||
|
||||
func (p *projectReferenceParser) parse(tasks []*projectReferenceParseTask) {
|
||||
p.loader.projectReferenceFileMapper.loader = p.loader
|
||||
p.start(tasks)
|
||||
p.wg.RunAndWait()
|
||||
p.initMapper(tasks)
|
||||
}
|
||||
|
||||
func (p *projectReferenceParser) start(tasks []*projectReferenceParseTask) {
|
||||
for i, task := range tasks {
|
||||
path := p.loader.toPath(task.configName)
|
||||
if loadedTask, loaded := p.tasksByFileName.LoadOrStore(path, task); loaded {
|
||||
// dedup tasks to ensure correct file order, regardless of which task would be started first
|
||||
tasks[i] = loadedTask
|
||||
} else {
|
||||
p.wg.Queue(func() {
|
||||
task.parse(p)
|
||||
p.start(task.subTasks)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *projectReferenceParser) initMapper(tasks []*projectReferenceParseTask) {
|
||||
totalReferences := p.tasksByFileName.Size() + 1
|
||||
p.loader.projectReferenceFileMapper.configToProjectReference = make(map[tspath.Path]*tsoptions.ParsedCommandLine, totalReferences)
|
||||
p.loader.projectReferenceFileMapper.referencesInConfigFile = make(map[tspath.Path][]tspath.Path, totalReferences)
|
||||
p.loader.projectReferenceFileMapper.sourceToProjectReference = make(map[tspath.Path]*tsoptions.SourceOutputAndProjectReference)
|
||||
p.loader.projectReferenceFileMapper.outputDtsToProjectReference = make(map[tspath.Path]*tsoptions.SourceOutputAndProjectReference)
|
||||
p.loader.projectReferenceFileMapper.referencesInConfigFile[p.loader.opts.Config.ConfigFile.SourceFile.Path()] = p.initMapperWorker(tasks, &collections.Set[*projectReferenceParseTask]{})
|
||||
if p.loader.projectReferenceFileMapper.opts.canUseProjectReferenceSource() && len(p.loader.projectReferenceFileMapper.outputDtsToProjectReference) != 0 {
|
||||
p.loader.projectReferenceFileMapper.host = newProjectReferenceDtsFakingHost(p.loader)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *projectReferenceParser) initMapperWorker(tasks []*projectReferenceParseTask, seen *collections.Set[*projectReferenceParseTask]) []tspath.Path {
|
||||
if len(tasks) == 0 {
|
||||
return nil
|
||||
}
|
||||
results := make([]tspath.Path, 0, len(tasks))
|
||||
for _, task := range tasks {
|
||||
path := p.loader.toPath(task.configName)
|
||||
results = append(results, path)
|
||||
// ensure we only walk each task once
|
||||
if !seen.AddIfAbsent(task) {
|
||||
continue
|
||||
}
|
||||
p.loader.projectReferenceFileMapper.configToProjectReference[path] = task.resolved
|
||||
if task.resolved != nil && p.loader.projectReferenceFileMapper.opts.Config.ConfigFile != task.resolved.ConfigFile {
|
||||
// Map current task's files first, before recursing into subtasks.
|
||||
// This matches TypeScript's behavior where child project references
|
||||
// overwrite parent entries when a file belongs to multiple projects.
|
||||
maps.Copy(p.loader.projectReferenceFileMapper.sourceToProjectReference, task.resolved.SourceToProjectReference())
|
||||
maps.Copy(p.loader.projectReferenceFileMapper.outputDtsToProjectReference, task.resolved.OutputDtsToProjectReference())
|
||||
if p.loader.projectReferenceFileMapper.opts.canUseProjectReferenceSource() {
|
||||
declDir := task.resolved.CompilerOptions().DeclarationDir
|
||||
if declDir == "" {
|
||||
declDir = task.resolved.CompilerOptions().OutDir
|
||||
}
|
||||
if declDir != "" {
|
||||
p.loader.dtsDirectories.Add(p.loader.toPath(declDir))
|
||||
}
|
||||
}
|
||||
}
|
||||
referencesInConfig := p.initMapperWorker(task.subTasks, seen)
|
||||
p.loader.projectReferenceFileMapper.referencesInConfigFile[path] = referencesInConfig
|
||||
}
|
||||
return results
|
||||
}
|
||||
Reference in New Issue
Block a user