package compiler import ( "context" "fmt" "io" "maps" "slices" "strings" "sync" "sync/atomic" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/binder" "github.com/microsoft/typescript-go/internal/checker" "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/json" "github.com/microsoft/typescript-go/internal/locale" "github.com/microsoft/typescript-go/internal/module" "github.com/microsoft/typescript-go/internal/modulespecifiers" "github.com/microsoft/typescript-go/internal/outputpaths" "github.com/microsoft/typescript-go/internal/packagejson" "github.com/microsoft/typescript-go/internal/parser" "github.com/microsoft/typescript-go/internal/printer" "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/sourcemap" "github.com/microsoft/typescript-go/internal/symlinks" "github.com/microsoft/typescript-go/internal/tracing" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" ) type ProgramOptions struct { Host CompilerHost Config *tsoptions.ParsedCommandLine UseSourceOfProjectReference bool SingleThreaded core.Tristate CreateCheckerPool func(*Program) CheckerPool TypingsLocation string ProjectName string Tracing *tracing.Tracing } func (p *ProgramOptions) canUseProjectReferenceSource() bool { return p.UseSourceOfProjectReference && !p.Config.CompilerOptions().DisableSourceOfProjectReferenceRedirect.IsTrue() } type lazyValue[T any] struct { value *T once sync.Once initialized atomic.Bool } func (l *lazyValue[T]) getValue(compute func() *T) *T { l.once.Do(func() { if l.value == nil { l.value = compute() } l.initialized.Store(true) }) return l.value } func (l *lazyValue[T]) tryReuse(from *lazyValue[T]) { if from.initialized.Load() { l.value = from.value l.initialized.Store(true) } } type packageNamesInfo struct { resolved *collections.Set[string] unresolved *collections.Set[string] deepImportPackages *collections.Set[string] } type Program struct { opts ProgramOptions checkerPool CheckerPool // always set; used as fallback for project system pools // compilerCheckerPool is set only when the built-in compiler checker pool is in use // (i.e. CreateCheckerPool was not provided). It enables grouped parallel iteration, // non-exclusive access for emit, and direct global diagnostics collection. compilerCheckerPool *checkerPool comparePathsOptions tspath.ComparePathsOptions processedFiles usesUriStyleNodeCoreModules core.Tristate commonSourceDirectory string commonSourceDirectoryOnce sync.Once declarationDiagnosticCache collections.SyncMap[*ast.SourceFile, []*ast.Diagnostic] programDiagnostics []*ast.Diagnostic hasEmitBlockingDiagnostics collections.Set[tspath.Path] sourceFilesToEmitOnce sync.Once sourceFilesToEmit []*ast.SourceFile // Cached unresolved imports for ATA unresolvedImports lazyValue[collections.Set[string]] knownSymlinks lazyValue[symlinks.KnownSymlinks] // Used by auto-imports packageNames lazyValue[packageNamesInfo] // Used by workspace/symbol hasTSFileOnce sync.Once hasTSFile bool // Cached map of package names to whether they bundle types packagesMapOnce sync.Once packagesMap map[string]bool } // FileExists implements checker.Program. func (p *Program) FileExists(path string) bool { return p.Host().FS().FileExists(path) } // GetCurrentDirectory implements checker.Program. func (p *Program) GetCurrentDirectory() string { return p.Host().GetCurrentDirectory() } // GetGlobalTypingsCacheLocation implements checker.Program. func (p *Program) GetGlobalTypingsCacheLocation() string { return p.opts.TypingsLocation } // GetNearestAncestorDirectoryWithPackageJson implements checker.Program. func (p *Program) GetNearestAncestorDirectoryWithPackageJson(dirname string) string { scoped := p.resolver.GetPackageScopeForPath(dirname) if scoped != nil && scoped.Exists() { return scoped.PackageDirectory } return "" } // GetPackageJsonInfo implements checker.Program. func (p *Program) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry { directory := tspath.GetDirectoryPath(pkgJsonPath) scoped := p.resolver.GetPackageScopeForPath(directory) if scoped != nil && scoped.Exists() && scoped.PackageDirectory == directory { return scoped } return nil } // PackageJsonCacheEntries iterates on all package json cache entries. func (p *Program) PackageJsonCacheEntries(f func(key tspath.Path, value *packagejson.InfoCacheEntry) bool) { p.resolver.PackageJsonCacheEntries(f) } // GetRedirectTargets returns the list of file paths that redirect to the given path. // These are files from the same package (same name@version) installed in different locations. func (p *Program) GetRedirectTargets(path tspath.Path) []string { return p.redirectTargetsMap[path] } // gets the original file that was included in program // this returns original source file name when including output of project reference // otherwise same name // Equivalent to originalFileName on SourceFile in Strada func (p *Program) GetSourceOfProjectReferenceIfOutputIncluded(file ast.HasFileName) string { if source, ok := p.outputFileToProjectReferenceSource[file.Path()]; ok { return source } return file.FileName() } // GetProjectReferenceFromSource implements checker.Program. func (p *Program) GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference { return p.projectReferenceFileMapper.getProjectReferenceFromSource(path) } // IsSourceFromProjectReference implements checker.Program. func (p *Program) IsSourceFromProjectReference(path tspath.Path) bool { return p.projectReferenceFileMapper.isSourceFromProjectReference(path) } func (p *Program) GetProjectReferenceFromOutputDts(path tspath.Path) *tsoptions.SourceOutputAndProjectReference { return p.projectReferenceFileMapper.getProjectReferenceFromOutputDts(path) } func (p *Program) GetResolvedProjectReferenceFor(path tspath.Path) (*tsoptions.ParsedCommandLine, bool) { return p.projectReferenceFileMapper.getResolvedReferenceFor(path) } func (p *Program) GetRedirectForResolution(file ast.HasFileName) *tsoptions.ParsedCommandLine { redirect, _ := p.projectReferenceFileMapper.getRedirectForResolution(file) return redirect } func (p *Program) GetParseFileRedirect(fileName string) string { return p.projectReferenceFileMapper.getParseFileRedirect(ast.NewHasFileName(fileName, p.toPath(fileName))) } func (p *Program) GetResolvedProjectReferences() []*tsoptions.ParsedCommandLine { return p.projectReferenceFileMapper.getResolvedProjectReferences() } func (p *Program) RangeResolvedProjectReference(f func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool) bool { return p.projectReferenceFileMapper.rangeResolvedProjectReference(f) } func (p *Program) RangeResolvedProjectReferenceInChildConfig( childConfig *tsoptions.ParsedCommandLine, f func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool, ) bool { return p.projectReferenceFileMapper.rangeResolvedProjectReferenceInChildConfig(childConfig, f) } // UseCaseSensitiveFileNames implements checker.Program. func (p *Program) UseCaseSensitiveFileNames() bool { return p.Host().FS().UseCaseSensitiveFileNames() } func (p *Program) UsesUriStyleNodeCoreModules() core.Tristate { return p.usesUriStyleNodeCoreModules } var _ checker.Program = (*Program)(nil) /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ func (p *Program) GetSourceFileFromReference(origin *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile { // TODO: The module loader in corsa is fairly different than strada, it should probably be able to expose this functionality at some point, // rather than redoing the logic approximately here, since most of the related logic now lives in module.Resolver // Still, without the failed lookup reporting that only the loader does, this isn't terribly complicated fileName := tspath.ResolvePath(tspath.GetDirectoryPath(origin.FileName()), ref.FileName) supportedExtensionsBase := tsoptions.GetSupportedExtensions(p.Options(), nil /*extraFileExtensions*/) supportedExtensions := tsoptions.GetSupportedExtensionsWithJsonIfResolveJsonModule(p.Options(), supportedExtensionsBase) allowNonTsExtensions := p.Options().AllowNonTsExtensions.IsTrue() if tspath.HasExtension(fileName) { if !allowNonTsExtensions { canonicalFileName := tspath.GetCanonicalFileName(fileName, p.UseCaseSensitiveFileNames()) supported := false for _, group := range supportedExtensions { if tspath.FileExtensionIsOneOf(canonicalFileName, group) { supported = true break } } if !supported { return nil // unsupported extensions are forced to fail } } return p.GetSourceFileForResolvedModule(fileName) } if allowNonTsExtensions { extensionless := p.GetSourceFileForResolvedModule(fileName) if extensionless != nil { return extensionless } } // Only try adding extensions from the first supported group (which should be .ts/.tsx/.d.ts) for _, ext := range supportedExtensions[0] { result := p.GetSourceFileForResolvedModule(fileName + ext) if result != nil { return result } } return nil } func NewProgram(opts ProgramOptions) *Program { p := &Program{opts: opts} if p.opts.Tracing != nil { defer p.opts.Tracing.Push(tracing.PhaseProgram, "createProgram", map[string]any{"configFilePath": opts.Config.CompilerOptions().ConfigFilePath}, true)() } p.processedFiles = processAllProgramFiles(p.opts, p.SingleThreaded()) p.initCheckerPool() p.verifyCompilerOptions() return p } // Return an updated program for which it is known that only the file with the given path has changed. // In addition to a new program, return a boolean indicating whether the data of the old program was reused. // createCheckerPool, if non-nil, overrides the CreateCheckerPool stored in the old program's options, // ensuring each caller uses a fresh closure and avoiding data races on captured variables. // The returned *ast.SourceFile is the changed file as acquired through newHost; it is nil // only if the host cannot locate the file (e.g. it was deleted). Callers that manage // host-side parse caches must release this exact pointer when the old program could not be // reused, since it was acquired speculatively before that decision was made. func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHost, createCheckerPool func(*Program) CheckerPool) (*Program, *ast.SourceFile, bool) { newOpts := p.opts newOpts.Host = newHost if createCheckerPool != nil { newOpts.CreateCheckerPool = createCheckerPool } oldFile := p.filesByPath[changedFilePath] newFile := newHost.GetSourceFile(oldFile.ParseOptions()) // If this file is part of a package redirect group (same package installed in multiple // node_modules locations), we need to rebuild the program because the redirect targets // might need recalculation. _, inRedirectFiles := p.redirectFilesByPath[changedFilePath] _, isRedirectTarget := p.redirectTargetsMap[changedFilePath] if inRedirectFiles || isRedirectTarget { return NewProgram(newOpts), newFile, false } if !canReplaceFileInProgram(oldFile, newFile) { return NewProgram(newOpts), newFile, false } if oldNeedsImportHelpers := p.importHelpersImportSpecifiers[oldFile.Path()] != nil; oldNeedsImportHelpers != p.needsImportHelpersImportSpecifier(newFile) { return NewProgram(newOpts), newFile, false } // TODO: reverify compiler options when config has changed? result := &Program{ opts: newOpts, comparePathsOptions: p.comparePathsOptions, processedFiles: p.processedFiles, usesUriStyleNodeCoreModules: p.usesUriStyleNodeCoreModules, programDiagnostics: p.programDiagnostics, hasEmitBlockingDiagnostics: p.hasEmitBlockingDiagnostics, } result.unresolvedImports.tryReuse(&p.unresolvedImports) result.knownSymlinks.tryReuse(&p.knownSymlinks) result.packageNames.tryReuse(&p.packageNames) result.initCheckerPool() index := core.FindIndex(result.files, func(file *ast.SourceFile) bool { return file.Path() == newFile.Path() }) result.files = slices.Clone(result.files) result.files[index] = newFile result.filesByPath = maps.Clone(result.filesByPath) result.filesByPath[newFile.Path()] = newFile updateFileIncludeProcessor(result) return result, newFile, true } func (p *Program) initCheckerPool() { if !p.finishedProcessing { panic("Program must finish processing files before initializing checker pool") } if p.opts.CreateCheckerPool != nil { p.checkerPool = p.opts.CreateCheckerPool(p) } else { pool := newCheckerPoolWithTracing(p, p.opts.Tracing) p.checkerPool = pool p.compilerCheckerPool = pool } } // GetCheckerPool returns the checker pool associated with this program. func (p *Program) GetCheckerPool() CheckerPool { return p.checkerPool } func canReplaceFileInProgram(file1 *ast.SourceFile, file2 *ast.SourceFile) bool { return file2 != nil && file1.ParseOptions() == file2.ParseOptions() && file1.UsesUriStyleNodeCoreModules == file2.UsesUriStyleNodeCoreModules && slices.EqualFunc(file1.Imports(), file2.Imports(), equalModuleSpecifiers) && slices.EqualFunc(file1.ModuleAugmentations, file2.ModuleAugmentations, equalModuleAugmentationNames) && slices.Equal(file1.AmbientModuleNames, file2.AmbientModuleNames) && slices.EqualFunc(file1.ReferencedFiles, file2.ReferencedFiles, equalFileReferences) && slices.EqualFunc(file1.TypeReferenceDirectives, file2.TypeReferenceDirectives, equalFileReferences) && slices.EqualFunc(file1.LibReferenceDirectives, file2.LibReferenceDirectives, equalFileReferences) && equalCheckJSDirectives(file1.CheckJsDirective, file2.CheckJsDirective) } func (p *Program) needsImportHelpersImportSpecifier(file *ast.SourceFile) bool { redirect, _ := p.projectReferenceFileMapper.getRedirectForResolution(file) optionsForFile := module.GetCompilerOptionsWithRedirect(p.opts.Config.CompilerOptions(), redirect) if !optionsForFile.ImportHelpers.IsTrue() { return false } isJavaScriptFile := ast.IsSourceFileJS(file) isExternalModuleFile := ast.IsExternalModule(file) if !isJavaScriptFile && (file.IsDeclarationFile || (!optionsForFile.GetIsolatedModules() && !isExternalModuleFile)) { return false } return true } func equalModuleSpecifiers(n1 *ast.Node, n2 *ast.Node) bool { return n1.Kind == n2.Kind && (!ast.IsStringLiteral(n1) || n1.Text() == n2.Text()) } func equalModuleAugmentationNames(n1 *ast.Node, n2 *ast.Node) bool { return n1.Kind == n2.Kind && n1.Text() == n2.Text() } func equalFileReferences(f1 *ast.FileReference, f2 *ast.FileReference) bool { return f1.FileName == f2.FileName && f1.ResolutionMode == f2.ResolutionMode && f1.Preserve == f2.Preserve } func equalCheckJSDirectives(d1 *ast.CheckJsDirective, d2 *ast.CheckJsDirective) bool { return d1 == nil && d2 == nil || d1 != nil && d2 != nil && d1.Enabled == d2.Enabled } func (p *Program) SourceFiles() []*ast.SourceFile { return p.files } func (p *Program) DuplicateSourceFiles() []*DuplicateSourceFile { return p.duplicateSourceFiles } func (p *Program) Options() *core.CompilerOptions { return p.opts.Config.CompilerOptions() } func (p *Program) CommandLine() *tsoptions.ParsedCommandLine { return p.opts.Config } func (p *Program) Host() CompilerHost { return p.opts.Host } func (p *Program) Tracing() *tracing.Tracing { return p.opts.Tracing } func (p *Program) GetConfigFileParsingDiagnostics() []*ast.Diagnostic { return slices.Clip(p.opts.Config.GetConfigFileParsingDiagnostics()) } // GetUnresolvedImports returns the unresolved imports for this program. // The result is cached and computed only once. func (p *Program) GetUnresolvedImports() *collections.Set[string] { return p.unresolvedImports.getValue(p.extractUnresolvedImports) } func (p *Program) extractUnresolvedImports() *collections.Set[string] { unresolvedSet := &collections.Set[string]{} for _, sourceFile := range p.files { unresolvedImports := p.extractUnresolvedImportsFromSourceFile(sourceFile) for _, imp := range unresolvedImports { unresolvedSet.Add(imp) } } return unresolvedSet } func (p *Program) extractUnresolvedImportsFromSourceFile(file *ast.SourceFile) []string { var unresolvedImports []string resolvedModules := p.resolvedModules[file.Path()] for cacheKey, resolution := range resolvedModules { resolved := resolution.IsResolved() if (!resolved || !tspath.ExtensionIsOneOf(resolution.Extension, tspath.SupportedTSExtensionsWithJsonFlat)) && !tspath.IsExternalModuleNameRelative(cacheKey.Name) { unresolvedImports = append(unresolvedImports, cacheKey.Name) } } return unresolvedImports } func (p *Program) SingleThreaded() bool { return p.opts.SingleThreaded.DefaultIfUnknown(p.Options().SingleThreaded).IsTrue() } func (p *Program) BindSourceFiles() { wg := core.NewWorkGroup(p.SingleThreaded()) for _, file := range p.files { if !file.IsBound() { wg.Queue(func() { if p.opts.Tracing != nil { defer p.opts.Tracing.Push(tracing.PhaseBind, "bindSourceFile", map[string]any{"path": string(file.Path())}, true)() } binder.BindSourceFile(file) }) } } wg.RunAndWait() } // Return the type checker associated with the program. func (p *Program) GetTypeChecker(ctx context.Context) (*checker.Checker, func()) { if p.compilerCheckerPool != nil { return p.compilerCheckerPool.getCheckerNonExclusive() } return p.checkerPool.GetChecker(ctx, nil) } func (p *Program) ForEachCheckerParallel(cb func(idx int, c *checker.Checker)) { if p.compilerCheckerPool != nil { p.compilerCheckerPool.forEachCheckerParallel(cb) } } // Return a checker for the given file. We may have multiple checkers in concurrent scenarios and this // method returns the checker that was tasked with checking the file. Note that it isn't possible to mix // types obtained from different checkers, so only non-type data (such as diagnostics or string // representations of types) should be obtained from checkers returned by this method. func (p *Program) GetTypeCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) { if p.compilerCheckerPool != nil { return p.compilerCheckerPool.getCheckerForFileNonExclusive(file) } return p.checkerPool.GetChecker(ctx, file) } // Return a checker for the given file, locked to the current thread to prevent data races from multiple threads // accessing the same checker. The lock will be released when the `done` function is called. func (p *Program) GetTypeCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) { if p.compilerCheckerPool != nil { return p.compilerCheckerPool.getCheckerForFileExclusive(ctx, file) } return p.checkerPool.GetChecker(ctx, file) } func (p *Program) GetResolvedModule(file ast.HasFileName, moduleReference string, mode core.ResolutionMode) *module.ResolvedModule { if resolutions, ok := p.resolvedModules[file.Path()]; ok { if resolved, ok := resolutions[module.ModeAwareCacheKey{Name: moduleReference, Mode: mode}]; ok { return resolved } } return nil } func (p *Program) GetResolvedModuleFromModuleSpecifier(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) *module.ResolvedModule { if !ast.IsStringLiteralLike(moduleSpecifier) { panic("moduleSpecifier must be a StringLiteralLike") } mode := p.GetModeForUsageLocation(file, moduleSpecifier) return p.GetResolvedModule(file, moduleSpecifier.Text(), mode) } func (p *Program) GetResolvedModules() map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule] { return p.resolvedModules } // GetPackagesMap returns a lazily-cached map of package names to whether they bundle types. // This is used by incremental diagnostic repopulation. func (p *Program) GetPackagesMap() map[string]bool { p.packagesMapOnce.Do(func() { p.packagesMap = make(map[string]bool) for _, resolvedModulesInFile := range p.resolvedModules { for _, mod := range resolvedModulesInFile { if mod.PackageId.Name != "" { p.packagesMap[mod.PackageId.Name] = p.packagesMap[mod.PackageId.Name] || mod.Extension == tspath.ExtensionDts } } } }) return p.packagesMap } // collectDiagnostics collects diagnostics from a single file or all files. // If sourceFile is non-nil, returns diagnostics for just that file. // If sourceFile is nil, returns diagnostics for all files in the program. func (p *Program) collectDiagnostics(ctx context.Context, sourceFile *ast.SourceFile, concurrent bool, collect func(context.Context, *ast.SourceFile) []*ast.Diagnostic) []*ast.Diagnostic { var result []*ast.Diagnostic if sourceFile != nil { result = collect(ctx, sourceFile) } else { diagnostics := p.collectDiagnosticsFromFiles(ctx, p.files, concurrent, collect) result = slices.Concat(diagnostics...) } return SortAndDeduplicateDiagnostics(result) } func (p *Program) collectDiagnosticsFromFiles(ctx context.Context, sourceFiles []*ast.SourceFile, concurrent bool, collect func(context.Context, *ast.SourceFile) []*ast.Diagnostic) [][]*ast.Diagnostic { diagnostics := make([][]*ast.Diagnostic, len(sourceFiles)) wg := core.NewWorkGroup(!concurrent || p.SingleThreaded()) for i, file := range sourceFiles { wg.Queue(func() { diagnostics[i] = collect(ctx, file) }) } wg.RunAndWait() return diagnostics } // collectCheckerDiagnostics collects diagnostics from a single file or all files, // using a callback that receives the checker for each file. When the checker pool // supports grouped iteration (compiler pool), files are grouped by checker and // processed in parallel with one task per checker, reducing contention and improving // cache locality. Otherwise, falls back to per-file concurrent collection. func (p *Program) collectCheckerDiagnostics(ctx context.Context, sourceFile *ast.SourceFile, collect func(context.Context, *checker.Checker, *ast.SourceFile) []*ast.Diagnostic) []*ast.Diagnostic { if sourceFile != nil { if p.SkipTypeChecking(sourceFile, false) { return nil } c, done := p.GetTypeCheckerForFileExclusive(ctx, sourceFile) result := collect(ctx, c, sourceFile) done() return SortAndDeduplicateDiagnostics(result) } return SortAndDeduplicateDiagnostics(slices.Concat(p.collectCheckerDiagnosticsFromFiles(ctx, p.files, collect)...)) } // collectCheckerDiagnosticsFromFiles collects checker diagnostics for a list of files. func (p *Program) collectCheckerDiagnosticsFromFiles(ctx context.Context, sourceFiles []*ast.SourceFile, collect func(context.Context, *checker.Checker, *ast.SourceFile) []*ast.Diagnostic) [][]*ast.Diagnostic { diagnostics := make([][]*ast.Diagnostic, len(sourceFiles)) if p.compilerCheckerPool != nil { p.compilerCheckerPool.forEachCheckerGroupDo(ctx, sourceFiles, p.SingleThreaded(), func(c *checker.Checker, fileIndex int, file *ast.SourceFile) { diagnostics[fileIndex] = collect(ctx, c, file) }) } else { wg := core.NewWorkGroup(p.SingleThreaded()) for i, file := range sourceFiles { if p.SkipTypeChecking(file, false) { continue } wg.Queue(func() { c, done := p.checkerPool.GetChecker(ctx, file) diagnostics[i] = collect(ctx, c, file) done() }) } wg.RunAndWait() } return diagnostics } func (p *Program) GetSyntacticDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { return p.collectDiagnostics(ctx, sourceFile, false /*concurrent*/, func(_ context.Context, file *ast.SourceFile) []*ast.Diagnostic { diags := core.Concatenate(file.Diagnostics(), file.JSDiagnostics()) // For JS files that won't be checked by the checker (no checkJs/ts-check), we need // program-level syntactic checks that require compiler options. This mirrors Strada's // getJSSyntacticDiagnosticsForFile in program.ts. if ast.IsSourceFileJS(file) && !ast.IsCheckJSEnabledForFile(file, p.Options()) { diags = append(diags, getAdditionalJSSyntacticDiagnostics(file, p.Options())...) } return diags }) } // getAdditionalJSSyntacticDiagnostics produces option-dependent syntactic diagnostics for JS files // that aren't covered by the parser or the checker. In Strada, the equivalent logic lives in // getJSSyntacticDiagnosticsForFile in program.ts. In Corsa, most of that function's checks were // moved into the parser (checkJSSyntax/checkJSDecoratorSyntax), but checks that depend on compiler // options can't live in the parser and must remain here. The checker handles these for checked files, // but doesn't run on unchecked JS files (no checkJs/ts-check). func getAdditionalJSSyntacticDiagnostics(file *ast.SourceFile, options *core.CompilerOptions) []*ast.Diagnostic { if options.ExperimentalDecorators.IsTrue() { return nil } var diags []*ast.Diagnostic // Parameter decorators are only valid with experimentalDecorators. Without it, // the checker would report this, but the checker doesn't run on unchecked JS files. var walk ast.Visitor walk = func(node *ast.Node) bool { if node.SubtreeFacts()&ast.SubtreeContainsDecorators == 0 { return false } if node.Kind == ast.KindParameter && ast.HasDecorators(node) { decorator := core.Find(node.ModifierNodes(), ast.IsDecorator) if decorator != nil { diags = append(diags, ast.NewDiagnostic(file, decorator.Loc, diagnostics.Decorators_are_not_valid_here)) } } node.ForEachChild(walk) return false } file.AsNode().ForEachChild(walk) return diags } func (p *Program) GetBindDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { if sourceFile != nil { binder.BindSourceFile(sourceFile) } else { p.BindSourceFiles() } return p.collectDiagnostics(ctx, sourceFile, false /*concurrent*/, func(_ context.Context, file *ast.SourceFile) []*ast.Diagnostic { return file.BindDiagnostics() }) } func (p *Program) GetSemanticDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { return p.collectCheckerDiagnostics(ctx, sourceFile, p.getSemanticDiagnosticsWithChecker) } func (p *Program) GetSemanticDiagnosticsWithoutNoEmitFiltering(ctx context.Context, sourceFiles []*ast.SourceFile) map[*ast.SourceFile][]*ast.Diagnostic { allDiags := p.collectCheckerDiagnosticsFromFiles(ctx, sourceFiles, p.getBindAndCheckDiagnosticsWithChecker) result := make(map[*ast.SourceFile][]*ast.Diagnostic, len(sourceFiles)) for i, diags := range allDiags { result[sourceFiles[i]] = SortAndDeduplicateDiagnostics(diags) } return result } func (p *Program) GetSuggestionDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { return p.collectCheckerDiagnostics(ctx, sourceFile, p.getSuggestionDiagnosticsWithChecker) } func (p *Program) GetProgramDiagnostics() []*ast.Diagnostic { return SortAndDeduplicateDiagnostics(core.Concatenate( p.programDiagnostics, p.includeProcessor.getDiagnostics(p).GetGlobalDiagnostics(), )) } func (p *Program) GetIncludeProcessorDiagnostics(sourceFile *ast.SourceFile) []*ast.Diagnostic { if p.SkipTypeChecking(sourceFile, false) { return nil } filtered, _ := p.getDiagnosticsWithPrecedingDirectives(sourceFile, p.includeProcessor.getDiagnostics(p).GetDiagnosticsForFile(sourceFile.FileName())) return filtered } func (p *Program) SkipTypeChecking(sourceFile *ast.SourceFile, ignoreNoCheck bool) bool { return (!ignoreNoCheck && p.Options().NoCheck.IsTrue()) || p.Options().SkipLibCheck.IsTrue() && sourceFile.IsDeclarationFile || p.Options().SkipDefaultLibCheck.IsTrue() && p.IsSourceFileDefaultLibrary(sourceFile.Path()) || p.IsSourceFromProjectReference(sourceFile.Path()) || !p.canIncludeBindAndCheckDiagnostics(sourceFile) } func (p *Program) canIncludeBindAndCheckDiagnostics(sourceFile *ast.SourceFile) bool { if sourceFile.CheckJsDirective != nil && !sourceFile.CheckJsDirective.Enabled { return false } if sourceFile.ScriptKind == core.ScriptKindTS || sourceFile.ScriptKind == core.ScriptKindTSX || sourceFile.ScriptKind == core.ScriptKindExternal { return true } isJS := sourceFile.ScriptKind == core.ScriptKindJS || sourceFile.ScriptKind == core.ScriptKindJSX isCheckJS := isJS && ast.IsCheckJSEnabledForFile(sourceFile, p.Options()) isPlainJS := ast.IsPlainJSFile(sourceFile, p.Options().CheckJs) // By default, only type-check .ts, .tsx, Deferred, plain JS, checked JS and External // - plain JS: .js files with no // ts-check and checkJs: undefined // - check JS: .js files with either // ts-check or checkJs: true // - external: files that are added by plugins return isPlainJS || isCheckJS || sourceFile.ScriptKind == core.ScriptKindDeferred } func (p *Program) getSourceFilesToEmit(targetSourceFile *ast.SourceFile, forceDtsEmit bool) []*ast.SourceFile { if targetSourceFile == nil && !forceDtsEmit { p.sourceFilesToEmitOnce.Do(func() { p.sourceFilesToEmit = getSourceFilesToEmit(p, nil, false) }) return p.sourceFilesToEmit } return getSourceFilesToEmit(p, targetSourceFile, forceDtsEmit) } func (p *Program) verifyCompilerOptions() { options := p.Options() sourceFile := core.Memoize(func() *ast.SourceFile { configFile := p.opts.Config.ConfigFile if configFile == nil { return nil } return configFile.SourceFile }) configFilePath := core.Memoize(func() string { file := sourceFile() if file != nil { return file.FileName() } return "" }) getCompilerOptionsPropertySyntax := core.Memoize(func() *ast.PropertyAssignment { return tsoptions.ForEachTsConfigPropArray(sourceFile(), "compilerOptions", core.Identity) }) getCompilerOptionsObjectLiteralSyntax := core.Memoize(func() *ast.ObjectLiteralExpression { compilerOptionsProperty := getCompilerOptionsPropertySyntax() if compilerOptionsProperty != nil && compilerOptionsProperty.Initializer != nil && ast.IsObjectLiteralExpression(compilerOptionsProperty.Initializer) { return compilerOptionsProperty.Initializer.AsObjectLiteralExpression() } return nil }) createOptionDiagnosticInObjectLiteralSyntax := func(objectLiteral *ast.ObjectLiteralExpression, onKey bool, key1 string, key2 string, message *diagnostics.Message, args ...any) *ast.Diagnostic { diag := tsoptions.ForEachPropertyAssignment(objectLiteral, key1, func(property *ast.PropertyAssignment) *ast.Diagnostic { return tsoptions.CreateDiagnosticForNodeInSourceFile(sourceFile(), core.IfElse(onKey, property.Name(), property.Initializer), message, args...) }, key2) if diag != nil { p.programDiagnostics = append(p.programDiagnostics, diag) } return diag } createCompilerOptionsDiagnostic := func(message *diagnostics.Message, args ...any) *ast.Diagnostic { compilerOptionsProperty := getCompilerOptionsPropertySyntax() var diag *ast.Diagnostic if compilerOptionsProperty != nil { diag = tsoptions.CreateDiagnosticForNodeInSourceFile(sourceFile(), compilerOptionsProperty.Name(), message, args...) } else { diag = ast.NewCompilerDiagnostic(message, args...) } p.programDiagnostics = append(p.programDiagnostics, diag) return diag } createDiagnosticForOption := func(onKey bool, option1 string, option2 string, message *diagnostics.Message, args ...any) *ast.Diagnostic { diag := createOptionDiagnosticInObjectLiteralSyntax(getCompilerOptionsObjectLiteralSyntax(), onKey, option1, option2, message, args...) if diag == nil { diag = createCompilerOptionsDiagnostic(message, args...) } return diag } createDiagnosticForOptionName := func(message *diagnostics.Message, option1 string, option2 string, args ...any) { newArgs := make([]any, 0, len(args)+2) newArgs = append(newArgs, option1, option2) newArgs = append(newArgs, args...) createDiagnosticForOption(true /*onKey*/, option1, option2, message, newArgs...) } createOptionValueDiagnostic := func(option1 string, message *diagnostics.Message, args ...any) { createDiagnosticForOption(false /*onKey*/, option1, "", message, args...) } createRemovedOptionDiagnostic := func(name string, value string, useInstead string) { var message *diagnostics.Message var args []any if value == "" { message = diagnostics.Option_0_has_been_removed_Please_remove_it_from_your_configuration args = []any{name} } else { message = diagnostics.Option_0_1_has_been_removed_Please_remove_it_from_your_configuration args = []any{name, value} } diag := createDiagnosticForOption(value == "", name, "", message, args...) if useInstead != "" { diag.AddMessageChain(ast.NewCompilerDiagnostic(diagnostics.Use_0_instead, useInstead)) } } // Removed in TS7 if options.BaseUrl != "" { // BaseUrl will have been turned absolute by this point. var useInstead string if configFilePath() != "" { relative := tspath.GetRelativePathFromFile(configFilePath(), options.BaseUrl, p.comparePathsOptions) if !(strings.HasPrefix(relative, "./") || strings.HasPrefix(relative, "../")) { relative = "./" + relative } suggestion := tspath.CombinePaths(relative, "*") useInstead = fmt.Sprintf(`"paths": {"*": [%s]}`, core.Must(json.Marshal(suggestion))) } createRemovedOptionDiagnostic("baseUrl", "", useInstead) } if options.OutFile != "" { createRemovedOptionDiagnostic("outFile", "", "") } if options.Target == core.ScriptTargetES5 { createRemovedOptionDiagnostic("target", "ES5", "") } if options.Module == core.ModuleKindAMD { createRemovedOptionDiagnostic("module", "AMD", "") } if options.Module == core.ModuleKindSystem { createRemovedOptionDiagnostic("module", "System", "") } if options.Module == core.ModuleKindUMD { createRemovedOptionDiagnostic("module", "UMD", "") } if options.ModuleResolution == core.ModuleResolutionKindClassic { createRemovedOptionDiagnostic("moduleResolution", "Classic", "") } if options.AlwaysStrict.IsFalse() { createRemovedOptionDiagnostic("alwaysStrict", "false", "") } if options.ESModuleInterop.IsFalse() { createRemovedOptionDiagnostic("esModuleInterop", "false", "") } if options.AllowSyntheticDefaultImports.IsFalse() { createRemovedOptionDiagnostic("allowSyntheticDefaultImports", "false", "") } if options.ModuleResolution == core.ModuleResolutionKindNode10 { createRemovedOptionDiagnostic("moduleResolution", "node10", "") } if !options.DownlevelIteration.IsUnknown() { createRemovedOptionDiagnostic("downlevelIteration", "", "") } if options.StrictPropertyInitialization.IsTrue() && !options.GetStrictOptionValue(options.StrictNullChecks) { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks") } if options.ExactOptionalPropertyTypes.IsTrue() && !options.GetStrictOptionValue(options.StrictNullChecks) { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "exactOptionalPropertyTypes", "strictNullChecks") } if options.IsolatedDeclarations.IsTrue() { if options.GetAllowJS() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "isolatedDeclarations") } if !options.GetEmitDeclarations() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "isolatedDeclarations", "declaration", "composite") } } if options.InlineSourceMap.IsTrue() { if options.SourceMap.IsTrue() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap") } if options.MapRoot != "" { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap") } } if options.Composite.IsTrue() { if options.Declaration.IsFalse() { createDiagnosticForOptionName(diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration", "") } if options.Incremental.IsFalse() { createDiagnosticForOptionName(diagnostics.Composite_projects_may_not_disable_incremental_compilation, "declaration", "") } } if options.TsBuildInfoFile == "" && options.Incremental.IsTrue() && options.ConfigFilePath == "" { createCompilerOptionsDiagnostic(diagnostics.Option_incremental_is_only_valid_with_a_known_configuration_file_like_tsconfig_json_or_when_tsBuildInfoFile_is_explicitly_provided) } p.verifyProjectReferences() if options.Composite.IsTrue() { var rootPaths collections.Set[tspath.Path] for _, fileName := range p.opts.Config.FileNames() { rootPaths.Add(p.toPath(fileName)) } for _, file := range p.files { if sourceFileMayBeEmitted(file, p, false) && !rootPaths.Has(file.Path()) { p.includeProcessor.addProcessingDiagnostic(&processingDiagnostic{ kind: processingDiagnosticKindExplainingFileInclude, data: &includeExplainingDiagnostic{ file: file.Path(), message: diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, args: []any{file.FileName(), configFilePath()}, }, }) } } } forEachOptionPathsSyntax := func(callback func(*ast.PropertyAssignment) *ast.Diagnostic) *ast.Diagnostic { return tsoptions.ForEachPropertyAssignment(getCompilerOptionsObjectLiteralSyntax(), "paths", callback) } createDiagnosticForOptionPaths := func(onKey bool, key string, message *diagnostics.Message, args ...any) *ast.Diagnostic { diag := forEachOptionPathsSyntax(func(pathProp *ast.PropertyAssignment) *ast.Diagnostic { if ast.IsObjectLiteralExpression(pathProp.Initializer) { return createOptionDiagnosticInObjectLiteralSyntax(pathProp.Initializer.AsObjectLiteralExpression(), onKey, key, "", message, args...) } return nil }) if diag == nil { diag = createCompilerOptionsDiagnostic(message, args...) } return diag } createDiagnosticForOptionPathKeyValue := func(key string, valueIndex int, message *diagnostics.Message, args ...any) *ast.Diagnostic { diag := forEachOptionPathsSyntax(func(pathProp *ast.PropertyAssignment) *ast.Diagnostic { if ast.IsObjectLiteralExpression(pathProp.Initializer) { return tsoptions.ForEachPropertyAssignment(pathProp.Initializer.AsObjectLiteralExpression(), key, func(keyProps *ast.PropertyAssignment) *ast.Diagnostic { initializer := keyProps.Initializer if ast.IsArrayLiteralExpression(initializer) { elements := initializer.ElementList() if elements != nil && len(elements.Nodes) > valueIndex { diag := tsoptions.CreateDiagnosticForNodeInSourceFile(sourceFile(), elements.Nodes[valueIndex], message, args...) p.programDiagnostics = append(p.programDiagnostics, diag) return diag } } return nil }) } return nil }) if diag == nil { diag = createCompilerOptionsDiagnostic(message, args...) } return diag } for key, value := range options.Paths.Entries() { // !!! This code does not handle cases where where the path mappings have the wrong types, // as that information is mostly lost during the parsing process. if !hasZeroOrOneAsteriskCharacter(key) { createDiagnosticForOptionPaths(true /*onKey*/, key, diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key) } if value == nil { createDiagnosticForOptionPaths(false /*onKey*/, key, diagnostics.Substitutions_for_pattern_0_should_be_an_array, key) } else if len(value) == 0 { createDiagnosticForOptionPaths(false /*onKey*/, key, diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key) } for i, subst := range value { if !hasZeroOrOneAsteriskCharacter(subst) { createDiagnosticForOptionPathKeyValue(key, i, diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key) } if !tspath.PathIsRelative(subst) && !tspath.PathIsAbsolute(subst) { createDiagnosticForOptionPathKeyValue(key, i, diagnostics.Non_relative_paths_are_not_allowed_Did_you_forget_a_leading_Slash) } } } if options.SourceMap.IsFalseOrUnknown() && options.InlineSourceMap.IsFalseOrUnknown() { if options.InlineSources.IsTrue() { createDiagnosticForOptionName(diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources", "") } if options.SourceRoot != "" { createDiagnosticForOptionName(diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot", "") } } if options.MapRoot != "" && !(options.SourceMap.IsTrue() || options.DeclarationMap.IsTrue()) { // Error to specify --mapRoot without --sourcemap createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap") } if options.DeclarationDir != "" { if !options.GetEmitDeclarations() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite") } } if options.DeclarationMap.IsTrue() && !options.GetEmitDeclarations() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite") } if options.Lib != nil && options.NoLib.IsTrue() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib") } if options.IsolatedModules.IsTrue() || options.VerbatimModuleSyntax.IsTrue() { if options.PreserveConstEnums.IsFalse() { createDiagnosticForOptionName(diagnostics.Option_preserveConstEnums_cannot_be_disabled_when_0_is_enabled, core.IfElse(options.VerbatimModuleSyntax.IsTrue(), "verbatimModuleSyntax", "isolatedModules"), "preserveConstEnums") } } if options.OutDir != "" || options.RootDir != "" || options.SourceRoot != "" || options.MapRoot != "" || (options.GetEmitDeclarations() && options.DeclarationDir != "") { // !!! sheetal checkSourceFilesBelongToPath - for root Dir and configFile - explaining why file is in the program dir := p.CommonSourceDirectory() if options.OutDir != "" && dir == "" && core.Some(p.files, func(f *ast.SourceFile) bool { return tspath.GetRootLength(f.FileName()) > 1 }) { createDiagnosticForOptionName(diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files, "outDir", "") } } if !options.NoEmit.IsTrue() && !options.Composite.IsTrue() && options.RootDir == "" && options.ConfigFilePath != "" && (options.OutDir != "" || (options.GetEmitDeclarations() && options.DeclarationDir != "") || options.OutFile != "") { // Check if rootDir inferred changed and issue diagnostic dir := p.CommonSourceDirectory() var emittedFiles []string for _, file := range p.files { if !file.IsDeclarationFile && sourceFileMayBeEmitted(file, p, false) { emittedFiles = append(emittedFiles, file.FileName()) } } dir59 := outputpaths.GetComputedCommonSourceDirectory(emittedFiles, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames()) if dir59 != "" && tspath.GetCanonicalFileName(dir, p.UseCaseSensitiveFileNames()) != tspath.GetCanonicalFileName(dir59, p.UseCaseSensitiveFileNames()) { // change in layout var option1 string if options.OutFile != "" { option1 = "outFile" } else if options.OutDir != "" { option1 = "outDir" } else { option1 = "declarationDir" } var option2 string if options.OutFile == "" && options.OutDir != "" { option2 = "declarationDir" } diag := createDiagnosticForOption( true, /*onKey*/ option1, option2, diagnostics.The_common_source_directory_of_0_is_1_The_rootDir_setting_must_be_explicitly_set_to_this_or_another_path_to_adjust_your_output_s_file_layout, tspath.GetBaseFileName(options.ConfigFilePath), tspath.GetRelativePathFromFile(options.ConfigFilePath, dir59, p.comparePathsOptions), ) diag.AddMessageChain(ast.NewCompilerDiagnostic(diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashts6_for_migration_information)) } } if options.CheckJs.IsTrue() && !options.GetAllowJS() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs") } if options.EmitDeclarationOnly.IsTrue() { if !options.GetEmitDeclarations() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "emitDeclarationOnly", "declaration", "composite") } } if options.EmitDecoratorMetadata.IsTrue() && options.ExperimentalDecorators.IsFalseOrUnknown() { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators") } if options.JsxFactory != "" { if options.ReactNamespace != "" { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory") } if options.Jsx == core.JsxEmitReactJSX || options.Jsx == core.JsxEmitReactJSXDev { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFactory", options.Jsx.String()) } if parser.ParseIsolatedEntityName(options.JsxFactory) == nil { createOptionValueDiagnostic("jsxFactory", diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.JsxFactory) } } else if options.ReactNamespace != "" && !scanner.IsIdentifierText(options.ReactNamespace, core.LanguageVariantStandard) { createOptionValueDiagnostic("reactNamespace", diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.ReactNamespace) } if options.JsxFragmentFactory != "" { if options.JsxFactory == "" { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "jsxFragmentFactory", "jsxFactory") } if options.Jsx == core.JsxEmitReactJSX || options.Jsx == core.JsxEmitReactJSXDev { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFragmentFactory", options.Jsx.String()) } if parser.ParseIsolatedEntityName(options.JsxFragmentFactory) == nil { createOptionValueDiagnostic("jsxFragmentFactory", diagnostics.Invalid_value_for_jsxFragmentFactory_0_is_not_a_valid_identifier_or_qualified_name, options.JsxFragmentFactory) } } if options.ReactNamespace != "" { if options.Jsx == core.JsxEmitReactJSX || options.Jsx == core.JsxEmitReactJSXDev { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "reactNamespace", options.Jsx.String()) } } if options.JsxImportSource != "" { if options.Jsx == core.JsxEmitReact { createDiagnosticForOptionName(diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxImportSource", options.Jsx.String()) } } moduleKind := options.GetEmitModuleKind() if options.AllowImportingTsExtensions.IsTrue() && !(options.NoEmit.IsTrue() || options.EmitDeclarationOnly.IsTrue() || options.RewriteRelativeImportExtensions.IsTrue()) { createOptionValueDiagnostic("allowImportingTsExtensions", diagnostics.Option_allowImportingTsExtensions_can_only_be_used_when_one_of_noEmit_emitDeclarationOnly_or_rewriteRelativeImportExtensions_is_set) } moduleResolution := options.GetModuleResolutionKind() if options.ResolvePackageJsonExports.IsTrue() && !moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution) { createDiagnosticForOptionName(diagnostics.Option_0_can_only_be_used_when_moduleResolution_is_set_to_node16_nodenext_or_bundler, "resolvePackageJsonExports", "") } if options.ResolvePackageJsonImports.IsTrue() && !moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution) { createDiagnosticForOptionName(diagnostics.Option_0_can_only_be_used_when_moduleResolution_is_set_to_node16_nodenext_or_bundler, "resolvePackageJsonImports", "") } if options.CustomConditions != nil && !moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution) { createDiagnosticForOptionName(diagnostics.Option_0_can_only_be_used_when_moduleResolution_is_set_to_node16_nodenext_or_bundler, "customConditions", "") } if moduleResolution == core.ModuleResolutionKindBundler && !emitModuleKindIsNonNodeESM(moduleKind) && moduleKind != core.ModuleKindPreserve && moduleKind != core.ModuleKindCommonJS { createOptionValueDiagnostic("moduleResolution", diagnostics.Option_0_can_only_be_used_when_module_is_set_to_preserve_commonjs_or_es2015_or_later, "bundler") } if core.ModuleKindNode16 <= moduleKind && moduleKind <= core.ModuleKindNodeNext && !(core.ModuleResolutionKindNode16 <= moduleResolution && moduleResolution <= core.ModuleResolutionKindNodeNext) { moduleKindName := moduleKind.String() var moduleResolutionName string if v, ok := core.ModuleKindToModuleResolutionKind[moduleKind]; ok { moduleResolutionName = v.String() } else { moduleResolutionName = "Node16" } createOptionValueDiagnostic("moduleResolution", diagnostics.Option_moduleResolution_must_be_set_to_0_or_left_unspecified_when_option_module_is_set_to_1, moduleResolutionName, moduleKindName) } else if core.ModuleResolutionKindNode16 <= moduleResolution && moduleResolution <= core.ModuleResolutionKindNodeNext && !(core.ModuleKindNode16 <= moduleKind && moduleKind <= core.ModuleKindNodeNext) { moduleResolutionName := moduleResolution.String() createOptionValueDiagnostic("module", diagnostics.Option_module_must_be_set_to_0_when_option_moduleResolution_is_set_to_1, moduleResolutionName, moduleResolutionName) } // !!! The below needs filesByName, which is not equivalent to p.filesByPath. // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files if !options.NoEmit.IsTrue() && !options.SuppressOutputPathCheck.IsTrue() { var emitFilesSeen collections.Set[string] // Verify that all the emit files are unique and don't overwrite input files verifyEmitFilePath := func(emitFileName string) { if emitFileName != "" { emitFilePath := p.toPath(emitFileName) // Report error if the output overwrites input file if _, ok := p.filesByPath[emitFilePath]; ok { diag := ast.NewCompilerDiagnostic(diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName) if configFilePath() == "" { // The program is from either an inferred project or an external project diag.AddMessageChain(ast.NewCompilerDiagnostic(diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig)) } p.blockEmittingOfFile(emitFileName, diag) } var emitFileKey string if !p.Host().FS().UseCaseSensitiveFileNames() { emitFileKey = tspath.ToFileNameLowerCase(string(emitFilePath)) } else { emitFileKey = string(emitFilePath) } // Report error if multiple files write into same file if emitFilesSeen.Has(emitFileKey) { // Already seen the same emit file - report error p.blockEmittingOfFile(emitFileName, ast.NewCompilerDiagnostic(diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files, emitFileName)) } else { emitFilesSeen.Add(emitFileKey) } } } outputpaths.ForEachEmittedFile(p, options, func(emitFileNames *outputpaths.OutputPaths, sourceFile *ast.SourceFile) bool { verifyEmitFilePath(emitFileNames.JsFilePath()) verifyEmitFilePath(emitFileNames.SourceMapFilePath()) verifyEmitFilePath(emitFileNames.DeclarationFilePath()) verifyEmitFilePath(emitFileNames.DeclarationMapPath()) return false }, p.getSourceFilesToEmit(nil, false), false) verifyEmitFilePath(p.opts.Config.GetBuildInfoFileName()) } } func (p *Program) blockEmittingOfFile(emitFileName string, diag *ast.Diagnostic) { p.hasEmitBlockingDiagnostics.Add(p.toPath(emitFileName)) p.programDiagnostics = append(p.programDiagnostics, diag) } func (p *Program) IsEmitBlocked(emitFileName string) bool { return p.hasEmitBlockingDiagnostics.Has(p.toPath(emitFileName)) } func (p *Program) verifyProjectReferences() { buildInfoFileName := core.IfElse(!p.Options().SuppressOutputPathCheck.IsTrue(), p.opts.Config.GetBuildInfoFileName(), "") createDiagnosticForReference := func(config *tsoptions.ParsedCommandLine, index int, message *diagnostics.Message, args ...any) { diag := tsoptions.CreateDiagnosticAtReferenceSyntax(config, index, message, args...) if diag == nil { diag = ast.NewCompilerDiagnostic(message, args...) } p.programDiagnostics = append(p.programDiagnostics, diag) } p.RangeResolvedProjectReference(func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool { ref := parent.ProjectReferences()[index] // !!! Deprecated in 5.0 and removed since 5.5 // verifyRemovedProjectReference(ref, parent, index); if config == nil { createDiagnosticForReference(parent, index, diagnostics.File_0_not_found, ref.Path) return true } refOptions := config.CompilerOptions() if !refOptions.Composite.IsTrue() || refOptions.NoEmit.IsTrue() { if len(parent.FileNames()) > 0 { if !refOptions.Composite.IsTrue() { createDiagnosticForReference(parent, index, diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.Path) } if refOptions.NoEmit.IsTrue() { createDiagnosticForReference(parent, index, diagnostics.Referenced_project_0_may_not_disable_emit, ref.Path) } } } if buildInfoFileName != "" && buildInfoFileName == config.GetBuildInfoFileName() { createDiagnosticForReference(parent, index, diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, buildInfoFileName, ref.Path) p.hasEmitBlockingDiagnostics.Add(p.toPath(buildInfoFileName)) } return true }) } func hasZeroOrOneAsteriskCharacter(str string) bool { seenAsterisk := false for _, ch := range str { if ch == '*' { if !seenAsterisk { seenAsterisk = true } else { // have already seen asterisk return false } } } return true } func moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution core.ModuleResolutionKind) bool { return moduleResolution >= core.ModuleResolutionKindNode16 && moduleResolution <= core.ModuleResolutionKindNodeNext || moduleResolution == core.ModuleResolutionKindBundler } func emitModuleKindIsNonNodeESM(moduleKind core.ModuleKind) bool { return moduleKind >= core.ModuleKindES2015 && moduleKind <= core.ModuleKindESNext } func (p *Program) GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic { if len(p.files) == 0 { return nil } if p.compilerCheckerPool != nil { return p.compilerCheckerPool.GetGlobalDiagnostics() } // For external pools (project system), global diagnostics are collected // incrementally as checkers are used, not via a bulk query. return nil } func (p *Program) GetDeclarationDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { return p.collectDiagnostics(ctx, sourceFile, true /*concurrent*/, p.getDeclarationDiagnosticsForFile) } func FilterNoEmitSemanticDiagnostics(diagnostics []*ast.Diagnostic, options *core.CompilerOptions) []*ast.Diagnostic { if !options.NoEmit.IsTrue() { return diagnostics } return core.Filter(diagnostics, func(d *ast.Diagnostic) bool { return !d.SkippedOnNoEmit() }) } func (p *Program) getSemanticDiagnosticsWithChecker(ctx context.Context, c *checker.Checker, sourceFile *ast.SourceFile) []*ast.Diagnostic { return core.Concatenate( FilterNoEmitSemanticDiagnostics(p.getBindAndCheckDiagnosticsWithChecker(ctx, c, sourceFile), p.Options()), p.GetIncludeProcessorDiagnostics(sourceFile), ) } // getBindAndCheckDiagnosticsWithChecker gets semantic diagnostics for a single file using a // caller-provided checker, including bind diagnostics, checker diagnostics, and handling // of @ts-ignore/@ts-expect-error directives. func (p *Program) getBindAndCheckDiagnosticsWithChecker(ctx context.Context, fileChecker *checker.Checker, sourceFile *ast.SourceFile) []*ast.Diagnostic { compilerOptions := p.Options() if p.SkipTypeChecking(sourceFile, false) { return nil } // Checker creation forces binding, so bind diagnostics will be populated. diags := slices.Clip(sourceFile.BindDiagnostics()) diags = append(diags, fileChecker.GetDiagnostics(ctx, sourceFile)...) isPlainJS := ast.IsPlainJSFile(sourceFile, compilerOptions.CheckJs) if isPlainJS { return core.Filter(diags, func(d *ast.Diagnostic) bool { return plainJSErrors.Has(d.Code()) }) } isJS := sourceFile.ScriptKind == core.ScriptKindJS || sourceFile.ScriptKind == core.ScriptKindJSX isCheckJS := isJS && ast.IsCheckJSEnabledForFile(sourceFile, compilerOptions) if isCheckJS { diags = append(diags, sourceFile.JSDocDiagnostics()...) } filtered, directivesByLine := p.getDiagnosticsWithPrecedingDirectives(sourceFile, diags) for _, directive := range directivesByLine { // Above we changed all used directive kinds to @ts-ignore, so any @ts-expect-error directives that // remain are unused and thus errors. if directive.Kind == ast.CommentDirectiveKindExpectError { filtered = append(filtered, ast.NewDiagnostic(sourceFile, directive.Loc, diagnostics.Unused_ts_expect_error_directive)) } } return filtered } func (p *Program) getDiagnosticsWithPrecedingDirectives(sourceFile *ast.SourceFile, diags []*ast.Diagnostic) ([]*ast.Diagnostic, map[int]ast.CommentDirective) { if len(sourceFile.CommentDirectives) == 0 { return diags, nil } // Build map of directives by line number directivesByLine := make(map[int]ast.CommentDirective) for _, directive := range sourceFile.CommentDirectives { line := scanner.GetECMALineOfPosition(sourceFile, directive.Loc.Pos()) directivesByLine[line] = directive } lineStarts := scanner.GetECMALineStarts(sourceFile) filtered := make([]*ast.Diagnostic, 0, len(diags)) for _, diagnostic := range diags { ignoreDiagnostic := false for line := scanner.ComputeLineOfPosition(lineStarts, diagnostic.Pos()) - 1; line >= 0; line-- { // If line contains a @ts-ignore or @ts-expect-error directive, ignore this diagnostic and change // the directive kind to @ts-ignore to indicate it was used. if directive, ok := directivesByLine[line]; ok { ignoreDiagnostic = true directive.Kind = ast.CommentDirectiveKindIgnore directivesByLine[line] = directive break } // Stop searching backwards when we encounter a line that isn't blank or a comment. if !isCommentOrBlankLine(sourceFile.Text(), int(lineStarts[line])) { break } } if !ignoreDiagnostic { filtered = append(filtered, diagnostic) } } return filtered, directivesByLine } func (p *Program) getDeclarationDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { if sourceFile.IsDeclarationFile { return []*ast.Diagnostic{} } if cached, ok := p.declarationDiagnosticCache.Load(sourceFile); ok { return cached } host, done := newEmitHost(ctx, p, sourceFile) defer done() diagnostics := getDeclarationDiagnostics(host, sourceFile) diagnostics, _ = p.declarationDiagnosticCache.LoadOrStore(sourceFile, diagnostics) return diagnostics } func (p *Program) getSuggestionDiagnosticsWithChecker(ctx context.Context, fileChecker *checker.Checker, sourceFile *ast.SourceFile) []*ast.Diagnostic { if p.SkipTypeChecking(sourceFile, false) { return nil } // Checker creation forces binding, so bind suggestion diagnostics will be populated. diags := slices.Clip(sourceFile.BindSuggestionDiagnostics) diags = append(diags, fileChecker.GetSuggestionDiagnostics(ctx, sourceFile)...) return diags } func isCommentOrBlankLine(text string, pos int) bool { for pos < len(text) && (text[pos] == ' ' || text[pos] == '\t') { pos++ } return pos == len(text) || pos < len(text) && (text[pos] == '\r' || text[pos] == '\n') || pos+1 < len(text) && text[pos] == '/' && text[pos+1] == '/' } func SortAndDeduplicateDiagnostics(diagnostics []*ast.Diagnostic) []*ast.Diagnostic { diagnostics = slices.Clone(diagnostics) slices.SortFunc(diagnostics, ast.CompareDiagnostics) return compactAndMergeRelatedInfos(diagnostics) } // Remove duplicate diagnostics and, for sequences of diagnostics that differ only by related information, // create a single diagnostic with sorted and deduplicated related information. func compactAndMergeRelatedInfos(diagnostics []*ast.Diagnostic) []*ast.Diagnostic { if len(diagnostics) < 2 { return diagnostics } i := 0 j := 0 for i < len(diagnostics) { d := diagnostics[i] n := 1 for i+n < len(diagnostics) && ast.EqualDiagnosticsNoRelatedInfo(d, diagnostics[i+n]) { n++ } if n > 1 { var relatedInfos []*ast.Diagnostic for k := range n { relatedInfos = append(relatedInfos, diagnostics[i+k].RelatedInformation()...) } if relatedInfos != nil { slices.SortFunc(relatedInfos, ast.CompareDiagnostics) relatedInfos = slices.CompactFunc(relatedInfos, ast.EqualDiagnostics) d = d.Clone().SetRelatedInfo(relatedInfos) } } diagnostics[j] = d i += n j++ } clear(diagnostics[j:]) return diagnostics[:j] } func (p *Program) LineCount() int { var count int for _, file := range p.files { count += len(file.ECMALineMap()) } return count } func (p *Program) IdentifierCount() int { var count int for _, file := range p.files { count += file.IdentifierCount } return count } func (p *Program) SymbolCount() int { var count int for _, file := range p.files { count += file.SymbolCount } var val atomic.Uint32 val.Store(uint32(count)) p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { val.Add(c.SymbolCount) }) return int(val.Load()) } func (p *Program) TypeCount() int { var val atomic.Uint32 p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { val.Add(c.TypeCount) }) return int(val.Load()) } func (p *Program) InstantiationCount() int { var val atomic.Uint32 p.ForEachCheckerParallel(func(_ int, c *checker.Checker) { val.Add(c.TotalInstantiationCount) }) return int(val.Load()) } func (p *Program) Program() *Program { return p } func (p *Program) GetSourceFileMetaData(path tspath.Path) ast.SourceFileMetaData { return p.sourceFileMetaDatas[path] } func (p *Program) GetEmitModuleFormatOfFile(sourceFile ast.HasFileName) core.ModuleKind { return ast.GetEmitModuleFormatOfFileWorker(sourceFile.FileName(), p.projectReferenceFileMapper.getCompilerOptionsForFile(sourceFile), p.GetSourceFileMetaData(sourceFile.Path())) } func (p *Program) GetEmitSyntaxForUsageLocation(sourceFile ast.HasFileName, location *ast.StringLiteralLike) core.ResolutionMode { return getEmitSyntaxForUsageLocationWorker(sourceFile.FileName(), p.sourceFileMetaDatas[sourceFile.Path()], location, p.projectReferenceFileMapper.getCompilerOptionsForFile(sourceFile)) } func (p *Program) GetImpliedNodeFormatForEmit(sourceFile ast.HasFileName) core.ResolutionMode { return ast.GetImpliedNodeFormatForEmitWorker(sourceFile.FileName(), p.projectReferenceFileMapper.getCompilerOptionsForFile(sourceFile).GetEmitModuleKind(), p.GetSourceFileMetaData(sourceFile.Path())) } func (p *Program) GetModeForUsageLocation(sourceFile ast.HasFileName, location *ast.StringLiteralLike) core.ResolutionMode { return getModeForUsageLocation(sourceFile.FileName(), p.sourceFileMetaDatas[sourceFile.Path()], location, p.projectReferenceFileMapper.getCompilerOptionsForFile(sourceFile)) } func (p *Program) GetDefaultResolutionModeForFile(sourceFile ast.HasFileName) core.ResolutionMode { return getDefaultResolutionModeForFile(sourceFile.FileName(), p.sourceFileMetaDatas[sourceFile.Path()], p.projectReferenceFileMapper.getCompilerOptionsForFile(sourceFile)) } func (p *Program) IsSourceFileDefaultLibrary(path tspath.Path) bool { _, ok := p.libFiles[path] return ok } func (p *Program) IsGlobalTypingsFile(fileName string) bool { if !tspath.IsDeclarationFileName(fileName) { return false } return tspath.ContainsPath(p.GetGlobalTypingsCacheLocation(), fileName, p.comparePathsOptions) } func (p *Program) GetDefaultLibFile(path tspath.Path) *LibFile { if libFile, ok := p.libFiles[path]; ok { return libFile } return nil } func (p *Program) CommonSourceDirectory() string { p.commonSourceDirectoryOnce.Do(func() { files := func() []string { return core.MapFiltered(p.files, func(file *ast.SourceFile) (string, bool) { return file.FileName(), sourceFileMayBeEmitted(file, p, false /*forceDtsEmit*/) && !file.IsDeclarationFile }) } p.commonSourceDirectory = outputpaths.GetCommonSourceDirectory( p.Options(), files, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames(), p.checkSourceFilesBelongToPath, ) }) return p.commonSourceDirectory } func (p *Program) checkSourceFilesBelongToPath(sourceFiles []string, rootDirectory string) bool { allFilesBelongToPath := true for _, file := range sourceFiles { absoluteSourceFilePath := tspath.GetCanonicalFileName(tspath.GetNormalizedAbsolutePath(file, p.GetCurrentDirectory()), p.UseCaseSensitiveFileNames()) if !tspath.ContainsPath(rootDirectory, file, p.comparePathsOptions) { p.includeProcessor.addProcessingDiagnostic(&processingDiagnostic{ kind: processingDiagnosticKindExplainingFileInclude, data: &includeExplainingDiagnostic{ file: tspath.Path(absoluteSourceFilePath), message: diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, args: []any{file, rootDirectory}, }, }) allFilesBelongToPath = false } } return allFilesBelongToPath } type WriteFileData struct { SourceMapUrlPos int BuildInfo any Diagnostics []*ast.Diagnostic SkippedDtsWrite bool } type WriteFile func(fileName string, text string, data *WriteFileData) error type EmitOptions struct { TargetSourceFile *ast.SourceFile // Single file to emit. If `nil`, emits all files EmitOnly EmitOnly WriteFile WriteFile } type EmitResult struct { EmitSkipped bool Diagnostics []*ast.Diagnostic // Contains declaration emit diagnostics EmittedFiles []string // Array of files the compiler wrote to disk SourceMaps []*SourceMapEmitResult // Array of sourceMapData if compiler emitted sourcemaps } type SourceMapEmitResult struct { InputSourceFileNames []string // Input source file (which one can use on program to get the file), 1:1 mapping with the sourceMap.sources list SourceMap *sourcemap.RawSourceMap GeneratedFile string } func (p *Program) Emit(ctx context.Context, options EmitOptions) *EmitResult { if tr := p.opts.Tracing; tr != nil { defer tr.Push(tracing.PhaseEmit, "emit", nil, true)() } if options.EmitOnly != EmitOnlyForcedDts { result := HandleNoEmitOnError( ctx, p, options.TargetSourceFile, ) if result != nil || ctx.Err() != nil { return result } } newLine := p.Options().NewLine.GetNewLineCharacter() writerPool := &sync.Pool{ New: func() any { return printer.NewTextWriter(newLine, 0) }, } wg := core.NewWorkGroup(p.SingleThreaded()) var emitters []*emitter sourceFiles := p.getSourceFilesToEmit(options.TargetSourceFile, options.EmitOnly == EmitOnlyForcedDts) for _, sourceFile := range sourceFiles { emitter := &emitter{ writer: nil, sourceFile: sourceFile, emitOnly: options.EmitOnly, writeFile: options.WriteFile, tr: p.opts.Tracing, } emitters = append(emitters, emitter) wg.Queue(func() { host, done := newEmitHost(ctx, p, sourceFile) defer done() emitter.host = host // take an unused writer writer := writerPool.Get().(printer.EmitTextWriter) writer.Clear() // attach writer and perform emit emitter.writer = writer emitter.paths = outputpaths.GetOutputPathsFor(sourceFile, host.Options(), host, options.EmitOnly == EmitOnlyForcedDts) emitter.emit() emitter.writer = nil // put the writer back in the pool writerPool.Put(writer) }) } // wait for emit to complete wg.RunAndWait() // collect results from emit, preserving input order return CombineEmitResults(core.Map(emitters, func(e *emitter) *EmitResult { return &e.emitResult })) } func CombineEmitResults(results []*EmitResult) *EmitResult { result := &EmitResult{} for _, emitResult := range results { if emitResult == nil { continue // Skip nil results } if emitResult.EmitSkipped { result.EmitSkipped = true } result.Diagnostics = append(result.Diagnostics, emitResult.Diagnostics...) result.EmittedFiles = append(result.EmittedFiles, emitResult.EmittedFiles...) if emitResult.SourceMaps != nil { result.SourceMaps = append(result.SourceMaps, emitResult.SourceMaps...) } } return result } type ProgramLike interface { Options() *core.CompilerOptions GetSourceFile(path string) *ast.SourceFile GetSourceFiles() []*ast.SourceFile GetConfigFileParsingDiagnostics() []*ast.Diagnostic GetSyntacticDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic GetBindDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic GetProgramDiagnostics() []*ast.Diagnostic GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic GetSemanticDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic GetDeclarationDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic GetSuggestionDiagnostics(ctx context.Context, file *ast.SourceFile) []*ast.Diagnostic Emit(ctx context.Context, options EmitOptions) *EmitResult CommonSourceDirectory() string IsSourceFileDefaultLibrary(path tspath.Path) bool Program() *Program } func HandleNoEmitOnError(ctx context.Context, program ProgramLike, file *ast.SourceFile) *EmitResult { if !program.Options().NoEmitOnError.IsTrue() { return nil // No emit on error is not set, so we can proceed with emitting } diagnostics := GetDiagnosticsOfAnyProgram( ctx, program, file, true, program.GetBindDiagnostics, program.GetSemanticDiagnostics, ) if len(diagnostics) == 0 { return nil // No diagnostics, so we can proceed with emitting } return &EmitResult{ Diagnostics: diagnostics, EmitSkipped: true, } } func GetDiagnosticsOfAnyProgram( ctx context.Context, program ProgramLike, file *ast.SourceFile, skipNoEmitCheckForDtsDiagnostics bool, getBindDiagnostics func(context.Context, *ast.SourceFile) []*ast.Diagnostic, getSemanticDiagnostics func(context.Context, *ast.SourceFile) []*ast.Diagnostic, ) []*ast.Diagnostic { allDiagnostics := slices.Clip(program.GetConfigFileParsingDiagnostics()) configFileParsingDiagnosticsLength := len(allDiagnostics) allDiagnostics = append(allDiagnostics, program.GetSyntacticDiagnostics(ctx, file)...) // If we didn't have any syntactic errors, then also try getting the program (options), // global and semantic errors. if len(allDiagnostics) == configFileParsingDiagnosticsLength { allDiagnostics = append(allDiagnostics, program.GetProgramDiagnostics()...) // Do binding early so we can track the time. getBindDiagnostics(ctx, file) if program.Options().ListFilesOnly.IsFalseOrUnknown() { allDiagnostics = append(allDiagnostics, program.GetGlobalDiagnostics(ctx)...) if len(allDiagnostics) == configFileParsingDiagnosticsLength { allDiagnostics = append(allDiagnostics, getSemanticDiagnostics(ctx, file)...) // Ask for the global diagnostics again (they were empty above); we may have found new during checking, e.g. missing globals. allDiagnostics = append(allDiagnostics, program.GetGlobalDiagnostics(ctx)...) } if (skipNoEmitCheckForDtsDiagnostics || program.Options().NoEmit.IsTrue()) && program.Options().GetEmitDeclarations() && len(allDiagnostics) == configFileParsingDiagnosticsLength { allDiagnostics = append(allDiagnostics, program.GetDeclarationDiagnostics(ctx, file)...) } } } return allDiagnostics } func (p *Program) toPath(filename string) tspath.Path { return tspath.ToPath(filename, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames()) } func (p *Program) GetSourceFile(filename string) *ast.SourceFile { path := p.toPath(filename) return p.GetSourceFileByPath(path) } func (p *Program) GetSourceFileForResolvedModule(fileName string) *ast.SourceFile { file := p.GetSourceFile(fileName) if file == nil { filename := p.GetParseFileRedirect(fileName) if filename != "" { return p.GetSourceFile(filename) } } return file } func (p *Program) FilesByPath() map[tspath.Path]*ast.SourceFile { return p.filesByPath } func (p *Program) GetSourceFileByPath(path tspath.Path) *ast.SourceFile { return p.filesByPath[path] } func (p *Program) HasSameFileNames(other *Program) bool { return maps.EqualFunc(p.filesByPath, other.filesByPath, func(a, b *ast.SourceFile) bool { // checks for casing differences on case-insensitive file systems return a.FileName() == b.FileName() }) && maps.EqualFunc(p.redirectFilesByPath, other.redirectFilesByPath, func(a, b *redirectsFile) bool { return a.FileName() == b.FileName() }) } func (p *Program) GetSourceFiles() []*ast.SourceFile { return p.files } // Testing only func (p *Program) GetIncludeReasons() map[tspath.Path][]*FileIncludeReason { return p.includeProcessor.fileIncludeReasons } // Testing only func (p *Program) IsMissingPath(path tspath.Path) bool { return slices.ContainsFunc(p.missingFiles, func(missingPath string) bool { return p.toPath(missingPath) == path }) } func (p *Program) ExplainFiles(w io.Writer, locale locale.Locale) { toRelativeFileName := func(fileName string) string { return tspath.GetRelativePathFromDirectory(p.GetCurrentDirectory(), fileName, p.comparePathsOptions) } filesExplained := 0 explainFile := func(file ast.HasFileName) { fmt.Fprintln(w, toRelativeFileName(file.FileName())) for _, reason := range p.includeProcessor.fileIncludeReasons[file.Path()] { fmt.Fprintln(w, " ", reason.toDiagnostic(p, true).Localize(locale)) } for _, diag := range p.includeProcessor.explainRedirectAndImpliedFormat(p, file.Path(), toRelativeFileName) { fmt.Fprintln(w, " ", diag.Localize(locale)) } filesExplained++ } redirectFiles := slices.Collect(maps.Values(p.redirectFilesByPath)) slices.SortFunc(redirectFiles, func(a, b *redirectsFile) int { return a.index - b.index }) files := p.GetSourceFiles() sourceFileIndex := 0 explainSourceFiles := func(endIndex int) { for filesExplained < endIndex { explainFile(files[sourceFileIndex]) sourceFileIndex++ } } for _, redirectFile := range redirectFiles { // Explain all sourceFiles till we reach this redirectFile index explainSourceFiles(redirectFile.index) explainFile(redirectFile) } // Explain any remaining sourceFiles explainSourceFiles(len(files) + len(redirectFiles)) } func (p *Program) GetLibFileFromReference(ref *ast.FileReference) *ast.SourceFile { path, ok := tsoptions.GetLibFileName(ref.FileName) if !ok { return nil } if sourceFile, ok := p.filesByPath[tspath.Path(path)]; ok { return sourceFile } return nil } func (p *Program) GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeRef *ast.FileReference, sourceFile *ast.SourceFile) *module.ResolvedTypeReferenceDirective { if resolutions, ok := p.typeResolutionsInFile[sourceFile.Path()]; ok { if resolved, ok := resolutions[module.ModeAwareCacheKey{Name: typeRef.FileName, Mode: p.getModeForTypeReferenceDirectiveInFile(typeRef, sourceFile)}]; ok { return resolved } } return nil } func (p *Program) GetResolvedTypeReferenceDirectives() map[tspath.Path]module.ModeAwareCache[*module.ResolvedTypeReferenceDirective] { return p.typeResolutionsInFile } func (p *Program) getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, sourceFile *ast.SourceFile) core.ResolutionMode { if ref.ResolutionMode != core.ResolutionModeNone { return ref.ResolutionMode } return p.GetDefaultResolutionModeForFile(sourceFile) } func (p *Program) IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool { return p.sourceFilesFoundSearchingNodeModules.Has(file.Path()) } func (p *Program) GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node) { if result := p.jsxRuntimeImportSpecifiers[path]; result != nil { return result.moduleReference, result.specifier } return "", nil } func (p *Program) GetImportHelpersImportSpecifier(path tspath.Path) *ast.Node { return p.importHelpersImportSpecifiers[path] } func (p *Program) SourceFileMayBeEmitted(sourceFile *ast.SourceFile, forceDtsEmit bool) bool { return sourceFileMayBeEmitted(sourceFile, p, forceDtsEmit) } func (p *Program) ResolvedPackageNames() *collections.Set[string] { return p.collectPackageNames().resolved } func (p *Program) UnresolvedPackageNames() *collections.Set[string] { return p.collectPackageNames().unresolved } func (p *Program) DeepImportPackageNames() *collections.Set[string] { return p.collectPackageNames().deepImportPackages } func (p *Program) collectPackageNames() *packageNamesInfo { return p.packageNames.getValue(func() *packageNamesInfo { packageNames := &packageNamesInfo{&collections.Set[string]{}, &collections.Set[string]{}, &collections.Set[string]{}} for _, file := range p.files { if p.IsSourceFileDefaultLibrary(file.Path()) || p.IsSourceFileFromExternalLibrary(file) || strings.Contains(file.FileName(), "/node_modules/") { // Checking for /node_modules/ is a little imprecise, but ATA treats locally installed typings // as root files, which would not pass IsSourceFileFromExternalLibrary. continue } for _, imp := range file.Imports() { if tspath.IsExternalModuleNameRelative(imp.Text()) { continue } if resolvedModules, ok := p.resolvedModules[file.Path()]; ok { key := module.ModeAwareCacheKey{Name: imp.Text(), Mode: p.GetModeForUsageLocation(file, imp)} if resolvedModule, ok := resolvedModules[key]; ok && resolvedModule.IsResolved() { if !resolvedModule.IsExternalLibraryImport { continue } // Priority order for getting package name: // 1. PackageId.Name (requires both name and version in package.json) name := resolvedModule.PackageId.Name if name == "" { // 2. GetPackageScopeForPath - get name from package.json in the package directory if packageScope := p.resolver.GetPackageScopeForPath(resolvedModule.ResolvedFileName); packageScope != nil && packageScope.Exists() { if scopeName, ok := packageScope.Contents.Name.GetValue(); ok { name = scopeName } } } if name == "" { // 3. GetPackageNameFromDirectory - extract from node_modules path name = modulespecifiers.GetPackageNameFromDirectory(resolvedModule.ResolvedFileName) } // 4. If all fail, don't add empty string if name != "" { packageNames.resolved.Add(name) // Detect deep imports: subpath imports in packages without exports. // These are imports like "lodash/fp" where the package has no exports // map, so auto-import can only find them via recursive directory search. _, rest := module.ParsePackageName(imp.Text()) if rest != "" { if scope := p.resolver.GetPackageScopeForPath(resolvedModule.ResolvedFileName); scope != nil && scope.Exists() && !scope.Contents.Exports.IsPresent() { packageNames.deepImportPackages.Add(module.GetPackageNameFromTypesPackageName(name)) } } } continue } } packageNames.unresolved.Add(imp.Text()) } } return packageNames }) } func (p *Program) IsLibFile(sourceFile *ast.SourceFile) bool { _, ok := p.libFiles[sourceFile.Path()] return ok } func (p *Program) HasTSFile() bool { p.hasTSFileOnce.Do(func() { for _, file := range p.files { if tspath.HasImplementationTSFileExtension(file.FileName()) { p.hasTSFile = true break } } }) return p.hasTSFile } func (p *Program) GetSymlinkCache() *symlinks.KnownSymlinks { return p.knownSymlinks.getValue(func() *symlinks.KnownSymlinks { knownSymlinks := symlinks.NewKnownSymlink(p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames()) // Resolved modules store realpath information when they're resolved inside node_modules if len(p.resolvedModules) > 0 || len(p.typeResolutionsInFile) > 0 { knownSymlinks.SetSymlinksFromResolutions(p.ForEachResolvedModule, p.ForEachResolvedTypeReferenceDirective) } // Check other dependencies for symlinks var seenPackageJsons collections.Set[tspath.Path] for filePath, meta := range p.sourceFileMetaDatas { if meta.PackageJsonDirectory == "" || !p.SourceFileMayBeEmitted(p.GetSourceFileByPath(filePath), false) || !seenPackageJsons.AddIfAbsent(p.toPath(meta.PackageJsonDirectory)) { continue } packageJsonName := tspath.CombinePaths(meta.PackageJsonDirectory, "package.json") info := p.GetPackageJsonInfo(packageJsonName) if info.GetContents() == nil { continue } for dep := range info.GetContents().GetRuntimeDependencyNames().Keys() { // Skip work in common case: we already saved a symlink for this package directory // in the node_modules adjacent to this package.json possibleDirectoryPath := p.toPath(tspath.CombinePaths(meta.PackageJsonDirectory, "node_modules", dep)) if knownSymlinks.HasDirectory(possibleDirectoryPath) { continue } if !strings.HasPrefix(dep, "@types") { possibleTypesDirectoryPath := p.toPath(tspath.CombinePaths(meta.PackageJsonDirectory, "node_modules", module.GetTypesPackageName(dep))) if knownSymlinks.HasDirectory(possibleTypesDirectoryPath) { continue } } if packageResolution := p.resolver.ResolvePackageDirectory(dep, packageJsonName, core.ResolutionModeCommonJS, nil); packageResolution.IsResolved() { knownSymlinks.ProcessResolution( tspath.CombinePaths(packageResolution.OriginalPath, "package.json"), tspath.CombinePaths(packageResolution.ResolvedFileName, "package.json"), ) } } } return knownSymlinks }) } func (p *Program) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule { resolved, _ := p.resolver.ResolveModuleName(moduleName, containingFile, resolutionMode, nil) return resolved } func (p *Program) ForEachResolvedModule(callback func(resolution *module.ResolvedModule, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) { forEachResolution(p.resolvedModules, callback, file) } func (p *Program) ForEachResolvedTypeReferenceDirective(callback func(resolution *module.ResolvedTypeReferenceDirective, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) { forEachResolution(p.typeResolutionsInFile, callback, file) } func forEachResolution[T any](resolutionCache map[tspath.Path]module.ModeAwareCache[T], callback func(resolution T, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) { if file != nil { if resolutions, ok := resolutionCache[file.Path()]; ok { for key, resolution := range resolutions { callback(resolution, key.Name, key.Mode, file.Path()) } } } else { for filePath, resolutions := range resolutionCache { for key, resolution := range resolutions { callback(resolution, key.Name, key.Mode, filePath) } } } } var plainJSErrors = collections.NewSetFromItems( // binder errors diagnostics.Cannot_redeclare_block_scoped_variable_0.Code(), diagnostics.A_module_cannot_have_multiple_default_exports.Code(), diagnostics.Another_export_default_is_here.Code(), diagnostics.The_first_export_default_is_here.Code(), diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module.Code(), diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode.Code(), diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here.Code(), diagnostics.X_constructor_is_a_reserved_word.Code(), diagnostics.X_delete_cannot_be_called_on_an_identifier_in_strict_mode.Code(), diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode.Code(), diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode.Code(), diagnostics.Invalid_use_of_0_in_strict_mode.Code(), diagnostics.A_label_is_not_allowed_here.Code(), diagnostics.X_with_statements_are_not_allowed_in_strict_mode.Code(), // grammar errors diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement.Code(), diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement.Code(), diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name.Code(), diagnostics.A_class_member_cannot_have_the_0_keyword.Code(), diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name.Code(), diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement.Code(), diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement.Code(), diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement.Code(), diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration.Code(), diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context.Code(), diagnostics.A_destructuring_declaration_must_have_an_initializer.Code(), diagnostics.A_get_accessor_cannot_have_parameters.Code(), diagnostics.A_rest_element_cannot_contain_a_binding_pattern.Code(), diagnostics.A_rest_element_cannot_have_a_property_name.Code(), diagnostics.A_rest_element_cannot_have_an_initializer.Code(), diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern.Code(), diagnostics.A_rest_parameter_cannot_have_an_initializer.Code(), diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list.Code(), diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma.Code(), diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block.Code(), diagnostics.A_set_accessor_cannot_have_rest_parameter.Code(), diagnostics.A_set_accessor_must_have_exactly_one_parameter.Code(), diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_module.Code(), diagnostics.An_export_declaration_cannot_have_modifiers.Code(), diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module.Code(), diagnostics.An_import_declaration_cannot_have_modifiers.Code(), diagnostics.An_object_member_cannot_be_declared_optional.Code(), diagnostics.Argument_of_dynamic_import_cannot_be_spread_element.Code(), diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable.Code(), diagnostics.Cannot_redeclare_identifier_0_in_catch_clause.Code(), diagnostics.Catch_clause_variable_cannot_have_an_initializer.Code(), diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator.Code(), diagnostics.Classes_can_only_extend_a_single_class.Code(), diagnostics.Classes_may_not_have_a_field_named_constructor.Code(), diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern.Code(), diagnostics.Duplicate_label_0.Code(), diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_set_of_attributes_as_arguments.Code(), diagnostics.X_for_await_loops_cannot_be_used_inside_a_class_static_block.Code(), diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression.Code(), diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name.Code(), diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array.Code(), diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names.Code(), diagnostics.Jump_target_cannot_cross_function_boundary.Code(), diagnostics.Line_terminator_not_permitted_before_arrow.Code(), diagnostics.Modifiers_cannot_appear_here.Code(), diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement.Code(), diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement.Code(), diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies.Code(), diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression.Code(), diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier.Code(), diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain.Code(), diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async.Code(), diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer.Code(), diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer.Code(), diagnostics.Trailing_comma_not_allowed.Code(), diagnostics.Variable_declaration_list_cannot_be_empty.Code(), diagnostics.X_0_and_1_operations_cannot_be_mixed_without_parentheses.Code(), diagnostics.X_0_expected.Code(), diagnostics.X_0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2.Code(), diagnostics.X_0_list_cannot_be_empty.Code(), diagnostics.X_0_modifier_already_seen.Code(), diagnostics.X_0_modifier_cannot_appear_on_a_constructor_declaration.Code(), diagnostics.X_0_modifier_cannot_appear_on_a_module_or_namespace_element.Code(), diagnostics.X_0_modifier_cannot_appear_on_a_parameter.Code(), diagnostics.X_0_modifier_cannot_appear_on_class_elements_of_this_kind.Code(), diagnostics.X_0_modifier_cannot_be_used_here.Code(), diagnostics.X_0_modifier_must_precede_1_modifier.Code(), diagnostics.X_0_declarations_can_only_be_declared_inside_a_block.Code(), diagnostics.X_0_declarations_must_be_initialized.Code(), diagnostics.X_extends_clause_already_seen.Code(), diagnostics.X_let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations.Code(), diagnostics.Class_constructor_may_not_be_a_generator.Code(), diagnostics.Class_constructor_may_not_be_an_accessor.Code(), diagnostics.X_await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules.Code(), diagnostics.X_await_using_statements_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules.Code(), diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class.Code(), // Type errors diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value.Code(), )