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 "time" 11 12 "golang.org/x/tools/go/analysis" 13 "golang.org/x/tools/go/analysis/passes/asmdecl" 14 "golang.org/x/tools/go/analysis/passes/assign" 15 "golang.org/x/tools/go/analysis/passes/atomic" 16 "golang.org/x/tools/go/analysis/passes/atomicalign" 17 "golang.org/x/tools/go/analysis/passes/bools" 18 "golang.org/x/tools/go/analysis/passes/buildtag" 19 "golang.org/x/tools/go/analysis/passes/cgocall" 20 "golang.org/x/tools/go/analysis/passes/composite" 21 "golang.org/x/tools/go/analysis/passes/copylock" 22 "golang.org/x/tools/go/analysis/passes/httpresponse" 23 "golang.org/x/tools/go/analysis/passes/loopclosure" 24 "golang.org/x/tools/go/analysis/passes/lostcancel" 25 "golang.org/x/tools/go/analysis/passes/nilfunc" 26 "golang.org/x/tools/go/analysis/passes/printf" 27 "golang.org/x/tools/go/analysis/passes/shift" 28 "golang.org/x/tools/go/analysis/passes/sortslice" 29 "golang.org/x/tools/go/analysis/passes/stdmethods" 30 "golang.org/x/tools/go/analysis/passes/structtag" 31 "golang.org/x/tools/go/analysis/passes/tests" 32 "golang.org/x/tools/go/analysis/passes/unmarshal" 33 "golang.org/x/tools/go/analysis/passes/unreachable" 34 "golang.org/x/tools/go/analysis/passes/unsafeptr" 35 "golang.org/x/tools/go/analysis/passes/unusedresult" 36 "golang.org/x/tools/internal/lsp/diff" 37 "golang.org/x/tools/internal/lsp/diff/myers" 38 "golang.org/x/tools/internal/lsp/protocol" 39 "golang.org/x/tools/internal/telemetry/tag" 40 errors "golang.org/x/xerrors" 41) 42 43var ( 44 DefaultOptions = Options{ 45 Env: os.Environ(), 46 TextDocumentSyncKind: protocol.Incremental, 47 HoverKind: SynopsisDocumentation, 48 InsertTextFormat: protocol.PlainTextTextFormat, 49 PreferredContentFormat: protocol.Markdown, 50 SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{ 51 Go: { 52 protocol.SourceOrganizeImports: true, 53 protocol.QuickFix: true, 54 }, 55 Mod: { 56 protocol.SourceOrganizeImports: true, 57 }, 58 Sum: {}, 59 }, 60 SupportedCommands: []string{ 61 "tidy", // for go.mod files 62 }, 63 Completion: CompletionOptions{ 64 Documentation: true, 65 Deep: true, 66 FuzzyMatching: true, 67 Literal: true, 68 Budget: 100 * time.Millisecond, 69 }, 70 ComputeEdits: myers.ComputeEdits, 71 Analyzers: defaultAnalyzers, 72 GoDiff: true, 73 LinkTarget: "pkg.go.dev", 74 } 75) 76 77type Options struct { 78 // Env is the current set of environment overrides on this view. 79 Env []string 80 81 // BuildFlags is used to adjust the build flags applied to the view. 82 BuildFlags []string 83 84 HoverKind HoverKind 85 DisabledAnalyses map[string]struct{} 86 87 StaticCheck bool 88 GoDiff bool 89 90 WatchFileChanges bool 91 InsertTextFormat protocol.InsertTextFormat 92 ConfigurationSupported bool 93 DynamicConfigurationSupported bool 94 DynamicWatchedFilesSupported bool 95 PreferredContentFormat protocol.MarkupKind 96 LineFoldingOnly bool 97 98 SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool 99 100 SupportedCommands []string 101 102 // TODO: Remove the option once we are certain there are no issues here. 103 TextDocumentSyncKind protocol.TextDocumentSyncKind 104 105 Completion CompletionOptions 106 107 ComputeEdits diff.ComputeEdits 108 109 Analyzers map[string]*analysis.Analyzer 110 111 // LocalPrefix is used to specify goimports's -local behavior. 112 LocalPrefix string 113 114 VerboseOutput bool 115 116 LinkTarget string 117} 118 119type CompletionOptions struct { 120 Deep bool 121 FuzzyMatching bool 122 CaseSensitive bool 123 Unimported bool 124 Documentation bool 125 FullDocumentation bool 126 Placeholders bool 127 Literal bool 128 129 // Budget is the soft latency goal for completion requests. Most 130 // requests finish in a couple milliseconds, but in some cases deep 131 // completions can take much longer. As we use up our budget we 132 // dynamically reduce the search scope to ensure we return timely 133 // results. Zero means unlimited. 134 Budget time.Duration 135} 136 137type HoverKind int 138 139const ( 140 SingleLine = HoverKind(iota) 141 NoDocumentation 142 SynopsisDocumentation 143 FullDocumentation 144 145 // structured is an experimental setting that returns a structured hover format. 146 // This format separates the signature from the documentation, so that the client 147 // can do more manipulation of these fields. 148 // 149 // This should only be used by clients that support this behavior. 150 Structured 151) 152 153type OptionResults []OptionResult 154 155type OptionResult struct { 156 Name string 157 Value interface{} 158 Error error 159 160 State OptionState 161 Replacement string 162} 163 164type OptionState int 165 166const ( 167 OptionHandled = OptionState(iota) 168 OptionDeprecated 169 OptionUnexpected 170) 171 172type LinkTarget string 173 174func SetOptions(options *Options, opts interface{}) OptionResults { 175 var results OptionResults 176 switch opts := opts.(type) { 177 case nil: 178 case map[string]interface{}: 179 for name, value := range opts { 180 results = append(results, options.set(name, value)) 181 } 182 default: 183 results = append(results, OptionResult{ 184 Value: opts, 185 Error: errors.Errorf("Invalid options type %T", opts), 186 }) 187 } 188 return results 189} 190 191func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) { 192 // Check if the client supports snippets in completion items. 193 if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport { 194 o.InsertTextFormat = protocol.SnippetTextFormat 195 } 196 // Check if the client supports configuration messages. 197 o.ConfigurationSupported = caps.Workspace.Configuration 198 o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration 199 o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration 200 201 // Check which types of content format are supported by this client. 202 if hover := caps.TextDocument.Hover; len(hover.ContentFormat) > 0 { 203 o.PreferredContentFormat = hover.ContentFormat[0] 204 } 205 // Check if the client supports only line folding. 206 fr := caps.TextDocument.FoldingRange 207 o.LineFoldingOnly = fr.LineFoldingOnly 208} 209 210func (o *Options) set(name string, value interface{}) OptionResult { 211 result := OptionResult{Name: name, Value: value} 212 switch name { 213 case "env": 214 menv, ok := value.(map[string]interface{}) 215 if !ok { 216 result.errorf("invalid config gopls.env type %T", value) 217 break 218 } 219 for k, v := range menv { 220 o.Env = append(o.Env, fmt.Sprintf("%s=%s", k, v)) 221 } 222 223 case "buildFlags": 224 iflags, ok := value.([]interface{}) 225 if !ok { 226 result.errorf("invalid config gopls.buildFlags type %T", value) 227 break 228 } 229 flags := make([]string, 0, len(iflags)) 230 for _, flag := range iflags { 231 flags = append(flags, fmt.Sprintf("%s", flag)) 232 } 233 o.BuildFlags = flags 234 235 case "noIncrementalSync": 236 if v, ok := result.asBool(); ok && v { 237 o.TextDocumentSyncKind = protocol.Full 238 } 239 case "watchFileChanges": 240 result.setBool(&o.WatchFileChanges) 241 case "completionDocumentation": 242 result.setBool(&o.Completion.Documentation) 243 case "usePlaceholders": 244 result.setBool(&o.Completion.Placeholders) 245 case "deepCompletion": 246 result.setBool(&o.Completion.Deep) 247 case "fuzzyMatching": 248 result.setBool(&o.Completion.FuzzyMatching) 249 case "caseSensitiveCompletion": 250 result.setBool(&o.Completion.CaseSensitive) 251 case "completeUnimported": 252 result.setBool(&o.Completion.Unimported) 253 254 case "hoverKind": 255 hoverKind, ok := value.(string) 256 if !ok { 257 result.errorf("invalid type %T for string option %q", value, name) 258 break 259 } 260 switch hoverKind { 261 case "NoDocumentation": 262 o.HoverKind = NoDocumentation 263 case "SingleLine": 264 o.HoverKind = SingleLine 265 case "SynopsisDocumentation": 266 o.HoverKind = SynopsisDocumentation 267 case "FullDocumentation": 268 o.HoverKind = FullDocumentation 269 case "Structured": 270 o.HoverKind = Structured 271 default: 272 result.errorf("Unsupported hover kind", tag.Of("HoverKind", hoverKind)) 273 } 274 275 case "linkTarget": 276 linkTarget, ok := value.(string) 277 if !ok { 278 result.errorf("invalid type %T for string option %q", value, name) 279 break 280 } 281 o.LinkTarget = linkTarget 282 283 case "experimentalDisabledAnalyses": 284 disabledAnalyses, ok := value.([]interface{}) 285 if !ok { 286 result.errorf("Invalid type %T for []string option %q", value, name) 287 break 288 } 289 o.DisabledAnalyses = make(map[string]struct{}) 290 for _, a := range disabledAnalyses { 291 o.DisabledAnalyses[fmt.Sprint(a)] = struct{}{} 292 } 293 294 case "staticcheck": 295 result.setBool(&o.StaticCheck) 296 297 case "go-diff": 298 result.setBool(&o.GoDiff) 299 300 case "local": 301 localPrefix, ok := value.(string) 302 if !ok { 303 result.errorf("invalid type %T for string option %q", value, name) 304 break 305 } 306 o.LocalPrefix = localPrefix 307 308 case "verboseOutput": 309 result.setBool(&o.VerboseOutput) 310 311 // Deprecated settings. 312 case "wantSuggestedFixes": 313 result.State = OptionDeprecated 314 315 case "disableDeepCompletion": 316 result.State = OptionDeprecated 317 result.Replacement = "deepCompletion" 318 319 case "disableFuzzyMatching": 320 result.State = OptionDeprecated 321 result.Replacement = "fuzzyMatching" 322 323 case "wantCompletionDocumentation": 324 result.State = OptionDeprecated 325 result.Replacement = "completionDocumentation" 326 327 case "wantUnimportedCompletions": 328 result.State = OptionDeprecated 329 result.Replacement = "completeUnimported" 330 331 default: 332 result.State = OptionUnexpected 333 } 334 return result 335} 336 337func (r *OptionResult) errorf(msg string, values ...interface{}) { 338 r.Error = errors.Errorf(msg, values...) 339} 340 341func (r *OptionResult) asBool() (bool, bool) { 342 b, ok := r.Value.(bool) 343 if !ok { 344 r.errorf("Invalid type %T for bool option %q", r.Value, r.Name) 345 return false, false 346 } 347 return b, true 348} 349 350func (r *OptionResult) setBool(b *bool) { 351 if v, ok := r.asBool(); ok { 352 *b = v 353 } 354} 355 356var defaultAnalyzers = map[string]*analysis.Analyzer{ 357 // The traditional vet suite: 358 asmdecl.Analyzer.Name: asmdecl.Analyzer, 359 assign.Analyzer.Name: assign.Analyzer, 360 atomic.Analyzer.Name: atomic.Analyzer, 361 atomicalign.Analyzer.Name: atomicalign.Analyzer, 362 bools.Analyzer.Name: bools.Analyzer, 363 buildtag.Analyzer.Name: buildtag.Analyzer, 364 cgocall.Analyzer.Name: cgocall.Analyzer, 365 composite.Analyzer.Name: composite.Analyzer, 366 copylock.Analyzer.Name: copylock.Analyzer, 367 httpresponse.Analyzer.Name: httpresponse.Analyzer, 368 loopclosure.Analyzer.Name: loopclosure.Analyzer, 369 lostcancel.Analyzer.Name: lostcancel.Analyzer, 370 nilfunc.Analyzer.Name: nilfunc.Analyzer, 371 printf.Analyzer.Name: printf.Analyzer, 372 shift.Analyzer.Name: shift.Analyzer, 373 stdmethods.Analyzer.Name: stdmethods.Analyzer, 374 structtag.Analyzer.Name: structtag.Analyzer, 375 tests.Analyzer.Name: tests.Analyzer, 376 unmarshal.Analyzer.Name: unmarshal.Analyzer, 377 unreachable.Analyzer.Name: unreachable.Analyzer, 378 unsafeptr.Analyzer.Name: unsafeptr.Analyzer, 379 unusedresult.Analyzer.Name: unusedresult.Analyzer, 380 381 // Non-vet analyzers 382 sortslice.Analyzer.Name: sortslice.Analyzer, 383} 384