792 lines
31 KiB
Go
792 lines
31 KiB
Go
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
|
|
}
|