Skip to content
Open
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
30 changes: 30 additions & 0 deletions packages/typescript/test/async/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,36 @@ describe("API", { concurrency }, () => {
assert.deepEqual(await originalProgram.getSourceFileNames(), ["/src/a.ts"]);
});

test("snapshot.update removes and creates synthetic programs together", async () => {
await using api = spawnAPI({
"/src/a.ts": `export const a = 1;`,
"/src/b.ts": `export const b = 2;`,
});
const snapshot = await api.createSnapshot({
createPrograms: [{
rootFiles: ["/src/a.ts"],
compilerOptions: { noLib: true },
}],
});
const removedProgram = snapshot.operation.createdPrograms[0];

const updated = await snapshot.update({
removePrograms: [removedProgram.id],
createPrograms: [{
rootFiles: ["/src/b.ts"],
compilerOptions: { noLib: true },
}],
});
const createdProgram = updated.operation.createdPrograms[0];

assert.notEqual(createdProgram.id, removedProgram.id);
assert.equal(updated.getProgram(removedProgram.id), undefined);
assert.deepEqual(updated.getProjects().map(project => project.id), [createdProgram.id]);
assert.deepEqual(await createdProgram.getSourceFileNames(), ["/src/b.ts"]);
assert.deepEqual(await createdProgram.getSemanticDiagnostics("/src/b.ts"), []);
assert.deepEqual(await removedProgram.getSourceFileNames(), ["/src/a.ts"]);
});

