1// Copyright 2019 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package source 6 7import ( 8 "fmt" 9 "os" 10 "regexp" 11 "time" 12 13 "golang.org/x/tools/go/analysis" 14 "golang.org/x/tools/go/analysis/passes/asmdecl" 15 "golang.org/x/tools/go/analysis/passes/assign" 16 "golang.org/x/tools/go/analysis/passes/atomic" 17 "golang.org/x/tools/go/analysis/passes/atomicalign" 18 "golang.org/x/tools/go/analysis/passes/bools" 19 "golang.org/x/tools/go/analysis/passes/buildtag" 20 "golang.org/x/tools/go/analysis/passes/cgocall" 21 "golang.org/x/tools/go/analysis/passes/composite" 22 "golang.org/x/tools/go/analysis/passes/copylock" 23 "golang.org/x/tools/go/analysis/passes/deepequalerrors" 24 "golang.org/x/tools/go/analysis/passes/errorsas" 25 "golang.org/x/tools/go/analysis/passes/httpresponse" 26 "golang.org/x/tools/go/analysis/passes/loopclosure" 27 "golang.org/x/tools/go/analysis/passes/lostcancel" 28 "golang.org/x/tools/go/analysis/passes/nilfunc" 29 "golang.org/x/tools/go/analysis/passes/printf" 30 "golang.org/x/tools/go/analysis/passes/shift" 31 "golang.org/x/tools/go/analysis/passes/sortslice" 32 "golang.org/x/tools/go/analysis/passes/stdmethods" 33 "golang.org/x/tools/go/analysis/passes/structtag" 34 "golang.org/x/tools/go/analysis/passes/testinggoroutine" 35 "golang.org/x/tools/go/analysis/passes/tests" 36 "golang.org/x/tools/go/analysis/passes/unmarshal" 37 "golang.org/x/tools/go/analysis/passes/unreachable" 38 "golang.org/x/tools/go/analysis/passes/unsafeptr" 39 "golang.org/x/tools/go/analysis/passes/unusedresult" 40 "golang.org/x/tools/internal/lsp/diff" 41 "golang.org/x/tools/internal/lsp/diff/myers" 42 "golang.org/x/tools/internal/lsp/protocol" 43 "golang.org/x/tools/internal/telemetry/tag" 44 errors "golang.org/x/xerrors" 45) 46 47func DefaultOptions() Options { 48 return Options{ 49 ClientOptions: ClientOptions{ 50 InsertTextFormat: protocol.PlainTextTextFormat, 51 PreferredContentFormat: protocol.Markdown, 52 ConfigurationSupported: true, 53 DynamicConfigurationSupported: true, 54 DynamicWatchedFilesSupported: true, 55 LineFoldingOnly: false, 56 }, 57 ServerOptions: ServerOptions{ 58 SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{ 59 Go: { 60 protocol.SourceOrganizeImports: true, 61 protocol.QuickFix: true, 62 }, 63 Mod: { 64 protocol.SourceOrganizeImports: true, 65 }, 66 Sum: {}, 67 }, 68 SupportedCommands: []string{ 69 "tidy", // for go.mod files 70 "upgrade.dependency", // for go.mod dependency upgrades 71 }, 72 }, 73 UserOptions: UserOptions{ 74 Env: os.Environ(), 75 HoverKind: FullDocumentation, 76 LinkTarget: "pkg.go.dev", 77 Matcher: Fuzzy, 78 DeepCompletion: true, 79 UnimportedCompletion: true, 80 CompletionDocumentation: true, 81 }, 82 DebuggingOptions: DebuggingOptions{ 83 CompletionBudget: 100 * time.Millisecond, 84 }, 85 ExperimentalOptions: ExperimentalOptions{ 86 TempModfile: true, 87 }, 88 Hooks: Hooks{ 89 ComputeEdits: myers.ComputeEdits, 90 URLRegexp: regexp.MustCompile(`(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?`), 91 Analyzers: defaultAnalyzers(), 92 GoDiff: true, 93 }, 94 } 95} 96 97type Options struct { 98 ClientOptions 99 ServerOptions 100 UserOptions 101 DebuggingOptions 102 ExperimentalOptions 103 Hooks 104} 105 106type ClientOptions struct { 107 InsertTextFormat protocol.InsertTextFormat 108 ConfigurationSupported bool 109 DynamicConfigurationSupported bool 110 DynamicWatchedFilesSupported bool 111 PreferredContentFormat protocol.MarkupKind 112 LineFoldingOnly bool 113} 114 115type ServerOptions struct { 116 SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool 117 SupportedCommands []string 118} 119 120type UserOptions struct { 121 // Env is the current set of environment overrides on this view. 122 Env []string 123 124 // BuildFlags is used to adjust the build flags applied to the view. 125 BuildFlags []string 126 127 // HoverKind specifies the format of the content for hover requests. 128 HoverKind HoverKind 129 130 // DisabledAnalyses specify analyses that the user would like to disable. 131 DisabledAnalyses map[string]struct{} 132 133 // StaticCheck enables additional analyses from staticcheck.io. 134 StaticCheck bool 135 136 // LinkTarget is the website used for documentation. 137 LinkTarget string 138 139 // LocalPrefix is used to specify goimports's -local behavior. 140 LocalPrefix string 141 142 // Matcher specifies the type of matcher to use for completion requests. 143 Matcher Matcher 144 145 // DeepCompletion allows completion to perform nested searches through 146 // possible candidates. 147 DeepCompletion bool 148 149 // UnimportedCompletion enables completion for unimported packages. 150 UnimportedCompletion bool 151 152 // CompletionDocumentation returns additional documentation with completion 153 // requests. 154 CompletionDocumentation bool 155 156 // Placeholders adds placeholders to parameters and structs in completion 157 // results. 158 Placeholders bool 159} 160 161type completionOptions struct { 162 deepCompletion bool 163 unimported bool 164 documentation bool 165 fullDocumentation bool 166 placeholders bool 167 literal bool 168 matcher Matcher 169 budget time.Duration 170} 171 172type Hooks struct { 173 GoDiff bool 174 ComputeEdits diff.ComputeEdits 175 URLRegexp *regexp.Regexp 176 Analyzers map[string]*analysis.Analyzer 177} 178 179type ExperimentalOptions struct { 180 // WARNING: This configuration will be changed in the future. 181 // It only exists while this feature is under development. 182 // Disable use of the -modfile flag in Go 1.14. 183 TempModfile bool 184} 185 186type DebuggingOptions struct { 187 VerboseOutput bool 188 189 // CompletionBudget is the soft latency goal for completion requests. Most 190 // requests finish in a couple milliseconds, but in some cases deep 191 // completions can take much longer. As we use up our budget we 192 // dynamically reduce the search scope to ensure we return timely 193 // results. Zero means unlimited. 194 CompletionBudget time.Duration 195} 196 197type Matcher int 198 199const ( 200 Fuzzy = Matcher(iota) 201 CaseInsensitive 202 CaseSensitive 203) 204 205type HoverKind int 206 207const ( 208 SingleLine = HoverKind(iota) 209 NoDocumentation 210 SynopsisDocumentation 211 FullDocumentation 212 213 // Structured is an experimental setting that returns a structured hover format. 214 // This format separates the signature from the documentation, so that the client 215 // can do more manipulation of these fields. 216 // 217 // This should only be used by clients that support this behavior. 218 Structured 219) 220 221type OptionResults []OptionResult 222 223type OptionResult struct { 224 Name string 225 Value interface{} 226 Error error 227 228 State OptionState 229 Replacement string 230} 231 232type OptionState int 233 234const ( 235 OptionHandled = OptionState(iota) 236 OptionDeprecated 237 OptionUnexpected 238) 239 240type LinkTarget string 241 242func SetOptions(options *Options, opts interface{}) OptionResults { 243 var results OptionResults 244 switch opts := opts.(type) { 245 case nil: 246 case map[string]interface{}: 247 for name, value := range opts { 248 results = append(results, options.set(name, value)) 249 } 250 default: 251 results = append(results, OptionResult{ 252 Value: opts, 253 Error: errors.Errorf("Invalid options type %T", opts), 254 }) 255 } 256 return results 257} 258 259func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) { 260 // Check if the client supports snippets in completion items. 261 if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport { 262 o.InsertTextFormat = protocol.SnippetTextFormat 263 } 264 // Check if the client supports configuration messages. 265 o.ConfigurationSupported = caps.Workspace.Configuration 266 o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration 267 o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration 268 269 // Check which types of content format are supported by this client. 270 if hover := caps.TextDocument.Hover; len(hover.ContentFormat) > 0 { 271 o.PreferredContentFormat = hover.ContentFormat[0] 272 } 273 // Check if the client supports only line folding. 274 fr := caps.TextDocument.FoldingRange 275 o.LineFoldingOnly = fr.LineFoldingOnly 276} 277 278func (o *Options) set(name string, value interface{}) OptionResult { 279 result := OptionResult{Name: name, Value: value} 280 switch name { 281 case "env": 282 menv, ok := value.(map[string]interface{}) 283 if !ok { 284 result.errorf("invalid config gopls.env type %T", value) 285 break 286 } 287 for k, v := range menv { 288 o.Env = append(o.Env, fmt.Sprintf("%s=%s", k, v)) 289 } 290 291 case "buildFlags": 292 iflags, ok := value.([]interface{}) 293 if !ok { 294 result.errorf("invalid config gopls.buildFlags type %T", value) 295 break 296 } 297 flags := make([]string, 0, len(iflags)) 298 for _, flag := range iflags { 299 flags = append(flags, fmt.Sprintf("%s", flag)) 300 } 301 o.BuildFlags = flags 302 303 case "completionDocumentation": 304 result.setBool(&o.CompletionDocumentation) 305 case "usePlaceholders": 306 result.setBool(&o.Placeholders) 307 case "deepCompletion": 308 result.setBool(&o.DeepCompletion) 309 case "completeUnimported": 310 result.setBool(&o.UnimportedCompletion) 311 case "completionBudget": 312 if v, ok := result.asString(); ok { 313 d, err := time.ParseDuration(v) 314 if err != nil { 315 result.errorf("failed to parse duration %q: %v", v, err) 316 break 317 } 318 o.CompletionBudget = d 319 } 320 321 case "matcher": 322 matcher, ok := result.asString() 323 if !ok { 324 break 325 } 326 switch matcher { 327 case "fuzzy": 328 o.Matcher = Fuzzy 329 case "caseSensitive": 330 o.Matcher = CaseSensitive 331 default: 332 o.Matcher = CaseInsensitive 333 } 334 335 case "hoverKind": 336 hoverKind, ok := result.asString() 337 if !ok { 338 break 339 } 340 switch hoverKind { 341 case "NoDocumentation": 342 o.HoverKind = NoDocumentation 343 case "SingleLine": 344 o.HoverKind = SingleLine 345 case "SynopsisDocumentation": 346 o.HoverKind = SynopsisDocumentation 347 case "FullDocumentation": 348 o.HoverKind = FullDocumentation 349 case "Structured": 350 o.HoverKind = Structured 351 default: 352 result.errorf("Unsupported hover kind", tag.Of("HoverKind", hoverKind)) 353 } 354 355 case "linkTarget": 356 linkTarget, ok := value.(string) 357 if !ok { 358 result.errorf("invalid type %T for string option %q", value, name) 359 break 360 } 361 o.LinkTarget = linkTarget 362 363 case "experimentalDisabledAnalyses": 364 disabledAnalyses, ok := value.([]interface{}) 365 if !ok { 366 result.errorf("Invalid type %T for []string option %q", value, name) 367 break 368 } 369 o.DisabledAnalyses = make(map[string]struct{}) 370 for _, a := range disabledAnalyses { 371 o.DisabledAnalyses[fmt.Sprint(a)] = struct{}{} 372 } 373 374 case "staticcheck": 375 result.setBool(&o.StaticCheck) 376 377 case "local": 378 localPrefix, ok := value.(string) 379 if !ok { 380 result.errorf("invalid type %T for string option %q", value, name) 381 break 382 } 383 o.LocalPrefix = localPrefix 384 385 case "verboseOutput": 386 result.setBool(&o.VerboseOutput) 387 388 case "tempModfile": 389 result.setBool(&o.TempModfile) 390 391 // Deprecated settings. 392 case "wantSuggestedFixes": 393 result.State = OptionDeprecated 394 395 case "disableDeepCompletion": 396 result.State = OptionDeprecated 397 result.Replacement = "deepCompletion" 398 399 case "disableFuzzyMatching": 400 result.State = OptionDeprecated 401 result.Replacement = "fuzzyMatching" 402 403 case "wantCompletionDocumentation": 404 result.State = OptionDeprecated 405 result.Replacement = "completionDocumentation" 406 407 case "wantUnimportedCompletions": 408 result.State = OptionDeprecated 409 result.Replacement = "completeUnimported" 410 411 case "fuzzyMatching": 412 result.State = OptionDeprecated 413 result.Replacement = "matcher" 414 415 case "caseSensitiveCompletion": 416 result.State = OptionDeprecated 417 result.Replacement = "matcher" 418 419 case "noIncrementalSync": 420 result.State = OptionDeprecated 421 422 case "watchFileChanges": 423 result.State = OptionDeprecated 424 425 case "go-diff": 426 result.State = OptionDeprecated 427 428 default: 429 result.State = OptionUnexpected 430 } 431 return result 432} 433 434func (r *OptionResult) errorf(msg string, values ...interface{}) { 435 r.Error = errors.Errorf(msg, values...) 436} 437 438func (r *OptionResult) asBool() (bool, bool) { 439 b, ok := r.Value.(bool) 440 if !ok { 441 r.errorf("Invalid type %T for bool option %q", r.Value, r.Name) 442 return false, false 443 } 444 return b, true 445} 446 447func (r *OptionResult) asString() (string, bool) { 448 b, ok := r.Value.(string) 449 if !ok { 450 r.errorf("Invalid type %T for string option %q", r.Value, r.Name) 451 return "", false 452 } 453 return b, true 454} 455 456func (r *OptionResult) setBool(b *bool) { 457 if v, ok := r.asBool(); ok { 458 *b = v 459 } 460} 461 462func defaultAnalyzers() map[string]*analysis.Analyzer { 463 return map[string]*analysis.Analyzer{ 464 // The traditional vet suite: 465 asmdecl.Analyzer.Name: asmdecl.Analyzer, 466 assign.Analyzer.Name: assign.Analyzer, 467 atomic.Analyzer.Name: atomic.Analyzer, 468 atomicalign.Analyzer.Name: atomicalign.Analyzer, 469 bools.Analyzer.Name: bools.Analyzer, 470 buildtag.Analyzer.Name: buildtag.Analyzer, 471 cgocall.Analyzer.Name: cgocall.Analyzer, 472 composite.Analyzer.Name: composite.Analyzer, 473 copylock.Analyzer.Name: copylock.Analyzer, 474 errorsas.Analyzer.Name: errorsas.Analyzer, 475 httpresponse.Analyzer.Name: httpresponse.Analyzer, 476 loopclosure.Analyzer.Name: loopclosure.Analyzer, 477 lostcancel.Analyzer.Name: lostcancel.Analyzer, 478 nilfunc.Analyzer.Name: nilfunc.Analyzer, 479 printf.Analyzer.Name: printf.Analyzer, 480 shift.Analyzer.Name: shift.Analyzer, 481 stdmethods.Analyzer.Name: stdmethods.Analyzer, 482 structtag.Analyzer.Name: structtag.Analyzer, 483 tests.Analyzer.Name: tests.Analyzer, 484 unmarshal.Analyzer.Name: unmarshal.Analyzer, 485 unreachable.Analyzer.Name: unreachable.Analyzer, 486 unsafeptr.Analyzer.Name: unsafeptr.Analyzer, 487 unusedresult.Analyzer.Name: unusedresult.Analyzer, 488 489 // Non-vet analyzers 490 deepequalerrors.Analyzer.Name: deepequalerrors.Analyzer, 491 sortslice.Analyzer.Name: sortslice.Analyzer, 492 testinggoroutine.Analyzer.Name: testinggoroutine.Analyzer, 493 } 494} 495