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 }