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