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
49 changes: 49 additions & 0 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type FourslashTest struct {
scriptInfos map[string]*scriptInfo
converters *ls.Converters

userPreferences *ls.UserPreferences
currentCaretPosition lsproto.Position
lastKnownMarkerName *string
activeFilename string
Expand Down Expand Up @@ -268,6 +269,29 @@ func sendRequest[Params, Resp any](t *testing.T, f *FourslashTest, info lsproto.
)
f.writeMsg(t, req.Message())
resp := f.readMsg(t)
if resp == nil {
return nil, *new(Resp), false
}

// currently, the only request that may be sent by the server during a client request is one `config` request
// !!! remove if `config` is handled in initialization and there are no other server-initiated requests
if resp.Kind == lsproto.MessageKindRequest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think we should move this handling to initialization. Or are going to need to handle other requests as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what we will need in the future

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could imagine the fourslash client handling diagnostics refresh requests and potentially even watch requests.

Copy link
Member Author

@iisaduan iisaduan Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to port the fourslash/server tests, then those probably will have to be handled?

req := resp.AsRequest()
switch req.Method {
case lsproto.MethodWorkspaceConfiguration:
req := lsproto.ResponseMessage{
ID: req.ID,
JSONRPC: req.JSONRPC,
Result: []any{f.userPreferences},
}
f.writeMsg(t, req.Message())
resp = f.readMsg(t)
default:
// other types of responses not yet used in fourslash; implement them if needed
t.Fatalf("Unexpected request received: %s", req)
}
}

if resp == nil {
return nil, *new(Resp), false
}
Expand Down Expand Up @@ -300,6 +324,21 @@ func (f *FourslashTest) readMsg(t *testing.T) *lsproto.Message {
return msg
}

func (f *FourslashTest) Configure(t *testing.T, config *ls.UserPreferences) {
f.userPreferences = config
sendNotification(t, f, lsproto.WorkspaceDidChangeConfigurationInfo, &lsproto.DidChangeConfigurationParams{
Settings: config,
})
}

func (f *FourslashTest) ConfigureWithReset(t *testing.T, config *ls.UserPreferences) (reset func()) {
originalConfig := f.userPreferences.Copy()
f.Configure(t, config)
return func() {
f.Configure(t, originalConfig)
}
}

func (f *FourslashTest) GoToMarkerOrRange(t *testing.T, markerOrRange MarkerOrRange) {
f.goToMarker(t, markerOrRange)
}
Expand Down Expand Up @@ -541,6 +580,10 @@ func (f *FourslashTest) verifyCompletionsWorker(t *testing.T, expected *Completi
Position: f.currentCaretPosition,
Context: &lsproto.CompletionContext{},
}
if expected != nil && expected.UserPreferences != nil {
reset := f.ConfigureWithReset(t, expected.UserPreferences)
defer reset()
}
resMsg, result, resultOk := sendRequest(t, f, lsproto.TextDocumentCompletionInfo, params)
if resMsg == nil {
t.Fatalf(prefix+"Nil response received for completion request", f.lastKnownMarkerName)
Expand Down Expand Up @@ -1449,6 +1492,12 @@ func (f *FourslashTest) getCurrentPositionPrefix() string {
}

func (f *FourslashTest) BaselineAutoImportsCompletions(t *testing.T, markerNames []string) {
reset := f.ConfigureWithReset(t, &ls.UserPreferences{
IncludeCompletionsForModuleExports: core.TSTrue,
IncludeCompletionsForImportStatements: core.TSTrue,
})
defer reset()

for _, markerName := range markerNames {
f.GoToMarker(t, markerName)
params := &lsproto.CompletionParams{
Expand Down
15 changes: 15 additions & 0 deletions internal/fourslash/tests/autoImportCompletion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ a/**/
},
})
f.BaselineAutoImportsCompletions(t, []string{""})
f.VerifyCompletions(t, "", &fourslash.CompletionsExpectedList{
UserPreferences: &ls.UserPreferences{
// completion autoimport preferences off; this tests if fourslash server communication correctly registers changes in user preferences
IncludeCompletionsForModuleExports: core.TSUnknown,
IncludeCompletionsForImportStatements: core.TSUnknown,
},
IsIncomplete: false,
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
EditRange: Ignored,
},
Items: &fourslash.CompletionsExpectedItems{
Excludes: []string{"anotherVar"},
},
})
}

func TestAutoImportCompletion2(t *testing.T) {
Expand Down
5 changes: 4 additions & 1 deletion internal/ls/host.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package ls

import "github.com/microsoft/typescript-go/internal/sourcemap"
import (
"github.com/microsoft/typescript-go/internal/sourcemap"
)

type Host interface {
UseCaseSensitiveFileNames() bool
ReadFile(path string) (contents string, ok bool)
Converters() *Converters
UserPreferences() *UserPreferences
GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo
}
4 changes: 4 additions & 0 deletions internal/ls/languageservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ func (l *LanguageService) GetProgram() *compiler.Program {
return l.program
}

func (l *LanguageService) UserPreferences() *UserPreferences {
return l.host.UserPreferences()
}

func (l *LanguageService) tryGetProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) {
program := l.GetProgram()
file := program.GetSourceFile(fileName)
Expand Down
Loading