Skip to content

Commit 64a2d44

Browse files
committed
Actually load ancestor project tree
1 parent 12c0e93 commit 64a2d44

17 files changed

+436
-38
lines changed

internal/compiler/program.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,21 @@ func (p *Program) GetParseFileRedirect(fileName string) string {
144144
return p.projectReferenceFileMapper.getParseFileRedirect(ast.NewHasFileName(fileName, p.toPath(fileName)))
145145
}
146146

147-
func (p *Program) ForEachResolvedProjectReference(
148-
fn func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int),
149-
) {
147+
func (p *Program) GetResolvedProjectReferences() []*tsoptions.ParsedCommandLine {
148+
return p.projectReferenceFileMapper.getResolvedProjectReferences()
149+
}
150+
151+
func (p *Program) ForEachResolvedProjectReference(fn func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int)) {
150152
p.projectReferenceFileMapper.forEachResolvedProjectReference(fn)
151153
}
152154

155+
func (p *Program) ForEachResolvedProjectReferenceInChildConfig(
156+
childConfig *tsoptions.ParsedCommandLine,
157+
fn func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool,
158+
) bool {
159+
return p.projectReferenceFileMapper.forEachResolvedProjectReferenceInChildConfig(childConfig, fn)
160+
}
161+
153162
// UseCaseSensitiveFileNames implements checker.Program.
154163
func (p *Program) UseCaseSensitiveFileNames() bool {
155164
return p.Host().FS().UseCaseSensitiveFileNames()

internal/compiler/projectreferencefilemapper.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ func (mapper *projectReferenceFileMapper) getParseFileRedirect(file ast.HasFileN
4646
}
4747

4848
func (mapper *projectReferenceFileMapper) getResolvedProjectReferences() []*tsoptions.ParsedCommandLine {
49+
if mapper.opts.Config.ConfigFile == nil {
50+
return nil
51+
}
4952
refs, ok := mapper.referencesInConfigFile[mapper.opts.Config.ConfigFile.SourceFile.Path()]
5053
var result []*tsoptions.ParsedCommandLine
5154
if ok {
@@ -115,23 +118,44 @@ func (mapper *projectReferenceFileMapper) forEachResolvedProjectReference(
115118
seenRef := collections.NewSetWithSizeHint[tspath.Path](len(mapper.referencesInConfigFile))
116119
seenRef.Add(mapper.opts.Config.ConfigFile.SourceFile.Path())
117120
refs := mapper.referencesInConfigFile[mapper.opts.Config.ConfigFile.SourceFile.Path()]
118-
mapper.forEachResolvedReferenceWorker(refs, fn, mapper.opts.Config, seenRef)
121+
mapper.forEachResolvedReferenceWorker(refs, func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool {
122+
fn(path, config, parent, index)
123+
return false
124+
}, mapper.opts.Config, seenRef)
119125
}
120126

121127
func (mapper *projectReferenceFileMapper) forEachResolvedReferenceWorker(
122128
references []tspath.Path,
123-
fn func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int),
129+
fn func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool,
124130
parent *tsoptions.ParsedCommandLine,
125131
seenRef *collections.Set[tspath.Path],
126-
) {
132+
) bool {
127133
for index, path := range references {
128134
if !seenRef.AddIfAbsent(path) {
129135
continue
130136
}
131137
config, _ := mapper.configToProjectReference[path]
132-
fn(path, config, parent, index)
133-
mapper.forEachResolvedReferenceWorker(mapper.referencesInConfigFile[path], fn, config, seenRef)
138+
if fn(path, config, parent, index) {
139+
return true
140+
}
141+
if mapper.forEachResolvedReferenceWorker(mapper.referencesInConfigFile[path], fn, config, seenRef) {
142+
return true
143+
}
144+
}
145+
return false
146+
}
147+
148+
func (mapper *projectReferenceFileMapper) forEachResolvedProjectReferenceInChildConfig(
149+
childConfig *tsoptions.ParsedCommandLine,
150+
fn func(path tspath.Path, config *tsoptions.ParsedCommandLine, parent *tsoptions.ParsedCommandLine, index int) bool,
151+
) bool {
152+
if childConfig == nil || childConfig.ConfigFile == nil {
153+
return false
134154
}
155+
seenRef := collections.NewSetWithSizeHint[tspath.Path](len(mapper.referencesInConfigFile))
156+
seenRef.Add(childConfig.ConfigFile.SourceFile.Path())
157+
refs := mapper.referencesInConfigFile[childConfig.ConfigFile.SourceFile.Path()]
158+
return mapper.forEachResolvedReferenceWorker(refs, fn, mapper.opts.Config, seenRef)
135159
}
136160

137161
func (mapper *projectReferenceFileMapper) getSourceToDtsIfSymlink(file ast.HasFileName) *tsoptions.SourceOutputAndProjectReference {

internal/ls/findallreferences.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"slices"
88
"strings"
9+
"sync"
910

1011
"github.com/microsoft/typescript-go/internal/ast"
1112
"github.com/microsoft/typescript-go/internal/astnav"
@@ -444,6 +445,12 @@ var _ lsproto.HasTextDocumentPosition = (*position)(nil)
444445
func (nld *position) TextDocumentURI() lsproto.DocumentUri { return nld.uri }
445446
func (nld *position) TextDocumentPosition() lsproto.Position { return nld.pos }
446447

448+
type NonLocalDefinition struct {
449+
position
450+
GetSourcePosition func() lsproto.HasTextDocumentPosition
451+
GetGeneratedPosition func() lsproto.HasTextDocumentPosition
452+
}
453+
447454
func getFileAndStartPosFromDeclaration(declaration *ast.Node) (*ast.SourceFile, core.TextPos) {
448455
file := ast.GetSourceFileOfNode(declaration)
449456
name := core.OrElse(ast.GetNameOfDeclaration(declaration), declaration)
@@ -452,7 +459,7 @@ func getFileAndStartPosFromDeclaration(declaration *ast.Node) (*ast.SourceFile,
452459
return file, core.TextPos(textRange.Pos())
453460
}
454461

455-
func (l *LanguageService) GetNonLocalDefinition(ctx context.Context, entry *SymbolAndEntries) lsproto.HasTextDocumentPosition {
462+
func (l *LanguageService) GetNonLocalDefinition(ctx context.Context, entry *SymbolAndEntries) *NonLocalDefinition {
456463
if !entry.canUseDefinitionSymbol() {
457464
return nil
458465
}
@@ -465,9 +472,31 @@ func (l *LanguageService) GetNonLocalDefinition(ctx context.Context, entry *Symb
465472
if emitResolver.IsDeclarationVisible(d) {
466473
file, startPos := getFileAndStartPosFromDeclaration(d)
467474
fileName := file.FileName()
468-
return &position{
469-
uri: lsconv.FileNameToDocumentURI(fileName),
470-
pos: l.converters.PositionToLineAndCharacter(file, startPos),
475+
return &NonLocalDefinition{
476+
position: position{
477+
uri: lsconv.FileNameToDocumentURI(fileName),
478+
pos: l.converters.PositionToLineAndCharacter(file, startPos),
479+
},
480+
GetSourcePosition: sync.OnceValue(func() lsproto.HasTextDocumentPosition {
481+
mapped := l.tryGetSourcePosition(fileName, startPos)
482+
if mapped != nil {
483+
return &position{
484+
uri: lsconv.FileNameToDocumentURI(mapped.FileName),
485+
pos: l.converters.PositionToLineAndCharacter(l.getScript(mapped.FileName), core.TextPos(mapped.Pos)),
486+
}
487+
}
488+
return nil
489+
}),
490+
GetGeneratedPosition: sync.OnceValue(func() lsproto.HasTextDocumentPosition {
491+
mapped := l.tryGetGeneratedPosition(fileName, startPos)
492+
if mapped != nil {
493+
return &position{
494+
uri: lsconv.FileNameToDocumentURI(mapped.FileName),
495+
pos: l.converters.PositionToLineAndCharacter(l.getScript(mapped.FileName), core.TextPos(mapped.Pos)),
496+
}
497+
}
498+
return nil
499+
}),
471500
}
472501
}
473502
}

internal/ls/source_map.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/microsoft/typescript-go/internal/debug"
66
"github.com/microsoft/typescript-go/internal/ls/lsconv"
77
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
8+
"github.com/microsoft/typescript-go/internal/outputpaths"
89
"github.com/microsoft/typescript-go/internal/sourcemap"
910
"github.com/microsoft/typescript-go/internal/tspath"
1011
)
@@ -86,3 +87,47 @@ func (l *LanguageService) tryGetSourcePositionWorker(
8687
}
8788
return documentPos
8889
}
90+
91+
func (l *LanguageService) tryGetGeneratedPosition(
92+
fileName string,
93+
position core.TextPos,
94+
) *sourcemap.DocumentPosition {
95+
newPos := l.tryGetGeneratedPositionWorker(fileName, position)
96+
if newPos != nil {
97+
if _, ok := l.ReadFile(newPos.FileName); !ok { // File doesn't exist
98+
return nil
99+
}
100+
}
101+
return newPos
102+
}
103+
104+
func (l *LanguageService) tryGetGeneratedPositionWorker(
105+
fileName string,
106+
position core.TextPos,
107+
) *sourcemap.DocumentPosition {
108+
if tspath.IsDeclarationFileName(fileName) {
109+
return nil
110+
}
111+
112+
program := l.GetProgram()
113+
if program == nil || program.GetSourceFile(fileName) == nil {
114+
return nil
115+
}
116+
117+
path := l.toPath(fileName)
118+
// If this is source file of project reference source (instead of redirect) there is no generated position
119+
if program.IsSourceFromProjectReference(path) {
120+
return nil
121+
}
122+
123+
declarationFileName := outputpaths.GetOutputDeclarationFileNameWorker(fileName, program.Options(), program)
124+
positionMapper := l.GetDocumentPositionMapper(declarationFileName)
125+
documentPos := positionMapper.GetGeneratedPosition(&sourcemap.DocumentPosition{FileName: fileName, Pos: int(position)})
126+
if documentPos == nil {
127+
return nil
128+
}
129+
if newPos := l.tryGetGeneratedPositionWorker(documentPos.FileName, core.TextPos(documentPos.Pos)); newPos != nil {
130+
return newPos
131+
}
132+
return documentPos
133+
}

internal/lsp/server.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"maps"
89
"runtime/debug"
910
"slices"
1011
"sync"
@@ -612,7 +613,7 @@ func registerMultiProjectReferenceRequestHandler[Req lsproto.HasTextDocumentPosi
612613

613614
var queue []projectAndTextDocumentPosition
614615
results := make(map[tspath.Path]Resp)
615-
var defaultDefinition lsproto.HasTextDocumentPosition
616+
var defaultDefinition *ls.NonLocalDefinition
616617

617618
searchPosition := func(project *project.Project, ls *ls.LanguageService, uri lsproto.DocumentUri, position lsproto.Position) (Resp, error) {
618619
originalNode, symbolsAndEntries, ok := ls.ProvideSymbolsAndEntries(ctx, uri, position, info.Method == lsproto.MethodTextDocumentRename)
@@ -695,16 +696,31 @@ func registerMultiProjectReferenceRequestHandler[Req lsproto.HasTextDocumentPosi
695696
}
696697

697698
if defaultDefinition != nil {
698-
// !!! TODO: sheetal
699-
// load ancestor projects for all the projects that can reference the ones already seen
700-
// for all projects
701-
// if this project is not seen add the project location to queue position can be in project
702-
// either the d.ts or source definition or symlink
699+
if err := s.session.ForEachProjectLocationLoadingProjectTree(
700+
ctx,
701+
maps.Keys(results),
702+
defaultDefinition,
703+
// Can loop forever without this (enqueue here, dequeue above, repeat)
704+
func(projectFromSnapshot *project.Project) bool {
705+
_, ok := results[projectFromSnapshot.Id()]
706+
return !ok
707+
},
708+
func(project *project.Project, uri lsproto.DocumentUri, position lsproto.Position) {
709+
// Enqueue the project and location for further processing
710+
queue = append(queue, projectAndTextDocumentPosition{
711+
project: project,
712+
Uri: uri,
713+
Position: position,
714+
})
715+
},
716+
); err != nil {
717+
return err
718+
}
703719
}
704720
}
705721

706722
var resp Resp
707-
if len(results) > 0 {
723+
if len(results) > 1 {
708724
resp = combineResults(defaultProject, results)
709725
} else {
710726
for _, value := range results {

internal/project/project.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ type Project struct {
7070
ProgramLastUpdate uint64
7171
// Set of projects that this project could be referencing.
7272
// Only set before actually loading config file to get actual project references
73-
PotentialProjectReferences *collections.Set[tspath.Path]
73+
potentialProjectReferences *collections.Set[tspath.Path]
7474

7575
programFilesWatch *WatchedFiles[PatternsAndIgnored]
7676
failedLookupsWatch *WatchedFiles[map[tspath.Path]string]
@@ -227,7 +227,7 @@ func (p *Project) Clone() *Project {
227227
Program: p.Program,
228228
ProgramUpdateKind: ProgramUpdateKindNone,
229229
ProgramLastUpdate: p.ProgramLastUpdate,
230-
PotentialProjectReferences: p.PotentialProjectReferences,
230+
potentialProjectReferences: p.potentialProjectReferences,
231231

232232
programFilesWatch: p.programFilesWatch,
233233
failedLookupsWatch: p.failedLookupsWatch,
@@ -276,12 +276,29 @@ func (p *Project) getCommandLineWithTypingsFiles() *tsoptions.ParsedCommandLine
276276
}
277277

278278
func (p *Project) setPotentialProjectReference(configFilePath tspath.Path) {
279-
if p.PotentialProjectReferences == nil {
280-
p.PotentialProjectReferences = &collections.Set[tspath.Path]{}
279+
if p.potentialProjectReferences == nil {
280+
p.potentialProjectReferences = &collections.Set[tspath.Path]{}
281281
} else {
282-
p.PotentialProjectReferences = p.PotentialProjectReferences.Clone()
282+
p.potentialProjectReferences = p.potentialProjectReferences.Clone()
283283
}
284-
p.PotentialProjectReferences.Add(configFilePath)
284+
p.potentialProjectReferences.Add(configFilePath)
285+
}
286+
287+
func (p *Project) forEachPotentialProjectReference(fn func(tspath.Path) bool) bool {
288+
if p.CommandLine != nil {
289+
for _, path := range p.CommandLine.ResolvedProjectReferencePaths() {
290+
if fn(p.toPath(path)) {
291+
return true
292+
}
293+
}
294+
} else if p.potentialProjectReferences != nil {
295+
for path := range p.potentialProjectReferences.Keys() {
296+
if fn(path) {
297+
return true
298+
}
299+
}
300+
}
301+
return false
285302
}
286303

287304
type CreateProgramResult struct {

0 commit comments

Comments
 (0)