test("snapshot.update reconfigures module resolution providers", async () => {
const root = toRootedFilePath("/src/index.ts", undefined);
const providedA = toRootedFilePath("/a.d.ts", undefined);
Expand Down
30 changes: 30 additions & 0 deletions packages/typescript/test/sync/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,36 @@ describe("API", { concurrency }, () => {
assert.deepEqual(originalProgram.getSourceFileNames(), ["/src/a.ts"]);
});

test("snapshot.update removes and creates synthetic programs together", () => {
using api = spawnAPI({
"/src/a.ts": `export const a = 1;`,
"/src/b.ts": `export const b = 2;`,
});
const snapshot = api.createSnapshot({
createPrograms: [{
rootFiles: ["/src/a.ts"],
compilerOptions: { noLib: true },
}],
});
const removedProgram = snapshot.operation.createdPrograms[0];

const updated = snapshot.update({
removePrograms: [removedProgram.id],
createPrograms: [{
rootFiles: ["/src/b.ts"],
compilerOptions: { noLib: true },
}],
});
const createdProgram = updated.operation.createdPrograms[0];

assert.notEqual(createdProgram.id, removedProgram.id);
assert.equal(updated.getProgram(removedProgram.id), undefined);
assert.deepEqual(updated.getProjects().map(project => project.id), [createdProgram.id]);
assert.deepEqual(createdProgram.getSourceFileNames(), ["/src/b.ts"]);
assert.deepEqual(createdProgram.getSemanticDiagnostics("/src/b.ts"), []);
assert.deepEqual(removedProgram.getSourceFileNames(), ["/src/a.ts"]);
});

test("snapshot.update reconfigures module resolution providers", () => {
const root = toRootedFilePath("/src/index.ts", undefined);
const providedA = toRootedFilePath("/a.d.ts", undefined);
Expand Down
37 changes: 37 additions & 0 deletions tsc/internal/api/session_apistate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,43 @@ func TestGetCurrentLanguageServerSnapshotCreatesAndRemovesPrograms(t *testing.T)
assert.Equal(t, len(projectSession.Snapshot().ProjectCollection.SyntheticProjects()), 0)
}

func TestGetCurrentLanguageServerSnapshotRemovesAndCreatesPrograms(t *testing.T) {
t.Parallel()

const fileName = "/home/projects/p/index.ts"
projectSession, _ := projecttestutil.Setup(map[string]any{fileName: `export const value = 1;`})
defer projectSession.Close()

session := NewLSPSession(projectSession, nil)
_, err := session.handleGetCurrentLanguageServerSnapshot(context.Background(), &GetCurrentLanguageServerSnapshotParams{
Changes: &LanguageServerSnapshotChanges{
CreatePrograms: []*CreateSnapshotProgramParams{{
RootFiles: []DocumentIdentifier{{FileName: fileName}},
CompilerOptions: core.CompilerOptions{NoLib: core.TSTrue},
}},
},
})
assert.NilError(t, err)

replaced, err := session.handleGetCurrentLanguageServerSnapshot(context.Background(), &GetCurrentLanguageServerSnapshotParams{
Changes: &LanguageServerSnapshotChanges{
RemovePrograms: []project.SyntheticProjectID{syntheticProjectID(1)},
CreatePrograms: []*CreateSnapshotProgramParams{{
RootFiles: []DocumentIdentifier{{FileName: fileName}},
CompilerOptions: core.CompilerOptions{NoLib: core.TSTrue},
}},
},
})
assert.NilError(t, err)
assert.DeepEqual(t, *replaced.Operation.CreatedPrograms, []project.SyntheticProjectID{syntheticProjectID(2)})
assert.Equal(t, len(projectSession.Snapshot().ProjectCollection.SyntheticProjects()), 1)
assert.Assert(t, !session.createdPrograms.Has(syntheticProjectID(1)))
assert.Assert(t, session.createdPrograms.Has(syntheticProjectID(2)))

session.Close()
assert.Equal(t, len(projectSession.Snapshot().ProjectCollection.SyntheticProjects()), 0)
}

func TestOpenFilePreservesWindowsDriveLetterCase(t *testing.T) {
t.Parallel()

Expand Down
42 changes: 42 additions & 0 deletions tsc/internal/api/session_createprogram_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,48 @@ func TestUpdateSnapshotEnsuresSyntheticProgram(t *testing.T) {
assert.Equal(t, ensured.Projects[0].Dirty, false)
}

func TestUpdateSnapshotRemovesAndCreatesPrograms(t *testing.T) {
t.Parallel()

const (
fileA = "/home/projects/p/a.ts"
fileB = "/home/projects/p/b.ts"
)
projectSession, _ := projecttestutil.Setup(map[string]any{
fileA: "export const a = 1;",
fileB: "export const b = 1;",
})
defer projectSession.Close()
session := NewLSPSession(projectSession, nil)
defer session.Close()

created, err := session.handleCreateSnapshot(context.Background(), &CreateSnapshotParams{
CreatePrograms: []*CreateSnapshotProgramParams{{
RootFiles: []DocumentIdentifier{{FileName: fileA}},
CompilerOptions: core.CompilerOptions{NoLib: core.TSTrue},
}},
})
assert.NilError(t, err)
assert.DeepEqual(t, *created.Operation.CreatedPrograms, []project.SyntheticProjectID{syntheticProjectID(1)})

updated, err := session.handleUpdateSnapshot(context.Background(), &UpdateSnapshotParams{
Snapshot: created.Snapshot,
Changes: &CreateSnapshotParams{
RemovePrograms: []project.SyntheticProjectID{syntheticProjectID(1)},
CreatePrograms: []*CreateSnapshotProgramParams{{
RootFiles: []DocumentIdentifier{{FileName: fileB}},
CompilerOptions: core.CompilerOptions{NoLib: core.TSTrue},
}},
},
})
assert.NilError(t, err)
assert.DeepEqual(t, *updated.Operation.CreatedPrograms, []project.SyntheticProjectID{syntheticProjectID(2)})
assert.DeepEqual(t, updated.Changes.RemovedProjects, []project.ID{syntheticProjectID(1).AsID()})
assert.Equal(t, len(updated.Projects), 1)
assert.Equal(t, updated.Projects[0].Id, syntheticProjectID(2).AsID())
assert.DeepEqual(t, updated.Projects[0].ParsedCommandLine.FileNames, []tspath.RootedFilePath{fileB})
}

func TestCreateProgramReportsNonCompositeProjectReference(t *testing.T) {
t.Parallel()

Expand Down
3 changes: 3 additions & 0 deletions tsc/internal/project/projectcollectionbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ func (b *ProjectCollectionBuilder) HandleAPIRequest(apiRequest *APISnapshotReque
func (b *ProjectCollectionBuilder) nextSyntheticProjectID() SyntheticProjectID {
for id := 1; ; id++ {
projectID := NewSyntheticProjectID(id)
if _, ok := b.base.syntheticProjects[projectID]; ok {
continue
}
if _, ok := b.syntheticProjects.Load(projectID); !ok {
return projectID
}
Expand Down
66 changes: 66 additions & 0 deletions tsc/internal/project/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,72 @@ func TestSnapshot(t *testing.T) {
assert.Equal(t, len(removedSnapshot.ProjectCollection.SyntheticProjects()), 1)
})

t.Run("removes and creates synthetic programs in one update", func(t *testing.T) {
t.Parallel()
session := setup(map[string]any{
"/a.ts": "export const a = 1;",
"/b.ts": "export const b = 1;",
})
defer session.Close()

ctx := context.Background()
options := &core.CompilerOptions{NoLib: core.TSTrue}
createdSnapshot, err := session.CloneSnapshot(
ctx,
session.Snapshot(),
FileChangeSummary{},
&APISnapshotRequest{CreatePrograms: []*APICreateProgramRequest{{
RootFileNames: []tspath.RootedFilePath{"/a.ts"},
CompilerOptions: options,
}}},
)
assert.NilError(t, err)
defer createdSnapshot.Deref()
firstProject := createdSnapshot.CreatedPrograms()[0]
firstProgramID, ok := firstProject.ID().Synthetic()
assert.Assert(t, ok)

replacedSnapshot, err := session.CloneSnapshot(
ctx,
createdSnapshot,
FileChangeSummary{},
&APISnapshotRequest{
RemovePrograms: collections.NewSetFromItems(firstProgramID),
CreatePrograms: []*APICreateProgramRequest{{
RootFileNames: []tspath.RootedFilePath{"/b.ts"},
CompilerOptions: options,
}},
},
)
assert.NilError(t, err)
defer replacedSnapshot.Deref()

createdPrograms := replacedSnapshot.CreatedPrograms()
assert.Equal(t, len(createdPrograms), 1)
secondProject := createdPrograms[0]
assert.Assert(t, secondProject.ID() != firstProject.ID())
assert.DeepEqual(t, secondProject.CommandLine.FileNames(), []tspath.RootedFilePath{"/b.ts"})
assert.Assert(t, secondProject.GetProgram().GetSourceFile("/b.ts") != nil)
assert.Assert(t, replacedSnapshot.ProjectCollection.GetProject(firstProject.ID()) == nil)
assert.Equal(t, replacedSnapshot.ProjectCollection.GetProject(secondProject.ID()), secondProject)
assert.Equal(t, len(replacedSnapshot.ProjectCollection.SyntheticProjects()), 1)
assert.Equal(t, createdSnapshot.ProjectCollection.GetProject(firstProject.ID()), firstProject)

reusedSnapshot, err := session.CloneSnapshot(
ctx,
replacedSnapshot,
FileChangeSummary{},
&APISnapshotRequest{CreatePrograms: []*APICreateProgramRequest{{
RootFileNames: []tspath.RootedFilePath{"/a.ts"},
CompilerOptions: options,
}}},
)
assert.NilError(t, err)
defer reusedSnapshot.Deref()
assert.Equal(t, reusedSnapshot.CreatedPrograms()[0].ID(), firstProject.ID())
assert.Equal(t, len(reusedSnapshot.ProjectCollection.SyntheticProjects()), 2)
})

t.Run("failed API update is not adopted", func(t *testing.T) {
t.Parallel()
session := setup(map[string]any{
Expand Down