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
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
@@ -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
@@ -65,6 +66,9 @@ func NewAPI(host APIHost, options APIOptions) *API {
},
},
}
api.configFileRegistry = &project.ConfigFileRegistry{
Host: api,
}
return api
}

@@ -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()
20 changes: 20 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
@@ -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
@@ -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"
)

@@ -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
@@ -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 {
@@ -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)
@@ -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)
@@ -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 {
@@ -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 {
@@ -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
6 changes: 3 additions & 3 deletions internal/checker/checker_test.go
Original file line number Diff line number Diff line change
@@ -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")
@@ -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{
@@ -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{
3 changes: 2 additions & 1 deletion internal/checker/utilities.go
Original file line number Diff line number Diff line change
@@ -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)
}

9 changes: 3 additions & 6 deletions internal/compiler/emitHost.go
Original file line number Diff line number Diff line change
@@ -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"
)

@@ -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)
}
5 changes: 4 additions & 1 deletion internal/compiler/emitter.go
Original file line number Diff line number Diff line change
@@ -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) {
Loading
Oops, something went wrong.