Skip to content

Commit 6a49d85

Browse files
authored
Handle unloaded projects in API snapshot responses (#4483)
1 parent 4457aa7 commit 6a49d85

3 files changed

Lines changed: 70 additions & 3 deletions

File tree

internal/api/proto.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,9 @@ type ProjectResponse struct {
500500
}
501501

502502
func NewProjectResponse(p *project.Project) *ProjectResponse {
503+
if p == nil || p.CommandLine == nil {
504+
panic("NewProjectResponse called with unloaded project")
505+
}
503506
return &ProjectResponse{
504507
Id: ProjectHandle(p),
505508
ConfigFileName: p.Name(),

internal/api/session.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -948,9 +948,12 @@ func (s *Session) handleUpdateSnapshot(ctx context.Context, params *UpdateSnapsh
948948

949949
// Build projects list
950950
projects := snapshot.ProjectCollection.Projects()
951-
projectResponses := make([]*ProjectResponse, len(projects))
952-
for i, proj := range projects {
953-
projectResponses[i] = NewProjectResponse(proj)
951+
projectResponses := make([]*ProjectResponse, 0, len(projects))
952+
for _, proj := range projects {
953+
if proj.CommandLine == nil {
954+
continue
955+
}
956+
projectResponses = append(projectResponses, NewProjectResponse(proj))
954957
}
955958

956959
// Compute changes from the previous latest snapshot

internal/api/session_apistate_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/microsoft/typescript-go/internal/bundled"
8+
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
89
"github.com/microsoft/typescript-go/internal/testutil/projecttestutil"
910
"github.com/microsoft/typescript-go/internal/tspath"
1011
"gotest.tools/v3/assert"
@@ -192,3 +193,63 @@ func TestSessionTracksAndReleasesAPIRefs(t *testing.T) {
192193
)
193194
})
194195
}
196+
197+
// TestUpdateSnapshotResponseSkipsUnloadedAncestorProject verifies that API
198+
// updateSnapshot does not report unloaded ancestor project placeholders. This
199+
// covers the case where opening a file loads its nearest configured project
200+
// while solution search discovers an ancestor tsconfig placeholder whose command
201+
// line is still nil.
202+
func TestUpdateSnapshotResponseSkipsUnloadedAncestorProject(t *testing.T) {
203+
t.Parallel()
204+
if !bundled.Embedded {
205+
t.Skip("bundled files are not embedded")
206+
}
207+
208+
const (
209+
nestedConfigFileName = "/repo/packages/app/tsconfig.json"
210+
ancestorConfigFileName = "/repo/packages/tsconfig.json"
211+
fileName = "/repo/packages/app/src/index.ts"
212+
)
213+
files := map[string]any{
214+
ancestorConfigFileName: `{ "files": [] }`,
215+
nestedConfigFileName: `{
216+
"compilerOptions": { "composite": true },
217+
"include": ["**/*"]
218+
}`,
219+
fileName: `let s: string = 1234;`,
220+
}
221+
projectSession, _ := projecttestutil.Setup(files)
222+
defer projectSession.Close()
223+
224+
projectSession.DidOpenFile(context.Background(), lsproto.DocumentUri("file://"+fileName), 1, files[fileName].(string), lsproto.LanguageKindTypeScript)
225+
snapshot := projectSession.Snapshot()
226+
nestedProject := snapshot.ProjectCollection.ConfiguredProject(tspath.Path(nestedConfigFileName))
227+
assert.Assert(t, nestedProject != nil)
228+
assert.Assert(t, nestedProject.CommandLine != nil)
229+
ancestorProject := snapshot.ProjectCollection.ConfiguredProject(tspath.Path(ancestorConfigFileName))
230+
assert.Assert(t, ancestorProject != nil)
231+
assert.Assert(t, ancestorProject.CommandLine == nil)
232+
233+
session := NewSession(projectSession, nil)
234+
defer session.Close()
235+
236+
response, err := session.handleUpdateSnapshot(context.Background(), &UpdateSnapshotParams{
237+
OpenProjects: []DocumentIdentifier{{FileName: nestedConfigFileName}},
238+
})
239+
assert.NilError(t, err)
240+
241+
var foundNestedProject bool
242+
var foundAncestorProject bool
243+
for _, project := range response.Projects {
244+
switch project.ConfigFileName {
245+
case nestedConfigFileName:
246+
foundNestedProject = true
247+
assert.Assert(t, project.RootFiles != nil)
248+
assert.Assert(t, project.CompilerOptions != nil)
249+
case ancestorConfigFileName:
250+
foundAncestorProject = true
251+
}
252+
}
253+
assert.Assert(t, foundNestedProject)
254+
assert.Assert(t, !foundAncestorProject)
255+
}

0 commit comments

Comments
 (0)