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