From 2ace8fe75fee7207b112ec35e7ad2f2af1dfcf31 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 17 Sep 2025 14:25:30 -0700 Subject: [PATCH 01/10] userpreferences --- internal/api/api.go | 6 +- internal/fourslash/fourslash.go | 36 +++++ .../tests/autoImportCompletion_test.go | 13 ++ internal/ls/languageservice.go | 20 ++- internal/ls/types.go | 18 +++ internal/lsp/server.go | 134 ++++++++++++++++-- internal/project/api.go | 2 +- internal/project/session.go | 27 +++- internal/project/snapshot.go | 8 ++ 9 files changed, 240 insertions(+), 24 deletions(-) diff --git a/internal/api/api.go b/internal/api/api.go index 15fc7c096f..5ec16cf541 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -160,7 +160,7 @@ func (api *API) GetSymbolAtPosition(ctx context.Context, projectId Handle[projec return nil, errors.New("project not found") } - languageService := ls.NewLanguageService(project, snapshot.Converters()) + languageService := ls.NewLanguageService(project, snapshot.Converters(), snapshot.UserPreferences()) symbol, err := languageService.GetSymbolAtPosition(ctx, fileName, position) if err != nil || symbol == nil { return nil, err @@ -202,7 +202,7 @@ func (api *API) GetSymbolAtLocation(ctx context.Context, projectId Handle[projec if node == nil { return nil, fmt.Errorf("node of kind %s not found at position %d in file %q", kind.String(), pos, sourceFile.FileName()) } - languageService := ls.NewLanguageService(project, snapshot.Converters()) + languageService := ls.NewLanguageService(project, snapshot.Converters(), snapshot.UserPreferences()) symbol := languageService.GetSymbolAtLocation(ctx, node) if symbol == nil { return nil, nil @@ -232,7 +232,7 @@ func (api *API) GetTypeOfSymbol(ctx context.Context, projectId Handle[project.Pr if !ok { return nil, fmt.Errorf("symbol %q not found", symbolHandle) } - languageService := ls.NewLanguageService(project, snapshot.Converters()) + languageService := ls.NewLanguageService(project, snapshot.Converters(), snapshot.UserPreferences()) t := languageService.GetTypeOfSymbol(ctx, symbol) if t == nil { return nil, nil diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 8927813263..a770860122 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -38,6 +38,7 @@ type FourslashTest struct { testData *TestData // !!! consolidate test files from test data and script info baselines map[string]*strings.Builder rangesByText *collections.MultiMap[string, *RangeMarker] + config *ls.UserPreferences scriptInfos map[string]*scriptInfo converters *ls.Converters @@ -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 { + req := resp.AsRequest() + switch req.Method { + case lsproto.MethodWorkspaceConfiguration: + req := lsproto.ResponseMessage{ + ID: req.ID, + JSONRPC: req.JSONRPC, + Result: []any{&f.config}, + } + 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 } @@ -300,6 +324,13 @@ func (f *FourslashTest) readMsg(t *testing.T) *lsproto.Message { return msg } +func (f *FourslashTest) configure(t *testing.T, config *ls.UserPreferences) { + f.config = config + sendNotification(t, f, lsproto.WorkspaceDidChangeConfigurationInfo, &lsproto.DidChangeConfigurationParams{ + Settings: config, + }) +} + func (f *FourslashTest) GoToMarkerOrRange(t *testing.T, markerOrRange MarkerOrRange) { f.goToMarker(t, markerOrRange) } @@ -541,6 +572,11 @@ func (f *FourslashTest) verifyCompletionsWorker(t *testing.T, expected *Completi Position: f.currentCaretPosition, Context: &lsproto.CompletionContext{}, } + if expected == nil { + f.configure(t, nil) + } else { + f.configure(t, expected.UserPreferences) + } resMsg, result, resultOk := sendRequest(t, f, lsproto.TextDocumentCompletionInfo, params) if resMsg == nil { t.Fatalf(prefix+"Nil response received for completion request", f.lastKnownMarkerName) diff --git a/internal/fourslash/tests/autoImportCompletion_test.go b/internal/fourslash/tests/autoImportCompletion_test.go index 53e473f0fe..3deaa93ce4 100644 --- a/internal/fourslash/tests/autoImportCompletion_test.go +++ b/internal/fourslash/tests/autoImportCompletion_test.go @@ -39,6 +39,19 @@ 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 + }, + IsIncomplete: false, + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Excludes: []string{"anotherVar"}, + }, + }) } func TestAutoImportCompletion2(t *testing.T) { diff --git a/internal/ls/languageservice.go b/internal/ls/languageservice.go index 7e47e50e34..e739f8ce4a 100644 --- a/internal/ls/languageservice.go +++ b/internal/ls/languageservice.go @@ -7,14 +7,16 @@ import ( ) type LanguageService struct { - host Host - converters *Converters + host Host + converters *Converters + userPreferences *UserPreferences } -func NewLanguageService(host Host, converters *Converters) *LanguageService { +func NewLanguageService(host Host, converters *Converters, preferences *UserPreferences) *LanguageService { return &LanguageService{ - host: host, - converters: converters, + host: host, + converters: converters, + userPreferences: preferences, } } @@ -22,6 +24,14 @@ func (l *LanguageService) GetProgram() *compiler.Program { return l.host.GetProgram() } +func (l *LanguageService) UpdateUserPreferences(preferences *UserPreferences) { + l.userPreferences = preferences +} + +func (l *LanguageService) UserPreferences() *UserPreferences { + return l.userPreferences +} + func (l *LanguageService) tryGetProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) { program := l.GetProgram() file := program.GetSourceFile(fileName) diff --git a/internal/ls/types.go b/internal/ls/types.go index f5f9d19014..17189ac54e 100644 --- a/internal/ls/types.go +++ b/internal/ls/types.go @@ -56,6 +56,24 @@ type UserPreferences struct { AutoImportFileExcludePatterns []string UseAliasesForRename *bool + + // Inlay Hints + IncludeInlayParameterNameHints string + IncludeInlayParameterNameHintsWhenArgumentMatchesName *bool + IncludeInlayFunctionParameterTypeHints *bool + IncludeInlayVariableTypeHints *bool + IncludeInlayVariableTypeHintsWhenTypeMatchesName *bool + IncludeInlayPropertyDeclarationTypeHints *bool + IncludeInlayFunctionLikeReturnTypeHints *bool + IncludeInlayEnumMemberValueHints *bool + InteractiveInlayHints *bool +} + +func (p *UserPreferences) GetOrDefault() UserPreferences { + if p == nil { + return UserPreferences{} + } + return *p } func (p *UserPreferences) ModuleSpecifierPreferences() modulespecifiers.UserPreferences { diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 165b0972db..a5849e1de0 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -217,6 +217,101 @@ func (s *Server) RefreshDiagnostics(ctx context.Context) error { return nil } +func (s *Server) Configure(ctx context.Context) (*ls.UserPreferences, error) { + result, err := s.sendRequest(ctx, lsproto.MethodWorkspaceConfiguration, &lsproto.ConfigurationParams{ + Items: []*lsproto.ConfigurationItem{ + { + Section: ptrTo("typescript"), + }, + }, + }) + if err != nil { + return nil, fmt.Errorf("configure request failed: %w", err) + } + configs := result.([]any) + userPreferences := &ls.UserPreferences{} + for _, config := range configs { + if config == nil { + continue + } + if item, ok := config.(map[string]any); ok { + for name, values := range item { + switch name { + case "inlayHints": + inlayHintsPreferences := values.(map[string]any) + if v, ok := inlayHintsPreferences["parameterNames"].(map[string]any); ok && v != nil { + if enabled, ok := v["enabled"]; ok { + if enabledStr, ok := enabled.(string); ok { + userPreferences.IncludeInlayParameterNameHints = enabledStr + } else { + userPreferences.IncludeInlayParameterNameHints = "" + } + } + if supressWhenArgumentMatchesName, ok := v["suppressWhenArgumentMatchesName"]; ok { + userPreferences.IncludeInlayParameterNameHintsWhenArgumentMatchesName = ptrTo(!supressWhenArgumentMatchesName.(bool)) + } + } + if v, ok := inlayHintsPreferences["parameterTypes"].(map[string]any); ok && v != nil { + if enabled, ok := v["enabled"]; ok { + userPreferences.IncludeInlayFunctionParameterTypeHints = ptrTo(enabled.(bool)) + } + } + if v, ok := inlayHintsPreferences["variableTypes"].(map[string]any); ok && v != nil { + if enabled, ok := v["enabled"]; ok { + userPreferences.IncludeInlayVariableTypeHints = ptrTo(enabled.(bool)) + } + if supressWhenTypeMatchesName, ok := v["suppressWhenTypeMatchesName"]; ok { + userPreferences.IncludeInlayVariableTypeHintsWhenTypeMatchesName = ptrTo(!supressWhenTypeMatchesName.(bool)) + } + } + if v, ok := inlayHintsPreferences["propertyDeclarationTypes"].(map[string]any); ok && v != nil { + if enabled, ok := v["enabled"]; ok { + userPreferences.IncludeInlayPropertyDeclarationTypeHints = ptrTo(enabled.(bool)) + } + } + if v, ok := inlayHintsPreferences["functionLikeReturnTypes"].(map[string]any); ok && v != nil { + if enabled, ok := v["enabled"]; ok { + userPreferences.IncludeInlayFunctionLikeReturnTypeHints = ptrTo(enabled.(bool)) + } + } + if v, ok := inlayHintsPreferences["enumMemberValues"].(map[string]any); ok && v != nil { + if enabled, ok := v["enabled"]; ok { + userPreferences.IncludeInlayEnumMemberValueHints = ptrTo(enabled.(bool)) + } + } + userPreferences.InteractiveInlayHints = ptrTo(true) + case "tsserver": + // !!! + case "unstable": + // !!! + case "tsc": + // !!! + case "updateImportsOnFileMove": + // !!! moveToFile + case "preferences": + // !!! + case "experimental": + // !!! + case "organizeImports": + // !!! + case "importModuleSpecifierEnding": + // !!! + } + } + continue + } + if item, ok := config.(ls.UserPreferences); ok { + // case for fourslash + userPreferences = &item + break + } + } + // !!! set defaults for services, remove after extension is updated + userPreferences.IncludeCompletionsForModuleExports = ptrTo(true) + userPreferences.IncludeCompletionsForImportStatements = ptrTo(true) + return userPreferences, nil +} + func (s *Server) Run() error { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() @@ -440,6 +535,7 @@ var handlers = sync.OnceValue(func() handlerMap { registerRequestHandler(handlers, lsproto.ShutdownInfo, (*Server).handleShutdown) registerNotificationHandler(handlers, lsproto.ExitInfo, (*Server).handleExit) + registerNotificationHandler(handlers, lsproto.WorkspaceDidChangeConfigurationInfo, (*Server).handleDidChangeWorkspaceConfiguration) registerNotificationHandler(handlers, lsproto.TextDocumentDidOpenInfo, (*Server).handleDidOpen) registerNotificationHandler(handlers, lsproto.TextDocumentDidChangeInfo, (*Server).handleDidChange) registerNotificationHandler(handlers, lsproto.TextDocumentDidSaveInfo, (*Server).handleDidSave) @@ -638,7 +734,6 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali if shouldEnableWatch(s.initializeParams) { s.watchEnabled = true } - s.session = project.NewSession(&project.SessionInit{ Options: &project.SessionOptions{ CurrentDirectory: s.cwd, @@ -655,6 +750,11 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali NpmExecutor: s, ParseCache: s.parseCache, }) + userPreferences, err := s.Configure(ctx) + if err != nil { + return err + } + s.session.Configure(userPreferences) // !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support if s.compilerOptionsForInferredProjects != nil { s.session.DidChangeCompilerOptionsForInferredProjects(ctx, s.compilerOptionsForInferredProjects) @@ -672,6 +772,16 @@ func (s *Server) handleExit(ctx context.Context, params any) error { return io.EOF } +func (s *Server) handleDidChangeWorkspaceConfiguration(ctx context.Context, params *lsproto.DidChangeConfigurationParams) error { + // !!! update user preferences + // !!! only usable by fourslash + if item, ok := params.Settings.(*ls.UserPreferences); ok { + // case for fourslash + s.session.Configure(item) + } + return nil +} + func (s *Server) handleDidOpen(ctx context.Context, params *lsproto.DidOpenTextDocumentParams) error { s.session.DidOpenFile(ctx, params.TextDocument.Uri, params.TextDocument.Version, params.TextDocument.Text, params.TextDocument.LanguageId) return nil @@ -757,10 +867,8 @@ func (s *Server) handleCompletion(ctx context.Context, languageService *ls.Langu params.Position, params.Context, getCompletionClientCapabilities(s.initializeParams), - &ls.UserPreferences{ - IncludeCompletionsForModuleExports: ptrTo(true), - IncludeCompletionsForImportStatements: ptrTo(true), - }) + languageService.UserPreferences(), + ) } func (s *Server) handleCompletionItemResolve(ctx context.Context, params *lsproto.CompletionItem, reqMsg *lsproto.RequestMessage) (lsproto.CompletionResolveResponse, error) { @@ -778,10 +886,7 @@ func (s *Server) handleCompletionItemResolve(ctx context.Context, params *lsprot params, data, getCompletionClientCapabilities(s.initializeParams), - &ls.UserPreferences{ - IncludeCompletionsForModuleExports: ptrTo(true), - IncludeCompletionsForImportStatements: ptrTo(true), - }, + languageService.UserPreferences(), ) } @@ -812,6 +917,13 @@ func (s *Server) handleDocumentOnTypeFormat(ctx context.Context, ls *ls.Language ) } +func (s *Server) handleInlayHints(ctx context.Context, ls *ls.LanguageService, params *lsproto.InlayHintParams) (lsproto.InlayHintResponse, error) { + // !!! + // userPreferences, _ := s.Configure(ctx) + // return ls.ProvideInlayHints(ctx, params, userPreferences), nil + panic("unimplemented") +} + func (s *Server) handleWorkspaceSymbol(ctx context.Context, params *lsproto.WorkspaceSymbolParams, reqMsg *lsproto.RequestMessage) (lsproto.WorkspaceSymbolResponse, error) { snapshot, release := s.session.Snapshot() defer release() @@ -855,7 +967,9 @@ func isBlockingMethod(method lsproto.Method) bool { lsproto.MethodTextDocumentDidChange, lsproto.MethodTextDocumentDidSave, lsproto.MethodTextDocumentDidClose, - lsproto.MethodWorkspaceDidChangeWatchedFiles: + lsproto.MethodWorkspaceDidChangeWatchedFiles, + lsproto.MethodWorkspaceDidChangeConfiguration, + lsproto.MethodWorkspaceConfiguration: return true } return false diff --git a/internal/project/api.go b/internal/project/api.go index ea84b3adb5..ad96de170f 100644 --- a/internal/project/api.go +++ b/internal/project/api.go @@ -7,7 +7,7 @@ import ( ) func (s *Session) OpenProject(ctx context.Context, configFileName string) (*Project, error) { - fileChanges, overlays, ataChanges := s.flushChanges(ctx) + fileChanges, overlays, ataChanges, _ := s.flushChanges(ctx) newSnapshot := s.UpdateSnapshot(ctx, overlays, SnapshotChange{ fileChanges: fileChanges, ataChanges: ataChanges, diff --git a/internal/project/session.go b/internal/project/session.go index 4e78c28884..7cb94f6261 100644 --- a/internal/project/session.go +++ b/internal/project/session.go @@ -79,6 +79,7 @@ type Session struct { // released from the parseCache. programCounter *programCounter + userPreferences *ls.UserPreferences compilerOptionsForInferredProjects *core.CompilerOptions typingsInstaller *ata.TypingsInstaller backgroundQueue *background.Queue @@ -92,6 +93,9 @@ type Session struct { snapshot *Snapshot snapshotMu sync.RWMutex + pendingConfigChanges bool + pendingConfigChangesMu sync.Mutex + // pendingFileChanges are accumulated from textDocument/* events delivered // by the LSP server through DidOpenFile(), DidChangeFile(), etc. They are // applied to the next snapshot update. @@ -146,6 +150,7 @@ func NewSession(init *SessionInit) *Session { extendedConfigCache, &ConfigFileRegistry{}, nil, + nil, toPath, ), pendingATAChanges: make(map[tspath.Path]*ATAStateChange), @@ -176,6 +181,13 @@ func (s *Session) Trace(msg string) { panic("ATA module resolution should not use tracing") } +func (s *Session) Configure(userPreferences *ls.UserPreferences) { + s.pendingConfigChangesMu.Lock() + defer s.pendingConfigChangesMu.Unlock() + s.pendingConfigChanges = true + s.userPreferences = userPreferences +} + func (s *Session) DidOpenFile(ctx context.Context, uri lsproto.DocumentUri, version int32, content string, languageKind lsproto.LanguageKind) { s.cancelDiagnosticsRefresh() s.pendingFileChangesMu.Lock() @@ -335,8 +347,8 @@ func (s *Session) Snapshot() (*Snapshot, func()) { func (s *Session) GetLanguageService(ctx context.Context, uri lsproto.DocumentUri) (*ls.LanguageService, error) { var snapshot *Snapshot - fileChanges, overlays, ataChanges := s.flushChanges(ctx) - updateSnapshot := !fileChanges.IsEmpty() || len(ataChanges) > 0 + fileChanges, overlays, ataChanges, updateConfig := s.flushChanges(ctx) + updateSnapshot := !fileChanges.IsEmpty() || len(ataChanges) > 0 || updateConfig if updateSnapshot { // If there are pending file changes, we need to update the snapshot. // Sending the requested URI ensures that the project for this URI is loaded. @@ -367,7 +379,7 @@ func (s *Session) GetLanguageService(ctx context.Context, uri lsproto.DocumentUr if project == nil { return nil, fmt.Errorf("no project found for URI %s", uri) } - return ls.NewLanguageService(project, snapshot.Converters()), nil + return ls.NewLanguageService(project, snapshot.Converters(), snapshot.UserPreferences()), nil } func (s *Session) UpdateSnapshot(ctx context.Context, overlays map[tspath.Path]*overlay, change SnapshotChange) *Snapshot { @@ -388,6 +400,7 @@ func (s *Session) UpdateSnapshot(ctx context.Context, overlays map[tspath.Path]* } // Enqueue logging, watch updates, and diagnostic refresh tasks + // !!! userPreferences/configuration updates s.backgroundQueue.Enqueue(context.Background(), func(ctx context.Context) { if s.options.LoggingEnabled { s.logger.Write(newSnapshot.builderLogs.String()) @@ -506,7 +519,7 @@ func (s *Session) Close() { s.backgroundQueue.Close() } -func (s *Session) flushChanges(ctx context.Context) (FileChangeSummary, map[tspath.Path]*overlay, map[tspath.Path]*ATAStateChange) { +func (s *Session) flushChanges(ctx context.Context) (FileChangeSummary, map[tspath.Path]*overlay, map[tspath.Path]*ATAStateChange, bool) { s.pendingFileChangesMu.Lock() defer s.pendingFileChangesMu.Unlock() s.pendingATAChangesMu.Lock() @@ -514,7 +527,11 @@ func (s *Session) flushChanges(ctx context.Context) (FileChangeSummary, map[tspa pendingATAChanges := s.pendingATAChanges s.pendingATAChanges = make(map[tspath.Path]*ATAStateChange) fileChanges, overlays := s.flushChangesLocked(ctx) - return fileChanges, overlays, pendingATAChanges + s.pendingConfigChangesMu.Lock() + updateConfig := s.pendingConfigChanges + s.pendingConfigChanges = false + defer s.pendingConfigChangesMu.Unlock() + return fileChanges, overlays, pendingATAChanges, updateConfig } // flushChangesLocked should only be called with s.pendingFileChangesMu held. diff --git a/internal/project/snapshot.go b/internal/project/snapshot.go index b5d7d1722e..28cdf5308b 100644 --- a/internal/project/snapshot.go +++ b/internal/project/snapshot.go @@ -32,6 +32,7 @@ type Snapshot struct { ProjectCollection *ProjectCollection ConfigFileRegistry *ConfigFileRegistry compilerOptionsForInferredProjects *core.CompilerOptions + userPreferences ls.UserPreferences builderLogs *logging.LogTree apiError error @@ -46,6 +47,7 @@ func NewSnapshot( extendedConfigCache *extendedConfigCache, configFileRegistry *ConfigFileRegistry, compilerOptionsForInferredProjects *core.CompilerOptions, + userPreferences *ls.UserPreferences, toPath func(fileName string) tspath.Path, ) *Snapshot { s := &Snapshot{ @@ -58,6 +60,7 @@ func NewSnapshot( ConfigFileRegistry: configFileRegistry, ProjectCollection: &ProjectCollection{toPath: toPath}, compilerOptionsForInferredProjects: compilerOptionsForInferredProjects, + userPreferences: userPreferences.GetOrDefault(), } s.converters = ls.NewConverters(s.sessionOptions.PositionEncoding, s.LineMap) s.refCount.Store(1) @@ -81,6 +84,10 @@ func (s *Snapshot) LineMap(fileName string) *ls.LineMap { return nil } +func (s *Snapshot) UserPreferences() *ls.UserPreferences { + return &s.userPreferences +} + func (s *Snapshot) Converters() *ls.Converters { return s.converters } @@ -234,6 +241,7 @@ func (s *Snapshot) Clone(ctx context.Context, change SnapshotChange, overlays ma session.extendedConfigCache, nil, compilerOptionsForInferredProjects, + session.userPreferences, s.toPath, ) newSnapshot.parentId = s.id From dbfff2667063a39ee9cc3e981fc02fc7a9c738c3 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 17 Sep 2025 14:53:31 -0700 Subject: [PATCH 02/10] remove unused fucntiosn --- internal/lsp/server.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index a5849e1de0..cdd0a4872e 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -917,13 +917,6 @@ func (s *Server) handleDocumentOnTypeFormat(ctx context.Context, ls *ls.Language ) } -func (s *Server) handleInlayHints(ctx context.Context, ls *ls.LanguageService, params *lsproto.InlayHintParams) (lsproto.InlayHintResponse, error) { - // !!! - // userPreferences, _ := s.Configure(ctx) - // return ls.ProvideInlayHints(ctx, params, userPreferences), nil - panic("unimplemented") -} - func (s *Server) handleWorkspaceSymbol(ctx context.Context, params *lsproto.WorkspaceSymbolParams, reqMsg *lsproto.RequestMessage) (lsproto.WorkspaceSymbolResponse, error) { snapshot, release := s.session.Snapshot() defer release() From ff140e9748664f197243860d4b8a38f13935ed8e Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Thu, 18 Sep 2025 13:35:12 -0700 Subject: [PATCH 03/10] fourslash updates --- internal/fourslash/fourslash.go | 29 +++++++++++++++++++++-------- internal/ls/languageservice.go | 4 ---- internal/ls/types.go | 15 ++++++++++++--- internal/project/snapshot.go | 6 +++--- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index a770860122..740771ffe3 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -38,11 +38,11 @@ type FourslashTest struct { testData *TestData // !!! consolidate test files from test data and script info baselines map[string]*strings.Builder rangesByText *collections.MultiMap[string, *RangeMarker] - config *ls.UserPreferences scriptInfos map[string]*scriptInfo converters *ls.Converters + userPreferences *ls.UserPreferences currentCaretPosition lsproto.Position lastKnownMarkerName *string activeFilename string @@ -282,7 +282,7 @@ func sendRequest[Params, Resp any](t *testing.T, f *FourslashTest, info lsproto. req := lsproto.ResponseMessage{ ID: req.ID, JSONRPC: req.JSONRPC, - Result: []any{&f.config}, + Result: []any{&f.userPreferences}, } f.writeMsg(t, req.Message()) resp = f.readMsg(t) @@ -324,13 +324,21 @@ func (f *FourslashTest) readMsg(t *testing.T) *lsproto.Message { return msg } -func (f *FourslashTest) configure(t *testing.T, config *ls.UserPreferences) { - f.config = config +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) } @@ -572,10 +580,9 @@ func (f *FourslashTest) verifyCompletionsWorker(t *testing.T, expected *Completi Position: f.currentCaretPosition, Context: &lsproto.CompletionContext{}, } - if expected == nil { - f.configure(t, nil) - } else { - f.configure(t, expected.UserPreferences) + 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 { @@ -1408,6 +1415,12 @@ func (f *FourslashTest) getCurrentPositionPrefix() string { } func (f *FourslashTest) BaselineAutoImportsCompletions(t *testing.T, markerNames []string) { + reset := f.ConfigureWithReset(t, &ls.UserPreferences{ + IncludeCompletionsForModuleExports: ptrTo(true), + IncludeCompletionsForImportStatements: ptrTo(true), + }) + defer reset() + for _, markerName := range markerNames { f.GoToMarker(t, markerName) params := &lsproto.CompletionParams{ diff --git a/internal/ls/languageservice.go b/internal/ls/languageservice.go index e739f8ce4a..1af4ffef22 100644 --- a/internal/ls/languageservice.go +++ b/internal/ls/languageservice.go @@ -24,10 +24,6 @@ func (l *LanguageService) GetProgram() *compiler.Program { return l.host.GetProgram() } -func (l *LanguageService) UpdateUserPreferences(preferences *UserPreferences) { - l.userPreferences = preferences -} - func (l *LanguageService) UserPreferences() *UserPreferences { return l.userPreferences } diff --git a/internal/ls/types.go b/internal/ls/types.go index 17189ac54e..c4e486cb37 100644 --- a/internal/ls/types.go +++ b/internal/ls/types.go @@ -69,11 +69,20 @@ type UserPreferences struct { InteractiveInlayHints *bool } -func (p *UserPreferences) GetOrDefault() UserPreferences { +func (p *UserPreferences) Copy() *UserPreferences { + // not a true deep copy if p == nil { - return UserPreferences{} + return nil } - return *p + copy := *p + return © +} + +func (p *UserPreferences) CopyOrDefault() *UserPreferences { + if p == nil { + return &UserPreferences{} + } + return p.Copy() } func (p *UserPreferences) ModuleSpecifierPreferences() modulespecifiers.UserPreferences { diff --git a/internal/project/snapshot.go b/internal/project/snapshot.go index 28cdf5308b..4af6ff80f7 100644 --- a/internal/project/snapshot.go +++ b/internal/project/snapshot.go @@ -32,7 +32,7 @@ type Snapshot struct { ProjectCollection *ProjectCollection ConfigFileRegistry *ConfigFileRegistry compilerOptionsForInferredProjects *core.CompilerOptions - userPreferences ls.UserPreferences + userPreferences *ls.UserPreferences builderLogs *logging.LogTree apiError error @@ -60,7 +60,7 @@ func NewSnapshot( ConfigFileRegistry: configFileRegistry, ProjectCollection: &ProjectCollection{toPath: toPath}, compilerOptionsForInferredProjects: compilerOptionsForInferredProjects, - userPreferences: userPreferences.GetOrDefault(), + userPreferences: userPreferences.CopyOrDefault(), } s.converters = ls.NewConverters(s.sessionOptions.PositionEncoding, s.LineMap) s.refCount.Store(1) @@ -85,7 +85,7 @@ func (s *Snapshot) LineMap(fileName string) *ls.LineMap { } func (s *Snapshot) UserPreferences() *ls.UserPreferences { - return &s.userPreferences + return s.userPreferences } func (s *Snapshot) Converters() *ls.Converters { From db2f0397ee53caa98f55fa3700042294d2ee3984 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Mon, 6 Oct 2025 23:15:10 -0700 Subject: [PATCH 04/10] preference parsing --- internal/ls/userpreferences.go | 424 +++++++++++++++++++++++++-- internal/lsp/server.go | 83 +----- internal/modulespecifiers/types.go | 4 +- internal/tsoptions/parsinghelpers.go | 268 ++++++++--------- 4 files changed, 547 insertions(+), 232 deletions(-) diff --git a/internal/ls/userpreferences.go b/internal/ls/userpreferences.go index a61e82d4c7..13d8d857d4 100644 --- a/internal/ls/userpreferences.go +++ b/internal/ls/userpreferences.go @@ -1,10 +1,27 @@ package ls import ( + "strings" + "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/modulespecifiers" + "github.com/microsoft/typescript-go/internal/tsoptions" ) +func NewDefaultUserPreferences() *UserPreferences { + return &UserPreferences{ + IncludeCompletionsForModuleExports: core.TSTrue, + IncludeCompletionsForImportStatements: core.TSTrue, + + AllowRenameOfImportPath: true, + ProvideRefactorNotApplicableReason: true, + IncludeCompletionsWithSnippetText: core.TSTrue, + DisplayPartsForJSDoc: true, + DisableLineTextInReferences: true, + InteractiveInlayHints: true, + } +} + type UserPreferences struct { QuotePreference QuotePreference LazyConfiguredProjectsFromExternalProject bool // !!! @@ -84,7 +101,7 @@ type UserPreferences struct { // This preference is ignored if organizeImportsCollation is not `unicode`. // // Default: `true` - OrganizeImportsAccentCollation OrganizeImportsAccentCollation // !!! + OrganizeImportsAccentCollation bool // !!! // Indicates whether upper case or lower case should sort first. When `false`, the default order for the locale // specified in organizeImportsCollationLocale is used. // @@ -97,7 +114,7 @@ type UserPreferences struct { OrganizeImportsCaseFirst OrganizeImportsCaseFirst // !!! // Indicates where named type-only imports should sort. "inline" sorts named imports without regard to if the import is type-only. // - // Default: `last` + // Default: `auto`, which defaults to `last` OrganizeImportsTypeOrder OrganizeImportsTypeOrder // !!! // ------- MoveToFile ------- @@ -137,12 +154,52 @@ type UserPreferences struct { type JsxAttributeCompletionStyle string const ( - JsxAttributeCompletionStyleUnknown JsxAttributeCompletionStyle = "" + JsxAttributeCompletionStyleUnknown JsxAttributeCompletionStyle = "" // !!! JsxAttributeCompletionStyleAuto JsxAttributeCompletionStyle = "auto" JsxAttributeCompletionStyleBraces JsxAttributeCompletionStyle = "braces" JsxAttributeCompletionStyleNone JsxAttributeCompletionStyle = "none" ) +func parseJsxAttributeCompletionStyle(val any) JsxAttributeCompletionStyle { + if s, ok := val.(string); ok { + switch strings.ToLower(s) { + case "braces": + return JsxAttributeCompletionStyleBraces + case "none": + return JsxAttributeCompletionStyleNone + } + } + return JsxAttributeCompletionStyleAuto +} + +func parseImportModuleSpecifierPreference(val any) modulespecifiers.ImportModuleSpecifierPreference { + if s, ok := val.(string); ok { + switch strings.ToLower(s) { + case "project-relative": + return modulespecifiers.ImportModuleSpecifierPreferenceProjectRelative + case "relative": + return modulespecifiers.ImportModuleSpecifierPreferenceRelative + case "non-relative": + return modulespecifiers.ImportModuleSpecifierPreferenceNonRelative + } + } + return modulespecifiers.ImportModuleSpecifierPreferenceShortest +} + +func parseImportModuleSpecifierEndingPreference(val any) modulespecifiers.ImportModuleSpecifierEndingPreference { + if s, ok := val.(string); ok { + switch strings.ToLower(s) { + case "minimal": + return modulespecifiers.ImportModuleSpecifierEndingPreferenceMinimal + case "index": + return modulespecifiers.ImportModuleSpecifierEndingPreferenceIndex + case "js": + return modulespecifiers.ImportModuleSpecifierEndingPreferenceJs + } + } + return modulespecifiers.ImportModuleSpecifierEndingPreferenceAuto +} + type IncludeInlayParameterNameHints string const ( @@ -151,15 +208,41 @@ const ( IncludeInlayParameterNameHintsLiterals IncludeInlayParameterNameHints = "literals" ) +func parseInlayParameterNameHints(val any) IncludeInlayParameterNameHints { + if prefStr, ok := val.(string); ok { + switch prefStr { + case "all": + return IncludeInlayParameterNameHintsAll + case "literals": + return IncludeInlayParameterNameHintsLiterals + } + } + return IncludeInlayParameterNameHintsNone +} + type IncludePackageJsonAutoImports string const ( - IncludePackageJsonAutoImportsUnknown IncludePackageJsonAutoImports = "" + IncludePackageJsonAutoImportsUnknown IncludePackageJsonAutoImports = "" // !!! IncludePackageJsonAutoImportsAuto IncludePackageJsonAutoImports = "auto" IncludePackageJsonAutoImportsOn IncludePackageJsonAutoImports = "on" IncludePackageJsonAutoImportsOff IncludePackageJsonAutoImports = "off" ) +func parseIncludePackageJsonAutoImports(val any) IncludePackageJsonAutoImports { + if s, ok := val.(string); ok { + switch strings.ToLower(s) { + case "on": + return IncludePackageJsonAutoImportsOn + case "off": + return IncludePackageJsonAutoImportsOff + default: + return IncludePackageJsonAutoImportsAuto + } + } + return IncludePackageJsonAutoImportsUnknown +} + type OrganizeImportsCollation bool const ( @@ -167,12 +250,12 @@ const ( OrganizeImportsCollationUnicode OrganizeImportsCollation = true ) -type OrganizeImportsAccentCollation int - -const ( - OrganizeImportsAccentCollationTrue OrganizeImportsAccentCollation = 0 - OrganizeImportsAccentCollationFalse OrganizeImportsAccentCollation = 1 -) +func parseOrganizeImportsCollation(val any) OrganizeImportsCollation { + if b, ok := val.(string); ok && strings.ToLower(b) == "unicode" { + return OrganizeImportsCollationUnicode + } + return OrganizeImportsCollationOrdinal +} type OrganizeImportsCaseFirst int @@ -182,14 +265,41 @@ const ( OrganizeImportsCaseFirstUpper OrganizeImportsCaseFirst = 2 ) +func parseOrganizeImportsCaseFirst(caseFirst any) OrganizeImportsCaseFirst { + if caseFirstStr, ok := caseFirst.(string); ok { + switch caseFirstStr { + case "lower": + return OrganizeImportsCaseFirstLower + case "upper": + return OrganizeImportsCaseFirstUpper + } + } + return OrganizeImportsCaseFirstFalse +} + type OrganizeImportsTypeOrder int const ( - OrganizeImportsTypeOrderLast OrganizeImportsTypeOrder = 0 - OrganizeImportsTypeOrderInline OrganizeImportsTypeOrder = 1 - OrganizeImportsTypeOrderFirst OrganizeImportsTypeOrder = 2 + OrganizeImportsTypeOrderAuto OrganizeImportsTypeOrder = 0 + OrganizeImportsTypeOrderLast OrganizeImportsTypeOrder = 1 + OrganizeImportsTypeOrderInline OrganizeImportsTypeOrder = 2 + OrganizeImportsTypeOrderFirst OrganizeImportsTypeOrder = 3 ) +func parseOrganizeImportsTypeOrder(typeOrder any) OrganizeImportsTypeOrder { + if typeOrderStr, ok := typeOrder.(string); ok { + switch typeOrderStr { + case "last": + return OrganizeImportsTypeOrderLast + case "inline": + return OrganizeImportsTypeOrderInline + case "first": + return OrganizeImportsTypeOrderFirst + } + } + return OrganizeImportsTypeOrderAuto +} + type QuotePreference string const ( @@ -199,24 +309,33 @@ const ( QuotePreferenceSingle QuotePreference = "single" ) -func (p *UserPreferences) Parse(config map[string]interface{}) { +func parseQuotePreference(val any) QuotePreference { + if s, ok := val.(string); ok { + switch strings.ToLower(s) { + case "auto": + return QuotePreferenceAuto + case "double": + return QuotePreferenceDouble + case "single": + return QuotePreferenceSingle + } + } + return QuotePreferenceUnknown } func (p *UserPreferences) Copy() *UserPreferences { - // not a true deep copy if p == nil { return nil } - copy := *p - return © + prefCopy := *p + copy(prefCopy.AutoImportSpecifierExcludeRegexes, p.AutoImportSpecifierExcludeRegexes) + copy(prefCopy.AutoImportFileExcludePatterns, p.AutoImportFileExcludePatterns) + return &prefCopy } func (p *UserPreferences) CopyOrDefault() *UserPreferences { if p == nil { - return &UserPreferences{ - IncludeCompletionsForModuleExports: core.TSTrue, - IncludeCompletionsForImportStatements: core.TSTrue, - } + return NewDefaultUserPreferences() } return p.Copy() } @@ -228,3 +347,266 @@ func (p *UserPreferences) ModuleSpecifierPreferences() modulespecifiers.UserPref AutoImportSpecifierExcludeRegexes: p.AutoImportSpecifierExcludeRegexes, } } + +// ------ Parsing Config Response ------- +func (p *UserPreferences) Parse(config map[string]interface{}) { + // Process unstable preferences first so that they do not overwrite stable properties + if unstable, ok := config["unstable"]; ok { + // unstable properties must be named the same as userPreferences + p.parseAll(unstable) + } + for name, values := range config { + switch name { + case "unstable": + continue + case "inlayHints": + p.parseInlayHints(values) + case "suggest": + p.parseSuggest(values) + case "preferences": + p.parsePreferences(values) + case "tsserver": + // !!! + case "tsc": + // !!! + case "experimental": + // !!! + default: + p.set(name, values) + } + } +} + +func (p *UserPreferences) parseAll(prefs any) { + prefsMap, ok := prefs.(map[string]any) + if !ok { + return + } + for name, value := range prefsMap { + p.set(name, value) + } +} + +func (p *UserPreferences) parseInlayHints(prefs any) { + inlayHintsPreferences, ok := prefs.(map[string]any) + if !ok { + return + } + for name, value := range inlayHintsPreferences { + if v, ok := value.(map[string]any); ok { + // vscode's inlay hints settings are nested objects with "enabled" and other properties + switch name { + case "parameterNames": + if enabled, ok := v["enabled"]; ok { + p.set("includeInlayParameterNameHints", enabled) + } + p.IncludeInlayParameterNameHintsWhenArgumentMatchesName = parseSupress(v, "supressWhenArgumentMatchesName") + case "parameterTypes": + p.IncludeInlayFunctionParameterTypeHints = parseEnabledBool(v) + case "variableTypes": + p.IncludeInlayVariableTypeHints = parseEnabledBool(v) + p.IncludeInlayVariableTypeHintsWhenTypeMatchesName = parseSupress(v, "supressWhenTypeMatchesName") + case "propertyDeclarationTypes": + p.IncludeInlayPropertyDeclarationTypeHints = parseEnabledBool(v) + case "functionLikeReturnTypes": + p.IncludeInlayFunctionLikeReturnTypeHints = parseEnabledBool(v) + case "enumMemberValues": + p.IncludeInlayEnumMemberValueHints = parseEnabledBool(v) + } + } else { + // non-vscode case + p.set(name, v) + } + } +} + +func (p *UserPreferences) parseSuggest(prefs any) { + completionsPreferences, ok := prefs.(map[string]any) + if !ok { + return + } + for name, value := range completionsPreferences { + switch name { + case "autoImports": + p.set("includeCompletionsForModuleExports", value) + case "objectLiteralMethodSnippets": + if v, ok := value.(map[string]any); ok { + p.set("includeCompletionsWithObjectLiteralMethodSnippets", parseEnabledBool(v)) + } + case "classMemberSnippets": + if v, ok := value.(map[string]any); ok { + p.set("includeCompletionsWithClassMemberSnippets", parseEnabledBool(v)) + } + case "includeAutomaticOptionalChainCompletions": + p.set("includeAutomaticOptionalChainCompletions", value) + case "includeCompletionsForImportStatements": + p.set("includeCompletionsForImportStatements", value) + } + } +} + +func (p *UserPreferences) parsePreferences(prefs any) { + prefsMap, ok := prefs.(map[string]any) + if !ok { + return + } + for name, value := range prefsMap { + if name == "organizeImports" { + p.parseOrganizeImportsPreferences(value) + } else { + p.set(name, value) + } + } +} + +func (p *UserPreferences) parseOrganizeImportsPreferences(prefs any) { + // !!! this used to be in the typescript-language-features extension + prefsMap, ok := prefs.(map[string]any) + if !ok { + return + } + if typeOrder, ok := prefsMap["typeOrder"]; ok { + p.set("organizeimportstypeorder", parseOrganizeImportsTypeOrder(typeOrder)) + } + if caseSensitivity, ok := prefsMap["caseSensitivity"]; ok { + if caseSensitivityStr, ok := caseSensitivity.(string); ok { + // default is already "auto" + switch caseSensitivityStr { + case "caseInsensitive": + p.OrganizeImportsIgnoreCase = core.TSTrue + case "caseSensitive": + p.OrganizeImportsIgnoreCase = core.TSFalse + } + } + } + if collation, ok := prefsMap["unicodeCollation"]; ok { + // The rest of the settings are only applicable when using unicode collation + if collationStr, ok := collation.(string); ok && collationStr == "unicode" { + p.set("organizeimportscollation", OrganizeImportsCollationUnicode) + if locale, ok := prefsMap["locale"]; ok { + p.set("organizeimportslocale", locale) + } + if numeric, ok := prefsMap["numericCollation"]; ok { + p.set("organizeimportsnumericcollation", numeric) + } + if accent, ok := prefsMap["accentCollation"]; ok { + p.set("organizeimportsaccentcollation", accent) + } + if caseFirst, ok := prefsMap["caseFirst"]; ok && !p.OrganizeImportsIgnoreCase.IsTrue() { + p.set("organizeimportscasefirst", caseFirst) + } + } + } +} + +func parseEnabledBool(v map[string]any) bool { + // vscode nested option + if enabled, ok := v["enabled"]; ok { + if e, ok := enabled.(bool); ok { + return e + } + } + return false +} + +func parseSupress(v map[string]any, name string) bool { + // vscode nested option + if val, ok := v[name]; ok { + if suppress, ok := val.(bool); ok { + return !suppress + } + } + return false +} + +func parseBoolWithDefault(val any, defaultV bool) bool { + if v, ok := val.(bool); ok { + return v + } + return defaultV +} + +func (p *UserPreferences) set(name string, value any) { + switch strings.ToLower(name) { + case "quotePreference": + p.QuotePreference = parseQuotePreference(value) + case "lazyconfiguredprojectsfromexternalproject": + p.LazyConfiguredProjectsFromExternalProject = parseBoolWithDefault(value, false) + // case "maximumhoverlength": + // p.MaximumHoverLength = tsoptions.ParseInt(value, 500) + case "includecompletionsformoduleexports": + p.IncludeCompletionsForModuleExports = tsoptions.ParseTristate(value) + case "includecompletionsforimportstatements": + p.IncludeCompletionsForImportStatements = tsoptions.ParseTristate(value) + case "includeautomaticoptionalchaincompletions": + p.IncludeAutomaticOptionalChainCompletions = tsoptions.ParseTristate(value) + case "includecompletionswithsnippettext": + p.IncludeCompletionsWithSnippetText = tsoptions.ParseTristate(value) + case "includecompletionswithclassmembersnippets": + p.IncludeCompletionsWithClassMemberSnippets = tsoptions.ParseTristate(value) + case "includecompletionswithobjectliteralmethodsnippets": + p.IncludeCompletionsWithObjectLiteralMethodSnippets = tsoptions.ParseTristate(value) + case "jsxattributecompletionstyle": + p.JsxAttributeCompletionStyle = parseJsxAttributeCompletionStyle(value) + case "importmodulespecifierpreference": + p.ImportModuleSpecifierPreference = parseImportModuleSpecifierPreference(value) + case "importmodulespecifierending": + p.ImportModuleSpecifierEnding = parseImportModuleSpecifierEndingPreference(value) + case "includepackagejsonautoimports": + p.IncludePackageJsonAutoImports = parseIncludePackageJsonAutoImports(value) + case "autoimportspecifierexcluderegexes": + p.AutoImportSpecifierExcludeRegexes = tsoptions.ParseStringArray(value) + case "autoimportfileexcludepatterns": + p.AutoImportFileExcludePatterns = tsoptions.ParseStringArray(value) + case "prefertypeonlyautoimports": + p.PreferTypeOnlyAutoImports = parseBoolWithDefault(value, false) + case "organizeimportsignorecase": + p.OrganizeImportsIgnoreCase = tsoptions.ParseTristate(value) + case "organizeimportscollation": + p.OrganizeImportsCollation = parseOrganizeImportsCollation(value) + case "organizeimportslocale": + p.OrganizeImportsLocale = tsoptions.ParseString(value) + case "organizeimportsnumericcollation": + p.OrganizeImportsNumericCollation = parseBoolWithDefault(value, false) + case "organizeimportsaccentcollation": + p.OrganizeImportsAccentCollation = parseBoolWithDefault(value, true) + case "organizeimportscasefirst": + p.OrganizeImportsCaseFirst = parseOrganizeImportsCaseFirst(value) + case "organizeimportstypeorder": + p.OrganizeImportsTypeOrder = parseOrganizeImportsTypeOrder(value) + case "allowtextchangesinnewfiles": + p.AllowTextChangesInNewFiles = parseBoolWithDefault(value, true) // !!! + case "usealiasesforrename", "provideprefixandsuffixtextforrename": + p.UseAliasesForRename = tsoptions.ParseTristate(value) + case "allowrenameofimportpath": + p.AllowRenameOfImportPath = parseBoolWithDefault(value, true) + case "providerefactornotapplicablereason": + p.ProvideRefactorNotApplicableReason = parseBoolWithDefault(value, true) + case "includeinlayparameternamehints": + p.IncludeInlayParameterNameHints = parseInlayParameterNameHints(value) + case "includeinlayparameternamehintswhenargumentmatchesname": + p.IncludeInlayParameterNameHintsWhenArgumentMatchesName = parseBoolWithDefault(value, false) + case "includeinlayfunctionparametertypeHints": + p.IncludeInlayFunctionParameterTypeHints = parseBoolWithDefault(value, false) + case "includeinlayvariabletypehints": + p.IncludeInlayVariableTypeHints = parseBoolWithDefault(value, false) + case "includeinlayvariabletypehintswhentypematchesname": + p.IncludeInlayVariableTypeHintsWhenTypeMatchesName = parseBoolWithDefault(value, false) + case "includeinlaypropertydeclarationtypehints": + p.IncludeInlayPropertyDeclarationTypeHints = parseBoolWithDefault(value, false) + case "includeinlayfunctionlikereturntypehints": + p.IncludeInlayFunctionLikeReturnTypeHints = parseBoolWithDefault(value, false) + case "includeinlayenummembervaluehints": + p.IncludeInlayEnumMemberValueHints = parseBoolWithDefault(value, false) + case "interactiveinlayhints": + p.InteractiveInlayHints = parseBoolWithDefault(value, true) + case "excludelibrarysymbolsinnavto": + p.ExcludeLibrarySymbolsInNavTo = parseBoolWithDefault(value, false) + case "disablesuggestions": + p.DisableSuggestions = parseBoolWithDefault(value, false) + case "disablelinetextinreferences": + p.DisableLineTextInReferences = parseBoolWithDefault(value, true) + case "displaypartsforjsdoc": + p.DisplayPartsForJSDoc = parseBoolWithDefault(value, true) + } +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 877e258a18..b5fcb7634a 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -230,86 +230,19 @@ func (s *Server) Configure(ctx context.Context) (*ls.UserPreferences, error) { return nil, fmt.Errorf("configure request failed: %w", err) } configs := result.([]any) - userPreferences := &ls.UserPreferences{} - for _, config := range configs { - if config == nil { - continue - } - if item, ok := config.(map[string]any); ok { - for name, values := range item { - switch name { - case "inlayHints": - inlayHintsPreferences := values.(map[string]any) - if v, ok := inlayHintsPreferences["parameterNames"].(map[string]any); ok && v != nil { - if enabled, ok := v["enabled"]; ok { - if enabledStr, ok := enabled.(string); ok { - userPreferences.IncludeInlayParameterNameHints = ls.IncludeInlayParameterNameHints(enabledStr) - } else { - userPreferences.IncludeInlayParameterNameHints = ls.IncludeInlayParameterNameHintsNone - } - } - if supressWhenArgumentMatchesName, ok := v["suppressWhenArgumentMatchesName"]; ok { - userPreferences.IncludeInlayParameterNameHintsWhenArgumentMatchesName = !supressWhenArgumentMatchesName.(bool) - } - } - if v, ok := inlayHintsPreferences["parameterTypes"].(map[string]any); ok && v != nil { - if enabled, ok := v["enabled"]; ok { - userPreferences.IncludeInlayFunctionParameterTypeHints = enabled.(bool) - } - } - if v, ok := inlayHintsPreferences["variableTypes"].(map[string]any); ok && v != nil { - if enabled, ok := v["enabled"]; ok { - userPreferences.IncludeInlayVariableTypeHints = enabled.(bool) - } - if supressWhenTypeMatchesName, ok := v["suppressWhenTypeMatchesName"]; ok { - userPreferences.IncludeInlayVariableTypeHintsWhenTypeMatchesName = !supressWhenTypeMatchesName.(bool) - } - } - if v, ok := inlayHintsPreferences["propertyDeclarationTypes"].(map[string]any); ok && v != nil { - if enabled, ok := v["enabled"]; ok { - userPreferences.IncludeInlayPropertyDeclarationTypeHints = enabled.(bool) - } - } - if v, ok := inlayHintsPreferences["functionLikeReturnTypes"].(map[string]any); ok && v != nil { - if enabled, ok := v["enabled"]; ok { - userPreferences.IncludeInlayFunctionLikeReturnTypeHints = enabled.(bool) - } - } - if v, ok := inlayHintsPreferences["enumMemberValues"].(map[string]any); ok && v != nil { - if enabled, ok := v["enabled"]; ok { - userPreferences.IncludeInlayEnumMemberValueHints = enabled.(bool) - } - } - userPreferences.InteractiveInlayHints = true - case "tsserver": - // - case "unstable": - // !!! - case "tsc": - // !!! - case "updateImportsOnFileMove": - // !!! moveToFile - case "preferences": - // !!! - case "experimental": - // !!! - case "organizeImports": - // !!! - case "importModuleSpecifierEnding": - // !!! - } - } - continue - } - if item, ok := config.(ls.UserPreferences); ok { + s.Log(fmt.Sprintf("\n\nconfiguration: %+v, %T\n\n", configs, configs)) + userPreferences := ls.NewDefaultUserPreferences() + for _, item := range configs { + if item == nil { + // continue + } else if config, ok := item.(map[string]any); ok { + userPreferences.Parse(config) + } else if item, ok := item.(ls.UserPreferences); ok { // case for fourslash userPreferences = &item break } } - // !!! set defaults for services, remove after extension is updated - userPreferences.IncludeCompletionsForModuleExports = core.TSTrue - userPreferences.IncludeCompletionsForImportStatements = core.TSTrue return userPreferences, nil } diff --git a/internal/modulespecifiers/types.go b/internal/modulespecifiers/types.go index 6cdfa3e689..c83dde2d83 100644 --- a/internal/modulespecifiers/types.go +++ b/internal/modulespecifiers/types.go @@ -68,7 +68,7 @@ type ModuleSpecifierGenerationHost interface { type ImportModuleSpecifierPreference string const ( - ImportModuleSpecifierPreferenceNone ImportModuleSpecifierPreference = "" + ImportModuleSpecifierPreferenceNone ImportModuleSpecifierPreference = "" // !!! ImportModuleSpecifierPreferenceShortest ImportModuleSpecifierPreference = "shortest" ImportModuleSpecifierPreferenceProjectRelative ImportModuleSpecifierPreference = "project-relative" ImportModuleSpecifierPreferenceRelative ImportModuleSpecifierPreference = "relative" @@ -78,7 +78,7 @@ const ( type ImportModuleSpecifierEndingPreference string const ( - ImportModuleSpecifierEndingPreferenceNone ImportModuleSpecifierEndingPreference = "" + ImportModuleSpecifierEndingPreferenceNone ImportModuleSpecifierEndingPreference = "" // !!! ImportModuleSpecifierEndingPreferenceAuto ImportModuleSpecifierEndingPreference = "auto" ImportModuleSpecifierEndingPreferenceMinimal ImportModuleSpecifierEndingPreference = "minimal" ImportModuleSpecifierEndingPreferenceIndex ImportModuleSpecifierEndingPreference = "index" diff --git a/internal/tsoptions/parsinghelpers.go b/internal/tsoptions/parsinghelpers.go index bf16518be2..91944af6a7 100644 --- a/internal/tsoptions/parsinghelpers.go +++ b/internal/tsoptions/parsinghelpers.go @@ -11,7 +11,7 @@ import ( "github.com/microsoft/typescript-go/internal/tspath" ) -func parseTristate(value any) core.Tristate { +func ParseTristate(value any) core.Tristate { if value == nil { return core.TSUnknown } @@ -25,7 +25,7 @@ func parseTristate(value any) core.Tristate { } } -func parseStringArray(value any) []string { +func ParseStringArray(value any) []string { if arr, ok := value.([]any); ok { if arr == nil { return nil @@ -45,14 +45,14 @@ func parseStringMap(value any) *collections.OrderedMap[string, []string] { if m, ok := value.(*collections.OrderedMap[string, any]); ok { result := collections.NewOrderedMapWithSizeHint[string, []string](m.Size()) for k, v := range m.Entries() { - result.Set(k, parseStringArray(v)) + result.Set(k, ParseStringArray(v)) } return result } return nil } -func parseString(value any) string { +func ParseString(value any) string { if str, ok := value.(string); ok { return str } @@ -186,119 +186,119 @@ func parseCompilerOptions(key string, value any, allOptions *core.CompilerOption } switch key { case "allowJs": - allOptions.AllowJs = parseTristate(value) + allOptions.AllowJs = ParseTristate(value) case "allowImportingTsExtensions": - allOptions.AllowImportingTsExtensions = parseTristate(value) + allOptions.AllowImportingTsExtensions = ParseTristate(value) case "allowSyntheticDefaultImports": - allOptions.AllowSyntheticDefaultImports = parseTristate(value) + allOptions.AllowSyntheticDefaultImports = ParseTristate(value) case "allowNonTsExtensions": - allOptions.AllowNonTsExtensions = parseTristate(value) + allOptions.AllowNonTsExtensions = ParseTristate(value) case "allowUmdGlobalAccess": - allOptions.AllowUmdGlobalAccess = parseTristate(value) + allOptions.AllowUmdGlobalAccess = ParseTristate(value) case "allowUnreachableCode": - allOptions.AllowUnreachableCode = parseTristate(value) + allOptions.AllowUnreachableCode = ParseTristate(value) case "allowUnusedLabels": - allOptions.AllowUnusedLabels = parseTristate(value) + allOptions.AllowUnusedLabels = ParseTristate(value) case "allowArbitraryExtensions": - allOptions.AllowArbitraryExtensions = parseTristate(value) + allOptions.AllowArbitraryExtensions = ParseTristate(value) case "alwaysStrict": - allOptions.AlwaysStrict = parseTristate(value) + allOptions.AlwaysStrict = ParseTristate(value) case "assumeChangesOnlyAffectDirectDependencies": - allOptions.AssumeChangesOnlyAffectDirectDependencies = parseTristate(value) + allOptions.AssumeChangesOnlyAffectDirectDependencies = ParseTristate(value) case "baseUrl": - allOptions.BaseUrl = parseString(value) + allOptions.BaseUrl = ParseString(value) case "build": - allOptions.Build = parseTristate(value) + allOptions.Build = ParseTristate(value) case "checkJs": - allOptions.CheckJs = parseTristate(value) + allOptions.CheckJs = ParseTristate(value) case "customConditions": - allOptions.CustomConditions = parseStringArray(value) + allOptions.CustomConditions = ParseStringArray(value) case "composite": - allOptions.Composite = parseTristate(value) + allOptions.Composite = ParseTristate(value) case "declarationDir": - allOptions.DeclarationDir = parseString(value) + allOptions.DeclarationDir = ParseString(value) case "diagnostics": - allOptions.Diagnostics = parseTristate(value) + allOptions.Diagnostics = ParseTristate(value) case "disableSizeLimit": - allOptions.DisableSizeLimit = parseTristate(value) + allOptions.DisableSizeLimit = ParseTristate(value) case "disableSourceOfProjectReferenceRedirect": - allOptions.DisableSourceOfProjectReferenceRedirect = parseTristate(value) + allOptions.DisableSourceOfProjectReferenceRedirect = ParseTristate(value) case "disableSolutionSearching": - allOptions.DisableSolutionSearching = parseTristate(value) + allOptions.DisableSolutionSearching = ParseTristate(value) case "disableReferencedProjectLoad": - allOptions.DisableReferencedProjectLoad = parseTristate(value) + allOptions.DisableReferencedProjectLoad = ParseTristate(value) case "declarationMap": - allOptions.DeclarationMap = parseTristate(value) + allOptions.DeclarationMap = ParseTristate(value) case "declaration": - allOptions.Declaration = parseTristate(value) + allOptions.Declaration = ParseTristate(value) case "downlevelIteration": - allOptions.DownlevelIteration = parseTristate(value) + allOptions.DownlevelIteration = ParseTristate(value) case "erasableSyntaxOnly": - allOptions.ErasableSyntaxOnly = parseTristate(value) + allOptions.ErasableSyntaxOnly = ParseTristate(value) case "emitDeclarationOnly": - allOptions.EmitDeclarationOnly = parseTristate(value) + allOptions.EmitDeclarationOnly = ParseTristate(value) case "extendedDiagnostics": - allOptions.ExtendedDiagnostics = parseTristate(value) + allOptions.ExtendedDiagnostics = ParseTristate(value) case "emitDecoratorMetadata": - allOptions.EmitDecoratorMetadata = parseTristate(value) + allOptions.EmitDecoratorMetadata = ParseTristate(value) case "emitBOM": - allOptions.EmitBOM = parseTristate(value) + allOptions.EmitBOM = ParseTristate(value) case "esModuleInterop": - allOptions.ESModuleInterop = parseTristate(value) + allOptions.ESModuleInterop = ParseTristate(value) case "exactOptionalPropertyTypes": - allOptions.ExactOptionalPropertyTypes = parseTristate(value) + allOptions.ExactOptionalPropertyTypes = ParseTristate(value) case "explainFiles": - allOptions.ExplainFiles = parseTristate(value) + allOptions.ExplainFiles = ParseTristate(value) case "experimentalDecorators": - allOptions.ExperimentalDecorators = parseTristate(value) + allOptions.ExperimentalDecorators = ParseTristate(value) case "forceConsistentCasingInFileNames": - allOptions.ForceConsistentCasingInFileNames = parseTristate(value) + allOptions.ForceConsistentCasingInFileNames = ParseTristate(value) case "generateCpuProfile": - allOptions.GenerateCpuProfile = parseString(value) + allOptions.GenerateCpuProfile = ParseString(value) case "generateTrace": - allOptions.GenerateTrace = parseString(value) + allOptions.GenerateTrace = ParseString(value) case "isolatedModules": - allOptions.IsolatedModules = parseTristate(value) + allOptions.IsolatedModules = ParseTristate(value) case "ignoreDeprecations": - allOptions.IgnoreDeprecations = parseString(value) + allOptions.IgnoreDeprecations = ParseString(value) case "importHelpers": - allOptions.ImportHelpers = parseTristate(value) + allOptions.ImportHelpers = ParseTristate(value) case "incremental": - allOptions.Incremental = parseTristate(value) + allOptions.Incremental = ParseTristate(value) case "init": - allOptions.Init = parseTristate(value) + allOptions.Init = ParseTristate(value) case "inlineSourceMap": - allOptions.InlineSourceMap = parseTristate(value) + allOptions.InlineSourceMap = ParseTristate(value) case "inlineSources": - allOptions.InlineSources = parseTristate(value) + allOptions.InlineSources = ParseTristate(value) case "isolatedDeclarations": - allOptions.IsolatedDeclarations = parseTristate(value) + allOptions.IsolatedDeclarations = ParseTristate(value) case "jsx": allOptions.Jsx = floatOrInt32ToFlag[core.JsxEmit](value) case "jsxFactory": - allOptions.JsxFactory = parseString(value) + allOptions.JsxFactory = ParseString(value) case "jsxFragmentFactory": - allOptions.JsxFragmentFactory = parseString(value) + allOptions.JsxFragmentFactory = ParseString(value) case "jsxImportSource": - allOptions.JsxImportSource = parseString(value) + allOptions.JsxImportSource = ParseString(value) case "lib": if _, ok := value.([]string); ok { allOptions.Lib = value.([]string) } else { - allOptions.Lib = parseStringArray(value) + allOptions.Lib = ParseStringArray(value) } case "libReplacement": - allOptions.LibReplacement = parseTristate(value) + allOptions.LibReplacement = ParseTristate(value) case "listEmittedFiles": - allOptions.ListEmittedFiles = parseTristate(value) + allOptions.ListEmittedFiles = ParseTristate(value) case "listFiles": - allOptions.ListFiles = parseTristate(value) + allOptions.ListFiles = ParseTristate(value) case "listFilesOnly": - allOptions.ListFilesOnly = parseTristate(value) + allOptions.ListFilesOnly = ParseTristate(value) case "locale": - allOptions.Locale = parseString(value) + allOptions.Locale = ParseString(value) case "mapRoot": - allOptions.MapRoot = parseString(value) + allOptions.MapRoot = ParseString(value) case "module": allOptions.Module = floatOrInt32ToFlag[core.ModuleKind](value) case "moduleDetectionKind": @@ -306,143 +306,143 @@ func parseCompilerOptions(key string, value any, allOptions *core.CompilerOption case "moduleResolution": allOptions.ModuleResolution = floatOrInt32ToFlag[core.ModuleResolutionKind](value) case "moduleSuffixes": - allOptions.ModuleSuffixes = parseStringArray(value) + allOptions.ModuleSuffixes = ParseStringArray(value) case "moduleDetection": allOptions.ModuleDetection = floatOrInt32ToFlag[core.ModuleDetectionKind](value) case "noCheck": - allOptions.NoCheck = parseTristate(value) + allOptions.NoCheck = ParseTristate(value) case "noFallthroughCasesInSwitch": - allOptions.NoFallthroughCasesInSwitch = parseTristate(value) + allOptions.NoFallthroughCasesInSwitch = ParseTristate(value) case "noEmitForJsFiles": - allOptions.NoEmitForJsFiles = parseTristate(value) + allOptions.NoEmitForJsFiles = ParseTristate(value) case "noErrorTruncation": - allOptions.NoErrorTruncation = parseTristate(value) + allOptions.NoErrorTruncation = ParseTristate(value) case "noImplicitAny": - allOptions.NoImplicitAny = parseTristate(value) + allOptions.NoImplicitAny = ParseTristate(value) case "noImplicitThis": - allOptions.NoImplicitThis = parseTristate(value) + allOptions.NoImplicitThis = ParseTristate(value) case "noLib": - allOptions.NoLib = parseTristate(value) + allOptions.NoLib = ParseTristate(value) case "noPropertyAccessFromIndexSignature": - allOptions.NoPropertyAccessFromIndexSignature = parseTristate(value) + allOptions.NoPropertyAccessFromIndexSignature = ParseTristate(value) case "noUncheckedIndexedAccess": - allOptions.NoUncheckedIndexedAccess = parseTristate(value) + allOptions.NoUncheckedIndexedAccess = ParseTristate(value) case "noEmitHelpers": - allOptions.NoEmitHelpers = parseTristate(value) + allOptions.NoEmitHelpers = ParseTristate(value) case "noEmitOnError": - allOptions.NoEmitOnError = parseTristate(value) + allOptions.NoEmitOnError = ParseTristate(value) case "noImplicitReturns": - allOptions.NoImplicitReturns = parseTristate(value) + allOptions.NoImplicitReturns = ParseTristate(value) case "noUnusedLocals": - allOptions.NoUnusedLocals = parseTristate(value) + allOptions.NoUnusedLocals = ParseTristate(value) case "noUnusedParameters": - allOptions.NoUnusedParameters = parseTristate(value) + allOptions.NoUnusedParameters = ParseTristate(value) case "noImplicitOverride": - allOptions.NoImplicitOverride = parseTristate(value) + allOptions.NoImplicitOverride = ParseTristate(value) case "noUncheckedSideEffectImports": - allOptions.NoUncheckedSideEffectImports = parseTristate(value) + allOptions.NoUncheckedSideEffectImports = ParseTristate(value) case "outFile": - allOptions.OutFile = parseString(value) + allOptions.OutFile = ParseString(value) case "noResolve": - allOptions.NoResolve = parseTristate(value) + allOptions.NoResolve = ParseTristate(value) case "paths": allOptions.Paths = parseStringMap(value) case "preserveWatchOutput": - allOptions.PreserveWatchOutput = parseTristate(value) + allOptions.PreserveWatchOutput = ParseTristate(value) case "preserveConstEnums": - allOptions.PreserveConstEnums = parseTristate(value) + allOptions.PreserveConstEnums = ParseTristate(value) case "preserveSymlinks": - allOptions.PreserveSymlinks = parseTristate(value) + allOptions.PreserveSymlinks = ParseTristate(value) case "project": - allOptions.Project = parseString(value) + allOptions.Project = ParseString(value) case "pretty": - allOptions.Pretty = parseTristate(value) + allOptions.Pretty = ParseTristate(value) case "resolveJsonModule": - allOptions.ResolveJsonModule = parseTristate(value) + allOptions.ResolveJsonModule = ParseTristate(value) case "resolvePackageJsonExports": - allOptions.ResolvePackageJsonExports = parseTristate(value) + allOptions.ResolvePackageJsonExports = ParseTristate(value) case "resolvePackageJsonImports": - allOptions.ResolvePackageJsonImports = parseTristate(value) + allOptions.ResolvePackageJsonImports = ParseTristate(value) case "reactNamespace": - allOptions.ReactNamespace = parseString(value) + allOptions.ReactNamespace = ParseString(value) case "rewriteRelativeImportExtensions": - allOptions.RewriteRelativeImportExtensions = parseTristate(value) + allOptions.RewriteRelativeImportExtensions = ParseTristate(value) case "rootDir": - allOptions.RootDir = parseString(value) + allOptions.RootDir = ParseString(value) case "rootDirs": - allOptions.RootDirs = parseStringArray(value) + allOptions.RootDirs = ParseStringArray(value) case "removeComments": - allOptions.RemoveComments = parseTristate(value) + allOptions.RemoveComments = ParseTristate(value) case "strict": - allOptions.Strict = parseTristate(value) + allOptions.Strict = ParseTristate(value) case "strictBindCallApply": - allOptions.StrictBindCallApply = parseTristate(value) + allOptions.StrictBindCallApply = ParseTristate(value) case "strictBuiltinIteratorReturn": - allOptions.StrictBuiltinIteratorReturn = parseTristate(value) + allOptions.StrictBuiltinIteratorReturn = ParseTristate(value) case "strictFunctionTypes": - allOptions.StrictFunctionTypes = parseTristate(value) + allOptions.StrictFunctionTypes = ParseTristate(value) case "strictNullChecks": - allOptions.StrictNullChecks = parseTristate(value) + allOptions.StrictNullChecks = ParseTristate(value) case "strictPropertyInitialization": - allOptions.StrictPropertyInitialization = parseTristate(value) + allOptions.StrictPropertyInitialization = ParseTristate(value) case "skipDefaultLibCheck": - allOptions.SkipDefaultLibCheck = parseTristate(value) + allOptions.SkipDefaultLibCheck = ParseTristate(value) case "sourceMap": - allOptions.SourceMap = parseTristate(value) + allOptions.SourceMap = ParseTristate(value) case "sourceRoot": - allOptions.SourceRoot = parseString(value) + allOptions.SourceRoot = ParseString(value) case "stripInternal": - allOptions.StripInternal = parseTristate(value) + allOptions.StripInternal = ParseTristate(value) case "suppressOutputPathCheck": - allOptions.SuppressOutputPathCheck = parseTristate(value) + allOptions.SuppressOutputPathCheck = ParseTristate(value) case "target": allOptions.Target = floatOrInt32ToFlag[core.ScriptTarget](value) case "traceResolution": - allOptions.TraceResolution = parseTristate(value) + allOptions.TraceResolution = ParseTristate(value) case "tsBuildInfoFile": - allOptions.TsBuildInfoFile = parseString(value) + allOptions.TsBuildInfoFile = ParseString(value) case "typeRoots": - allOptions.TypeRoots = parseStringArray(value) + allOptions.TypeRoots = ParseStringArray(value) case "types": - allOptions.Types = parseStringArray(value) + allOptions.Types = ParseStringArray(value) case "useDefineForClassFields": - allOptions.UseDefineForClassFields = parseTristate(value) + allOptions.UseDefineForClassFields = ParseTristate(value) case "useUnknownInCatchVariables": - allOptions.UseUnknownInCatchVariables = parseTristate(value) + allOptions.UseUnknownInCatchVariables = ParseTristate(value) case "verbatimModuleSyntax": - allOptions.VerbatimModuleSyntax = parseTristate(value) + allOptions.VerbatimModuleSyntax = ParseTristate(value) case "version": - allOptions.Version = parseTristate(value) + allOptions.Version = ParseTristate(value) case "help": - allOptions.Help = parseTristate(value) + allOptions.Help = ParseTristate(value) case "all": - allOptions.All = parseTristate(value) + allOptions.All = ParseTristate(value) case "maxNodeModuleJsDepth": allOptions.MaxNodeModuleJsDepth = parseNumber(value) case "skipLibCheck": - allOptions.SkipLibCheck = parseTristate(value) + allOptions.SkipLibCheck = ParseTristate(value) case "noEmit": - allOptions.NoEmit = parseTristate(value) + allOptions.NoEmit = ParseTristate(value) case "showConfig": - allOptions.ShowConfig = parseTristate(value) + allOptions.ShowConfig = ParseTristate(value) case "configFilePath": - allOptions.ConfigFilePath = parseString(value) + allOptions.ConfigFilePath = ParseString(value) case "noDtsResolution": - allOptions.NoDtsResolution = parseTristate(value) + allOptions.NoDtsResolution = ParseTristate(value) case "pathsBasePath": - allOptions.PathsBasePath = parseString(value) + allOptions.PathsBasePath = ParseString(value) case "outDir": - allOptions.OutDir = parseString(value) + allOptions.OutDir = ParseString(value) case "newLine": allOptions.NewLine = floatOrInt32ToFlag[core.NewLineKind](value) case "watch": - allOptions.Watch = parseTristate(value) + allOptions.Watch = ParseTristate(value) case "pprofDir": - allOptions.PprofDir = parseString(value) + allOptions.PprofDir = ParseString(value) case "singleThreaded": - allOptions.SingleThreaded = parseTristate(value) + allOptions.SingleThreaded = ParseTristate(value) case "quiet": - allOptions.Quiet = parseTristate(value) + allOptions.Quiet = ParseTristate(value) default: // different than any key above return false @@ -477,11 +477,11 @@ func ParseWatchOptions(key string, value any, allOptions *core.WatchOptions) []* allOptions.FallbackPolling = value.(core.PollingKind) } case "synchronousWatchDirectory": - allOptions.SyncWatchDir = parseTristate(value) + allOptions.SyncWatchDir = ParseTristate(value) case "excludeDirectories": - allOptions.ExcludeDir = parseStringArray(value) + allOptions.ExcludeDir = ParseStringArray(value) case "excludeFiles": - allOptions.ExcludeFiles = parseStringArray(value) + allOptions.ExcludeFiles = ParseStringArray(value) } return nil } @@ -495,13 +495,13 @@ func ParseTypeAcquisition(key string, value any, allOptions *core.TypeAcquisitio } switch key { case "enable": - allOptions.Enable = parseTristate(value) + allOptions.Enable = ParseTristate(value) case "include": - allOptions.Include = parseStringArray(value) + allOptions.Include = ParseStringArray(value) case "exclude": - allOptions.Exclude = parseStringArray(value) + allOptions.Exclude = ParseStringArray(value) case "disableFilenameBasedTypeAcquisition": - allOptions.DisableFilenameBasedTypeAcquisition = parseTristate(value) + allOptions.DisableFilenameBasedTypeAcquisition = ParseTristate(value) } return nil } @@ -519,15 +519,15 @@ func ParseBuildOptions(key string, value any, allOptions *core.BuildOptions) []* } switch key { case "clean": - allOptions.Clean = parseTristate(value) + allOptions.Clean = ParseTristate(value) case "dry": - allOptions.Dry = parseTristate(value) + allOptions.Dry = ParseTristate(value) case "force": - allOptions.Force = parseTristate(value) + allOptions.Force = ParseTristate(value) case "stopBuildOnErrors": - allOptions.StopBuildOnErrors = parseTristate(value) + allOptions.StopBuildOnErrors = ParseTristate(value) case "verbose": - allOptions.Verbose = parseTristate(value) + allOptions.Verbose = ParseTristate(value) } return nil } From c7474b5c4bc1796688574a05fee402d8b8279d0c Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 7 Oct 2025 10:20:56 -0700 Subject: [PATCH 05/10] userpreferences snapshot --- internal/project/session.go | 14 +++++++++----- internal/project/snapshot.go | 8 +++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/internal/project/session.go b/internal/project/session.go index 3e7f40ded6..dd4d295326 100644 --- a/internal/project/session.go +++ b/internal/project/session.go @@ -347,8 +347,8 @@ func (s *Session) Snapshot() (*Snapshot, func()) { func (s *Session) GetLanguageService(ctx context.Context, uri lsproto.DocumentUri) (*ls.LanguageService, error) { var snapshot *Snapshot - fileChanges, overlays, ataChanges, updateConfig := s.flushChanges(ctx) - updateSnapshot := !fileChanges.IsEmpty() || len(ataChanges) > 0 || updateConfig + fileChanges, overlays, ataChanges, newConfig := s.flushChanges(ctx) + updateSnapshot := !fileChanges.IsEmpty() || len(ataChanges) > 0 || newConfig != nil if updateSnapshot { // If there are pending file changes, we need to update the snapshot. // Sending the requested URI ensures that the project for this URI is loaded. @@ -356,6 +356,7 @@ func (s *Session) GetLanguageService(ctx context.Context, uri lsproto.DocumentUr reason: UpdateReasonRequestedLanguageServicePendingChanges, fileChanges: fileChanges, ataChanges: ataChanges, + newConfig: newConfig, requestedURIs: []lsproto.DocumentUri{uri}, }) } else { @@ -519,7 +520,7 @@ func (s *Session) Close() { s.backgroundQueue.Close() } -func (s *Session) flushChanges(ctx context.Context) (FileChangeSummary, map[tspath.Path]*overlay, map[tspath.Path]*ATAStateChange, bool) { +func (s *Session) flushChanges(ctx context.Context) (FileChangeSummary, map[tspath.Path]*overlay, map[tspath.Path]*ATAStateChange, *ls.UserPreferences) { s.pendingFileChangesMu.Lock() defer s.pendingFileChangesMu.Unlock() s.pendingATAChangesMu.Lock() @@ -528,10 +529,13 @@ func (s *Session) flushChanges(ctx context.Context) (FileChangeSummary, map[tspa s.pendingATAChanges = make(map[tspath.Path]*ATAStateChange) fileChanges, overlays := s.flushChangesLocked(ctx) s.pendingConfigChangesMu.Lock() - updateConfig := s.pendingConfigChanges + var newConfig *ls.UserPreferences + if s.pendingConfigChanges { + newConfig = s.userPreferences.Copy() + } s.pendingConfigChanges = false defer s.pendingConfigChangesMu.Unlock() - return fileChanges, overlays, pendingATAChanges, updateConfig + return fileChanges, overlays, pendingATAChanges, newConfig } // flushChangesLocked should only be called with s.pendingFileChangesMu held. diff --git a/internal/project/snapshot.go b/internal/project/snapshot.go index e0304d593b..fedd946315 100644 --- a/internal/project/snapshot.go +++ b/internal/project/snapshot.go @@ -133,6 +133,7 @@ type SnapshotChange struct { // It should only be set the value in the next snapshot should be changed. If nil, the // value from the previous snapshot will be copied to the new snapshot. compilerOptionsForInferredProjects *core.CompilerOptions + newConfig *ls.UserPreferences // ataChanges contains ATA-related changes to apply to projects in the new snapshot. ataChanges map[tspath.Path]*ATAStateChange apiRequest *APISnapshotRequest @@ -251,6 +252,11 @@ func (s *Snapshot) Clone(ctx context.Context, change SnapshotChange, overlays ma } } } + + userPreferences := s.userPreferences + if change.newConfig != nil { + userPreferences = change.newConfig + } snapshotFS, _ := fs.Finalize() newSnapshot := NewSnapshot( @@ -261,7 +267,7 @@ func (s *Snapshot) Clone(ctx context.Context, change SnapshotChange, overlays ma session.extendedConfigCache, nil, compilerOptionsForInferredProjects, - session.userPreferences, + userPreferences, s.toPath, ) newSnapshot.parentId = s.id From 54168cb7cfe0b568d2b9a85925d37c69d06a2388 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 7 Oct 2025 10:22:39 -0700 Subject: [PATCH 06/10] fmt --- internal/project/snapshot.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/project/snapshot.go b/internal/project/snapshot.go index fedd946315..f88f1ef7fa 100644 --- a/internal/project/snapshot.go +++ b/internal/project/snapshot.go @@ -133,7 +133,7 @@ type SnapshotChange struct { // It should only be set the value in the next snapshot should be changed. If nil, the // value from the previous snapshot will be copied to the new snapshot. compilerOptionsForInferredProjects *core.CompilerOptions - newConfig *ls.UserPreferences + newConfig *ls.UserPreferences // ataChanges contains ATA-related changes to apply to projects in the new snapshot. ataChanges map[tspath.Path]*ATAStateChange apiRequest *APISnapshotRequest @@ -252,7 +252,7 @@ func (s *Snapshot) Clone(ctx context.Context, change SnapshotChange, overlays ma } } } - + userPreferences := s.userPreferences if change.newConfig != nil { userPreferences = change.newConfig From 0dff9de429ad3e2ab59a2ed9d83c8aea19df373a Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 7 Oct 2025 15:02:49 -0700 Subject: [PATCH 07/10] didchangeconfigure --- internal/fourslash/fourslash.go | 2 +- .../tests/autoImportCompletion_test.go | 2 ++ internal/ls/userpreferences.go | 27 +++++++++++++-- internal/lsp/server.go | 33 +++++++++---------- internal/project/session.go | 4 +++ 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index c7387a956b..98fc8474d4 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -282,7 +282,7 @@ func sendRequest[Params, Resp any](t *testing.T, f *FourslashTest, info lsproto. req := lsproto.ResponseMessage{ ID: req.ID, JSONRPC: req.JSONRPC, - Result: []any{&f.userPreferences}, + Result: []any{f.userPreferences}, } f.writeMsg(t, req.Message()) resp = f.readMsg(t) diff --git a/internal/fourslash/tests/autoImportCompletion_test.go b/internal/fourslash/tests/autoImportCompletion_test.go index 2637cd4480..078eeac2c8 100644 --- a/internal/fourslash/tests/autoImportCompletion_test.go +++ b/internal/fourslash/tests/autoImportCompletion_test.go @@ -43,6 +43,8 @@ a/**/ 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{ diff --git a/internal/ls/userpreferences.go b/internal/ls/userpreferences.go index 13d8d857d4..99e65e80e5 100644 --- a/internal/ls/userpreferences.go +++ b/internal/ls/userpreferences.go @@ -349,7 +349,21 @@ func (p *UserPreferences) ModuleSpecifierPreferences() modulespecifiers.UserPref } // ------ Parsing Config Response ------- -func (p *UserPreferences) Parse(config map[string]interface{}) { + +// returns non-nil if should break loop +func (p *UserPreferences) Parse(item any) *UserPreferences { + if item == nil { + // continue + } else if config, ok := item.(map[string]any); ok { + p.parseWorker(config) + } else if item, ok := item.(*UserPreferences); ok { + // case for fourslash + return item.CopyOrDefault() + } + return nil +} + +func (p *UserPreferences) parseWorker(config map[string]interface{}) { // Process unstable preferences first so that they do not overwrite stable properties if unstable, ok := config["unstable"]; ok { // unstable properties must be named the same as userPreferences @@ -526,14 +540,21 @@ func parseBoolWithDefault(val any, defaultV bool) bool { return defaultV } +func parseIntWithDefault(val any, defaultV int) int { + if v, ok := val.(int); ok { + return v + } + return defaultV +} + func (p *UserPreferences) set(name string, value any) { switch strings.ToLower(name) { case "quotePreference": p.QuotePreference = parseQuotePreference(value) case "lazyconfiguredprojectsfromexternalproject": p.LazyConfiguredProjectsFromExternalProject = parseBoolWithDefault(value, false) - // case "maximumhoverlength": - // p.MaximumHoverLength = tsoptions.ParseInt(value, 500) + case "maximumhoverlength": + p.MaximumHoverLength = parseIntWithDefault(value, 500) case "includecompletionsformoduleexports": p.IncludeCompletionsForModuleExports = tsoptions.ParseTristate(value) case "includecompletionsforimportstatements": diff --git a/internal/lsp/server.go b/internal/lsp/server.go index b5fcb7634a..3860e1950e 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -218,7 +218,7 @@ func (s *Server) RefreshDiagnostics(ctx context.Context) error { return nil } -func (s *Server) Configure(ctx context.Context) (*ls.UserPreferences, error) { +func (s *Server) RequestConfiguration(ctx context.Context) (*ls.UserPreferences, error) { result, err := s.sendRequest(ctx, lsproto.MethodWorkspaceConfiguration, &lsproto.ConfigurationParams{ Items: []*lsproto.ConfigurationItem{ { @@ -233,14 +233,8 @@ func (s *Server) Configure(ctx context.Context) (*ls.UserPreferences, error) { s.Log(fmt.Sprintf("\n\nconfiguration: %+v, %T\n\n", configs, configs)) userPreferences := ls.NewDefaultUserPreferences() for _, item := range configs { - if item == nil { - // continue - } else if config, ok := item.(map[string]any); ok { - userPreferences.Parse(config) - } else if item, ok := item.(ls.UserPreferences); ok { - // case for fourslash - userPreferences = &item - break + if parsed := userPreferences.Parse(item); parsed != nil { + return parsed, nil } } return userPreferences, nil @@ -696,11 +690,15 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali NpmExecutor: s, ParseCache: s.parseCache, }) - userPreferences, err := s.Configure(ctx) - if err != nil { - return err + + // request userPreferences if not provided at initialization + if s.session.UserPreferences() == nil { + userPreferences, err := s.RequestConfiguration(ctx) + if err != nil { + return err + } + s.session.Configure(userPreferences) } - s.session.Configure(userPreferences) // !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support if s.compilerOptionsForInferredProjects != nil { s.session.DidChangeCompilerOptionsForInferredProjects(ctx, s.compilerOptionsForInferredProjects) @@ -719,12 +717,11 @@ func (s *Server) handleExit(ctx context.Context, params any) error { } func (s *Server) handleDidChangeWorkspaceConfiguration(ctx context.Context, params *lsproto.DidChangeConfigurationParams) error { - // !!! update user preferences - // !!! only usable by fourslash - if item, ok := params.Settings.(*ls.UserPreferences); ok { - // case for fourslash - s.session.Configure(item) + userPreferences := s.session.UserPreferences().CopyOrDefault() + if parsed := userPreferences.Parse(params.Settings); parsed != nil { + userPreferences = parsed } + s.session.Configure(userPreferences) return nil } diff --git a/internal/project/session.go b/internal/project/session.go index dd4d295326..d02603817d 100644 --- a/internal/project/session.go +++ b/internal/project/session.go @@ -176,6 +176,10 @@ func (s *Session) GetCurrentDirectory() string { return s.options.CurrentDirectory } +func (s *Session) UserPreferences() *ls.UserPreferences { + return s.userPreferences +} + // Trace implements module.ResolutionHost func (s *Session) Trace(msg string) { panic("ATA module resolution should not use tracing") From aa26910fa231f5207ca552c3a4cc3009c778c80e Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 7 Oct 2025 15:15:08 -0700 Subject: [PATCH 08/10] initalization stub --- internal/lsp/server.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 267f968961..0fa6c3e40a 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -16,7 +16,6 @@ import ( "syscall" "time" - "github.com/go-json-experiment/json" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/ls" @@ -712,13 +711,16 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali }) // request userPreferences if not provided at initialization - if s.session.UserPreferences() == nil { + if s.initializeParams.InitializationOptions == nil { userPreferences, err := s.RequestConfiguration(ctx) if err != nil { return err } s.session.Configure(userPreferences) + } else { + // !!! handle userPreferences from initalization } + // !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support if s.compilerOptionsForInferredProjects != nil { s.session.DidChangeCompilerOptionsForInferredProjects(ctx, s.compilerOptionsForInferredProjects) From 3febabdbfc68790de370ddebda5df7376eb4e5b1 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 7 Oct 2025 15:21:24 -0700 Subject: [PATCH 09/10] fmt --- internal/lsp/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 0fa6c3e40a..99dc0f0639 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -16,6 +16,7 @@ import ( "syscall" "time" + "github.com/go-json-experiment/json" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/ls" From 8c33d222638eac455079f4178d41bc17e5ac5653 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 7 Oct 2025 15:52:34 -0700 Subject: [PATCH 10/10] lint --- internal/lsp/server.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 99dc0f0639..23ddf5324d 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - // "go/printer" "io" "os" "os/exec" @@ -719,7 +718,7 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali } s.session.Configure(userPreferences) } else { - // !!! handle userPreferences from initalization + // !!! handle userPreferences from initialization } // !!! temporary; remove when we have `handleDidChangeConfiguration`/implicit project config support