Skip to content

Project references in a program #1078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ type API struct {
host APIHost
options APIOptions

documentRegistry *project.DocumentRegistry
scriptInfosMu sync.RWMutex
scriptInfos map[tspath.Path]*project.ScriptInfo
documentRegistry *project.DocumentRegistry
scriptInfosMu sync.RWMutex
scriptInfos map[tspath.Path]*project.ScriptInfo
configFileRegistry *project.ConfigFileRegistry

projects handleMap[project.Project]
filesMu sync.Mutex
Expand Down Expand Up @@ -65,6 +66,9 @@ func NewAPI(host APIHost, options APIOptions) *API {
},
},
}
api.configFileRegistry = &project.ConfigFileRegistry{
Host: api,
}
return api
}

Expand All @@ -83,6 +87,11 @@ func (api *API) DocumentRegistry() *project.DocumentRegistry {
return api.documentRegistry
}

// ConfigFileRegistry implements ProjectHost.
func (api *API) ConfigFileRegistry() *project.ConfigFileRegistry {
return api.configFileRegistry
}

// FS implements ProjectHost.
func (api *API) FS() vfs.FS {
return api.host.FS()
Expand Down
20 changes: 20 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3579,3 +3579,23 @@ func IsTrivia(token Kind) bool {
func HasDecorators(node *Node) bool {
return HasSyntacticModifier(node, ModifierFlagsDecorator)
}

type hasFileNameImpl struct {
fileName string
path tspath.Path
}

func NewHasFileName(fileName string, path tspath.Path) HasFileName {
return &hasFileNameImpl{
fileName: fileName,
path: path,
}
}

func (h *hasFileNameImpl) FileName() string {
return h.fileName
}

func (h *hasFileNameImpl) Path() tspath.Path {
return h.path
}
56 changes: 36 additions & 20 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/stringutil"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)

Expand Down Expand Up @@ -527,6 +528,7 @@ type Program interface {
BindSourceFiles()
FileExists(fileName string) bool
GetSourceFile(fileName string) *ast.SourceFile
GetSourceFileForResolvedModule(fileName string) *ast.SourceFile
GetEmitModuleFormatOfFile(sourceFile ast.HasFileName) core.ModuleKind
GetEmitSyntaxForUsageLocation(sourceFile ast.HasFileName, usageLocation *ast.StringLiteralLike) core.ResolutionMode
GetImpliedNodeFormatForEmit(sourceFile ast.HasFileName) core.ModuleKind
Expand All @@ -535,6 +537,8 @@ type Program interface {
GetSourceFileMetaData(path tspath.Path) *ast.SourceFileMetaData
GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node)
GetImportHelpersImportSpecifier(path tspath.Path) *ast.Node
IsSourceFromProjectReference(path tspath.Path) bool
GetSourceAndProjectReference(path tspath.Path) *tsoptions.SourceAndProjectReference
}

type Host interface {
Expand Down Expand Up @@ -2086,7 +2090,7 @@ func (c *Checker) getSymbol(symbols ast.SymbolTable, name string, meaning ast.Sy
}

func (c *Checker) CheckSourceFile(ctx context.Context, sourceFile *ast.SourceFile) {
if SkipTypeChecking(sourceFile, c.compilerOptions) {
if SkipTypeChecking(sourceFile, c.compilerOptions, c.program) {
return
}
c.checkSourceFile(ctx, sourceFile)
Expand Down Expand Up @@ -6462,15 +6466,13 @@ func (c *Checker) checkAliasSymbol(node *ast.Node) {
// in files that are unambiguously CommonJS in this mode.
c.error(node, diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_module_is_set_to_preserve)
}
// !!!

// if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && !ast.IsTypeOnlyImportOrExportDeclaration(node) && node.Flags&ast.NodeFlagsAmbient == 0 && targetFlags&ast.SymbolFlagsConstEnum != 0 {
// constEnumDeclaration := target.ValueDeclaration
// redirect := host.getRedirectReferenceForResolutionFromSourceOfProject(ast.GetSourceFileOfNode(constEnumDeclaration).ResolvedPath)
// if constEnumDeclaration.Flags&ast.NodeFlagsAmbient != 0 && (redirect == nil || !shouldPreserveConstEnums(redirect.commandLine.options)) {
// c.error(node, diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, c.getIsolatedModulesLikeFlagName())
// }
// }
if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && !ast.IsTypeOnlyImportOrExportDeclaration(node) && node.Flags&ast.NodeFlagsAmbient == 0 && targetFlags&ast.SymbolFlagsConstEnum != 0 {
constEnumDeclaration := target.ValueDeclaration
redirect := c.program.GetSourceAndProjectReference(ast.GetSourceFileOfNode(constEnumDeclaration).Path())
if constEnumDeclaration.Flags&ast.NodeFlagsAmbient != 0 && (redirect == nil || !redirect.Resolved.CompilerOptions().ShouldPreserveConstEnums()) {
c.error(node, diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, c.getIsolatedModulesLikeFlagName())
}
}
}
if ast.IsImportSpecifier(node) {
targetSymbol := c.resolveAliasWithDeprecationCheck(symbol, node)
Expand Down Expand Up @@ -7184,15 +7186,14 @@ func (c *Checker) checkConstEnumAccess(node *ast.Node, t *Type) {
// --verbatimModuleSyntax only gets checked here when the enum usage does not
// resolve to an import, because imports of ambient const enums get checked
// separately in `checkAliasSymbol`.
// !!!
// if c.compilerOptions.IsolatedModules.IsTrue() || c.compilerOptions.VerbatimModuleSyntax.IsTrue() && ok && c.resolveName(node, getFirstIdentifier(node).Text(), ast.SymbolFlagsAlias, nil, false, true) == nil {
// // Debug.assert(t.symbol.Flags&ast.SymbolFlagsConstEnum != 0)
// constEnumDeclaration := t.symbol.ValueDeclaration
// redirect := host.getRedirectReferenceForResolutionFromSourceOfProject(ast.GetSourceFileOfNode(constEnumDeclaration).ResolvedPath)
// if constEnumDeclaration.Flags&ast.NodeFlagsAmbient != 0 && !isValidTypeOnlyAliasUseSite(node) && (redirect == nil || !shouldPreserveConstEnums(redirect.commandLine.options)) {
// c.error(node, diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, c.getIsolatedModulesLikeFlagName())
// }
// }
if c.compilerOptions.IsolatedModules.IsTrue() || c.compilerOptions.VerbatimModuleSyntax.IsTrue() && ok && c.resolveName(node, ast.GetFirstIdentifier(node).Text(), ast.SymbolFlagsAlias, nil, false, true) == nil {
// Debug.assert(t.symbol.Flags&ast.SymbolFlagsConstEnum != 0)
constEnumDeclaration := t.symbol.ValueDeclaration
redirect := c.program.GetSourceAndProjectReference(ast.GetSourceFileOfNode(constEnumDeclaration).Path())
if constEnumDeclaration.Flags&ast.NodeFlagsAmbient != 0 && !ast.IsValidTypeOnlyAliasUseSite(node) && (redirect == nil || !redirect.Resolved.CompilerOptions().ShouldPreserveConstEnums()) {
c.error(node, diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, c.getIsolatedModulesLikeFlagName())
}
}
}

func (c *Checker) instantiateTypeWithSingleGenericCallSignature(node *ast.Node, t *Type, checkMode CheckMode) *Type {
Expand Down Expand Up @@ -14467,7 +14468,7 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri
var sourceFile *ast.SourceFile
resolvedModule := c.program.GetResolvedModule(importingSourceFile, moduleReference, mode)
if resolvedModule.IsResolved() {
sourceFile = c.program.GetSourceFile(resolvedModule.ResolvedFileName)
sourceFile = c.program.GetSourceFileForResolvedModule(resolvedModule.ResolvedFileName)
}

if sourceFile != nil {
Expand Down Expand Up @@ -14573,6 +14574,21 @@ func (c *Checker) resolveExternalModule(location *ast.Node, moduleReference stri
}

if moduleNotFoundError != nil {

// See if this was possibly a projectReference redirect
if resolvedModule.IsResolved() {
redirect := c.program.GetOutputAndProjectReference(tspath.ToPath(resolvedModule.ResolvedFileName, c.program.GetCurrentDirectory(), c.program.UseCaseSensitiveFileNames()))
if redirect != nil && redirect.OutputDts != "" {
c.error(
errorNode,
diagnostics.Output_file_0_has_not_been_built_from_source_file_1,
redirect.OutputDts,
resolvedModule.ResolvedFileName,
)
return nil
}
}

// !!!
isExtensionlessRelativePathImport := tspath.PathIsRelative(moduleReference) && !tspath.HasExtension(moduleReference)
resolutionIsNode16OrNext := c.moduleResolutionKind == core.ModuleResolutionKindNode16 || c.moduleResolutionKind == core.ModuleResolutionKindNodeNext
Expand Down
6 changes: 3 additions & 3 deletions internal/checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ foo.bar;`
fs = bundled.WrapFS(fs)

cd := "/"
host := compiler.NewCompilerHost(nil, cd, fs, bundled.LibPath())
host := compiler.NewCompilerHost(nil, cd, fs, bundled.LibPath(), nil)

parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, host, nil)
assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line")
Expand Down Expand Up @@ -70,7 +70,7 @@ func TestCheckSrcCompiler(t *testing.T) {

rootPath := tspath.CombinePaths(tspath.NormalizeSlashes(repo.TypeScriptSubmodulePath), "src", "compiler")

host := compiler.NewCompilerHost(nil, rootPath, fs, bundled.LibPath())
host := compiler.NewCompilerHost(nil, rootPath, fs, bundled.LibPath(), nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, host, nil)
assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line")
p := compiler.NewProgram(compiler.ProgramOptions{
Expand All @@ -87,7 +87,7 @@ func BenchmarkNewChecker(b *testing.B) {

rootPath := tspath.CombinePaths(tspath.NormalizeSlashes(repo.TypeScriptSubmodulePath), "src", "compiler")

host := compiler.NewCompilerHost(nil, rootPath, fs, bundled.LibPath())
host := compiler.NewCompilerHost(nil, rootPath, fs, bundled.LibPath(), nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, host, nil)
assert.Equal(b, len(errors), 0, "Expected no errors in parsed command line")
p := compiler.NewProgram(compiler.ProgramOptions{
Expand Down
3 changes: 2 additions & 1 deletion internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1471,10 +1471,11 @@ func forEachYieldExpression(body *ast.Node, visitor func(expr *ast.Node)) {
traverse(body)
}

func SkipTypeChecking(sourceFile *ast.SourceFile, options *core.CompilerOptions) bool {
func SkipTypeChecking(sourceFile *ast.SourceFile, options *core.CompilerOptions, host Program) bool {
return options.NoCheck.IsTrue() ||
options.SkipLibCheck.IsTrue() && sourceFile.IsDeclarationFile ||
options.SkipDefaultLibCheck.IsTrue() && sourceFile.HasNoDefaultLib ||
host.IsSourceFromProjectReference(sourceFile.Path()) ||
!canIncludeBindAndCheckDiagnostics(sourceFile, options)
}

Expand Down
9 changes: 3 additions & 6 deletions internal/compiler/emitHost.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/microsoft/typescript-go/internal/outputpaths"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers/declarations"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)

Expand Down Expand Up @@ -73,18 +74,14 @@ func (host *emitHost) GetPackageJsonInfo(pkgJsonPath string) modulespecifiers.Pa
return host.program.GetPackageJsonInfo(pkgJsonPath)
}

func (host *emitHost) GetProjectReferenceRedirect(path string) string {
return host.program.GetProjectReferenceRedirect(path)
func (host *emitHost) GetOutputAndProjectReference(path tspath.Path) *tsoptions.OutputDtsAndProjectReference {
return host.program.GetOutputAndProjectReference(path)
}

func (host *emitHost) GetRedirectTargets(path tspath.Path) []string {
return host.program.GetRedirectTargets(path)
}

func (host *emitHost) IsSourceOfProjectReferenceRedirect(path string) bool {
return host.program.IsSourceOfProjectReferenceRedirect(path)
}

func (host *emitHost) GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags {
return host.GetEmitResolver(ast.GetSourceFileOfNode(node), true).GetEffectiveDeclarationFlags(node, flags)
}
Expand Down
5 changes: 4 additions & 1 deletion internal/compiler/emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,10 @@ func sourceFileMayBeEmitted(sourceFile *ast.SourceFile, host printer.EmitHost, f
return true
}

// !!! Source files from referenced projects are not emitted
// Source files from referenced projects are not emitted
if host.GetOutputAndProjectReference(sourceFile.Path()) != nil {
return false
}

// Any non json file should be emitted
if !ast.IsJsonSourceFile(sourceFile) {
Expand Down
Loading