diff --git a/tsc/internal/fourslash/tests/workspaceSymbolNewInferredProject_test.go b/tsc/internal/fourslash/tests/workspaceSymbolNewInferredProject_test.go new file mode 100644 index 0000000000000..55bff5331e00e --- /dev/null +++ b/tsc/internal/fourslash/tests/workspaceSymbolNewInferredProject_test.go @@ -0,0 +1,57 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/TypeScript/tsc/internal/fourslash" + "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto" + "github.com/microsoft/TypeScript/tsc/internal/testutil" +) + +// workspace/symbol used to crash when an open file got kicked out of its tsconfig project +// and then another file from that project was opened. The orphaned file ends up in a fresh +// inferred project, and nothing was building a program for it. Before that, the orphaned +// file's symbols were missing because loading project trees didn't refresh the inferred project. +func TestWorkspaceSymbolNewInferredProject(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` +// @Filename: /home/src/projects/p/tsconfig.json +// @noOpen: true +{ "files": ["a.ts", "b.ts"] } + +// @Filename: /home/src/projects/p/a.ts +import "./e"; + +// @Filename: /home/src/projects/p/b.ts +// @noOpen: true +export const b = 1; + +// @Filename: /home/src/projects/p/e.ts +export const e = 1; +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + + // e.ts isn't listed in tsconfig.json, it only gets pulled in by the import in a.ts. + // Once that import is gone, e.ts should move to the inferred project. + f.GoToFile(t, "/home/src/projects/p/a.ts") + f.Replace(t, 0, len(`import "./e";`), "") + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + {Pattern: "b", Includes: new([]*lsproto.SymbolInformation{})}, + {Pattern: "e", Includes: new([]*lsproto.SymbolInformation{{ + Name: "e", + Kind: lsproto.SymbolKindVariable, + Location: lsproto.Location{ + Uri: "file:///home/src/projects/p/e.ts", + Range: lsproto.Range{Start: lsproto.Position{Line: 0, Character: 13}, End: lsproto.Position{Line: 0, Character: 14}}, + }, + }})}, + }) + + // The tsconfig project is already up to date, so opening b.ts takes the fast path. + f.GoToFile(t, "/home/src/projects/p/b.ts") + f.VerifyWorkspaceSymbol(t, []*fourslash.VerifyWorkspaceSymbolCase{ + {Pattern: "b", Includes: new([]*lsproto.SymbolInformation{})}, + }) +} diff --git a/tsc/internal/project/projectcollectionbuilder.go b/tsc/internal/project/projectcollectionbuilder.go index 2a190503e8468..99872b4cb6eee 100644 --- a/tsc/internal/project/projectcollectionbuilder.go +++ b/tsc/internal/project/projectcollectionbuilder.go @@ -760,6 +760,13 @@ func (b *ProjectCollectionBuilder) DidRequestProjectTrees(projectTreeRequest *Pr } wg.RunAndWait() + // Updated configured projects may have moved open files in or out of the inferred project. + // Callers iterate over all language service projects, so the inferred one needs a program too. + b.cleanupInferredProject(logger) + if b.inferredProject.Value() != nil { + b.updateProgram(b.inferredProject, logger) + } + if logger != nil { elapsed := time.Since(startTime) logger.Log(fmt.Sprintf("Completed project tree request for %v in %v", projectTreeRequest.Projects(), elapsed))