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