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	"context"
9	"fmt"
10	"path/filepath"
11	"regexp"
12	"strings"
13	"sync"
14	"time"
15
16	"golang.org/x/tools/go/analysis"
17	"golang.org/x/tools/go/analysis/passes/asmdecl"
18	"golang.org/x/tools/go/analysis/passes/assign"
19	"golang.org/x/tools/go/analysis/passes/atomic"
20	"golang.org/x/tools/go/analysis/passes/atomicalign"
21	"golang.org/x/tools/go/analysis/passes/bools"
22	"golang.org/x/tools/go/analysis/passes/buildtag"
23	"golang.org/x/tools/go/analysis/passes/cgocall"
24	"golang.org/x/tools/go/analysis/passes/composite"
25	"golang.org/x/tools/go/analysis/passes/copylock"
26	"golang.org/x/tools/go/analysis/passes/deepequalerrors"
27	"golang.org/x/tools/go/analysis/passes/errorsas"
28	"golang.org/x/tools/go/analysis/passes/fieldalignment"
29	"golang.org/x/tools/go/analysis/passes/httpresponse"
30	"golang.org/x/tools/go/analysis/passes/ifaceassert"
31	"golang.org/x/tools/go/analysis/passes/loopclosure"
32	"golang.org/x/tools/go/analysis/passes/lostcancel"
33	"golang.org/x/tools/go/analysis/passes/nilfunc"
34	"golang.org/x/tools/go/analysis/passes/printf"
35	"golang.org/x/tools/go/analysis/passes/shadow"
36	"golang.org/x/tools/go/analysis/passes/shift"
37	"golang.org/x/tools/go/analysis/passes/sortslice"
38	"golang.org/x/tools/go/analysis/passes/stdmethods"
39	"golang.org/x/tools/go/analysis/passes/stringintconv"
40	"golang.org/x/tools/go/analysis/passes/structtag"
41	"golang.org/x/tools/go/analysis/passes/testinggoroutine"
42	"golang.org/x/tools/go/analysis/passes/tests"
43	"golang.org/x/tools/go/analysis/passes/unmarshal"
44	"golang.org/x/tools/go/analysis/passes/unreachable"
45	"golang.org/x/tools/go/analysis/passes/unsafeptr"
46	"golang.org/x/tools/go/analysis/passes/unusedresult"
47	"golang.org/x/tools/internal/lsp/analysis/fillreturns"
48	"golang.org/x/tools/internal/lsp/analysis/fillstruct"
49	"golang.org/x/tools/internal/lsp/analysis/nonewvars"
50	"golang.org/x/tools/internal/lsp/analysis/noresultvalues"
51	"golang.org/x/tools/internal/lsp/analysis/simplifycompositelit"
52	"golang.org/x/tools/internal/lsp/analysis/simplifyrange"
53	"golang.org/x/tools/internal/lsp/analysis/simplifyslice"
54	"golang.org/x/tools/internal/lsp/analysis/undeclaredname"
55	"golang.org/x/tools/internal/lsp/analysis/unusedparams"
56	"golang.org/x/tools/internal/lsp/diff"
57	"golang.org/x/tools/internal/lsp/diff/myers"
58	"golang.org/x/tools/internal/lsp/protocol"
59	errors "golang.org/x/xerrors"
60)
61
62var (
63	optionsOnce    sync.Once
64	defaultOptions *Options
65)
66
67// DefaultOptions is the options that are used for Gopls execution independent
68// of any externally provided configuration (LSP initialization, command
69// invokation, etc.).
70func DefaultOptions() *Options {
71	optionsOnce.Do(func() {
72		var commands []string
73		for _, c := range Commands {
74			commands = append(commands, c.ID())
75		}
76		defaultOptions = &Options{
77			ClientOptions: ClientOptions{
78				InsertTextFormat:                  protocol.PlainTextTextFormat,
79				PreferredContentFormat:            protocol.Markdown,
80				ConfigurationSupported:            true,
81				DynamicConfigurationSupported:     true,
82				DynamicWatchedFilesSupported:      true,
83				LineFoldingOnly:                   false,
84				HierarchicalDocumentSymbolSupport: true,
85			},
86			ServerOptions: ServerOptions{
87				SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
88					Go: {
89						protocol.SourceFixAll:          true,
90						protocol.SourceOrganizeImports: true,
91						protocol.QuickFix:              true,
92						protocol.RefactorRewrite:       true,
93						protocol.RefactorExtract:       true,
94					},
95					Mod: {
96						protocol.SourceOrganizeImports: true,
97					},
98					Sum: {},
99				},
100				SupportedCommands: commands,
101			},
102			UserOptions: UserOptions{
103				BuildOptions: BuildOptions{
104					ExpandWorkspaceToModule:     true,
105					ExperimentalPackageCacheKey: true,
106				},
107				UIOptions: UIOptions{
108					DiagnosticOptions: DiagnosticOptions{
109						ExperimentalDiagnosticsDelay: 250 * time.Millisecond,
110						Annotations: map[Annotation]bool{
111							Bounds: true,
112							Escape: true,
113							Inline: true,
114							Nil:    true,
115						},
116					},
117					DocumentationOptions: DocumentationOptions{
118						HoverKind:    FullDocumentation,
119						LinkTarget:   "pkg.go.dev",
120						LinksInHover: true,
121					},
122					NavigationOptions: NavigationOptions{
123						ImportShortcut: Both,
124						SymbolMatcher:  SymbolFuzzy,
125						SymbolStyle:    DynamicSymbols,
126					},
127					CompletionOptions: CompletionOptions{
128						Matcher:          Fuzzy,
129						CompletionBudget: 100 * time.Millisecond,
130					},
131					Codelenses: map[string]bool{
132						CommandGenerate.Name:          true,
133						CommandRegenerateCgo.Name:     true,
134						CommandTidy.Name:              true,
135						CommandToggleDetails.Name:     false,
136						CommandUpgradeDependency.Name: true,
137						CommandVendor.Name:            true,
138					},
139				},
140			},
141			InternalOptions: InternalOptions{
142				LiteralCompletions:      true,
143				TempModfile:             true,
144				CompleteUnimported:      true,
145				CompletionDocumentation: true,
146				DeepCompletion:          true,
147			},
148			Hooks: Hooks{
149				ComputeEdits:         myers.ComputeEdits,
150				URLRegexp:            urlRegexp(),
151				DefaultAnalyzers:     defaultAnalyzers(),
152				TypeErrorAnalyzers:   typeErrorAnalyzers(),
153				ConvenienceAnalyzers: convenienceAnalyzers(),
154				StaticcheckAnalyzers: map[string]Analyzer{},
155				GoDiff:               true,
156			},
157		}
158	})
159	return defaultOptions
160}
161
162// Options holds various configuration that affects Gopls execution, organized
163// by the nature or origin of the settings.
164type Options struct {
165	ClientOptions
166	ServerOptions
167	UserOptions
168	InternalOptions
169	Hooks
170}
171
172// ClientOptions holds LSP-specific configuration that is provided by the
173// client.
174type ClientOptions struct {
175	InsertTextFormat                  protocol.InsertTextFormat
176	ConfigurationSupported            bool
177	DynamicConfigurationSupported     bool
178	DynamicWatchedFilesSupported      bool
179	PreferredContentFormat            protocol.MarkupKind
180	LineFoldingOnly                   bool
181	HierarchicalDocumentSymbolSupport bool
182	SemanticTypes                     []string
183	SemanticMods                      []string
184}
185
186// ServerOptions holds LSP-specific configuration that is provided by the
187// server.
188type ServerOptions struct {
189	SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool
190	SupportedCommands    []string
191}
192
193type BuildOptions struct {
194	// BuildFlags is the set of flags passed on to the build system when invoked.
195	// It is applied to queries like `go list`, which is used when discovering files.
196	// The most common use is to set `-tags`.
197	BuildFlags []string
198
199	// Env adds environment variables to external commands run by `gopls`, most notably `go list`.
200	Env map[string]string
201
202	// DirectoryFilters can be used to exclude unwanted directories from the
203	// workspace. By default, all directories are included. Filters are an
204	// operator, `+` to include and `-` to exclude, followed by a path prefix
205	// relative to the workspace folder. They are evaluated in order, and
206	// the last filter that applies to a path controls whether it is included.
207	// The path prefix can be empty, so an initial `-` excludes everything.
208	//
209	// Examples:
210	// Exclude node_modules: `-node_modules`
211	// Include only project_a: `-` (exclude everything), `+project_a`
212	// Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`
213	DirectoryFilters []string
214
215	// ExpandWorkspaceToModule instructs `gopls` to adjust the scope of the
216	// workspace to find the best available module root. `gopls` first looks for
217	// a go.mod file in any parent directory of the workspace folder, expanding
218	// the scope to that directory if it exists. If no viable parent directory is
219	// found, gopls will check if there is exactly one child directory containing
220	// a go.mod file, narrowing the scope to that directory if it exists.
221	ExpandWorkspaceToModule bool `status:"experimental"`
222
223	// ExperimentalWorkspaceModule opts a user into the experimental support
224	// for multi-module workspaces.
225	ExperimentalWorkspaceModule bool `status:"experimental"`
226
227	// ExperimentalPackageCacheKey controls whether to use a coarser cache key
228	// for package type information to increase cache hits. This setting removes
229	// the user's environment, build flags, and working directory from the cache
230	// key, which should be a safe change as all relevant inputs into the type
231	// checking pass are already hashed into the key. This is temporarily guarded
232	// by an experiment because caching behavior is subtle and difficult to
233	// comprehensively test.
234	ExperimentalPackageCacheKey bool `status:"experimental"`
235
236	// AllowModfileModifications disables -mod=readonly, allowing imports from
237	// out-of-scope modules. This option will eventually be removed.
238	AllowModfileModifications bool `status:"experimental"`
239
240	// AllowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module
241	// downloads rather than requiring user action. This option will eventually
242	// be removed.
243	AllowImplicitNetworkAccess bool `status:"experimental"`
244}
245
246type UIOptions struct {
247	DocumentationOptions
248	CompletionOptions
249	NavigationOptions
250	DiagnosticOptions
251
252	// Codelenses overrides the enabled/disabled state of code lenses. See the
253	// "Code Lenses" section of the
254	// [Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md)
255	// for the list of supported lenses.
256	//
257	// Example Usage:
258	//
259	// ```json5
260	// "gopls": {
261	// ...
262	//   "codelens": {
263	//     "generate": false,  // Don't show the `go generate` lens.
264	//     "gc_details": true  // Show a code lens toggling the display of gc's choices.
265	//   }
266	// ...
267	// }
268	// ```
269	Codelenses map[string]bool
270
271	// SemanticTokens controls whether the LSP server will send
272	// semantic tokens to the client.
273	SemanticTokens bool `status:"experimental"`
274}
275
276type CompletionOptions struct {
277	// Placeholders enables placeholders for function parameters or struct
278	// fields in completion responses.
279	UsePlaceholders bool
280
281	// CompletionBudget is the soft latency goal for completion requests. Most
282	// requests finish in a couple milliseconds, but in some cases deep
283	// completions can take much longer. As we use up our budget we
284	// dynamically reduce the search scope to ensure we return timely
285	// results. Zero means unlimited.
286	CompletionBudget time.Duration `status:"debug"`
287
288	// Matcher sets the algorithm that is used when calculating completion
289	// candidates.
290	Matcher Matcher `status:"advanced"`
291}
292
293type DocumentationOptions struct {
294	// HoverKind controls the information that appears in the hover text.
295	// SingleLine and Structured are intended for use only by authors of editor plugins.
296	HoverKind HoverKind
297
298	// LinkTarget controls where documentation links go.
299	// It might be one of:
300	//
301	// * `"godoc.org"`
302	// * `"pkg.go.dev"`
303	//
304	// If company chooses to use its own `godoc.org`, its address can be used as well.
305	LinkTarget string
306
307	// LinksInHover toggles the presence of links to documentation in hover.
308	LinksInHover bool
309}
310
311type FormattingOptions struct {
312	// Local is the equivalent of the `goimports -local` flag, which puts
313	// imports beginning with this string after third-party packages. It should
314	// be the prefix of the import path whose imports should be grouped
315	// separately.
316	Local string
317
318	// Gofumpt indicates if we should run gofumpt formatting.
319	Gofumpt bool
320}
321
322type DiagnosticOptions struct {
323	// Analyses specify analyses that the user would like to enable or disable.
324	// A map of the names of analysis passes that should be enabled/disabled.
325	// A full list of analyzers that gopls uses can be found
326	// [here](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).
327	//
328	// Example Usage:
329	//
330	// ```json5
331	// ...
332	// "analyses": {
333	//   "unreachable": false, // Disable the unreachable analyzer.
334	//   "unusedparams": true  // Enable the unusedparams analyzer.
335	// }
336	// ...
337	// ```
338	Analyses map[string]bool
339
340	// Staticcheck enables additional analyses from staticcheck.io.
341	Staticcheck bool `status:"experimental"`
342
343	// Annotations specifies the various kinds of optimization diagnostics
344	// that should be reported by the gc_details command.
345	Annotations map[Annotation]bool `status:"experimental"`
346
347	// ExperimentalDiagnosticsDelay controls the amount of time that gopls waits
348	// after the most recent file modification before computing deep diagnostics.
349	// Simple diagnostics (parsing and type-checking) are always run immediately
350	// on recently modified packages.
351	//
352	// This option must be set to a valid duration string, for example `"250ms"`.
353	ExperimentalDiagnosticsDelay time.Duration `status:"experimental"`
354}
355
356type NavigationOptions struct {
357	// ImportShortcut specifies whether import statements should link to
358	// documentation or go to definitions.
359	ImportShortcut ImportShortcut
360
361	// SymbolMatcher sets the algorithm that is used when finding workspace symbols.
362	SymbolMatcher SymbolMatcher `status:"advanced"`
363
364	// SymbolStyle controls how symbols are qualified in symbol responses.
365	//
366	// Example Usage:
367	//
368	// ```json5
369	// "gopls": {
370	// ...
371	//   "symbolStyle": "dynamic",
372	// ...
373	// }
374	// ```
375	SymbolStyle SymbolStyle `status:"advanced"`
376}
377
378// UserOptions holds custom Gopls configuration (not part of the LSP) that is
379// modified by the client.
380type UserOptions struct {
381	BuildOptions
382	UIOptions
383	FormattingOptions
384
385	// VerboseOutput enables additional debug logging.
386	VerboseOutput bool `status:"debug"`
387}
388
389// EnvSlice returns Env as a slice of k=v strings.
390func (u *UserOptions) EnvSlice() []string {
391	var result []string
392	for k, v := range u.Env {
393		result = append(result, fmt.Sprintf("%v=%v", k, v))
394	}
395	return result
396}
397
398// SetEnvSlice sets Env from a slice of k=v strings.
399func (u *UserOptions) SetEnvSlice(env []string) {
400	u.Env = map[string]string{}
401	for _, kv := range env {
402		split := strings.SplitN(kv, "=", 2)
403		if len(split) != 2 {
404			continue
405		}
406		u.Env[split[0]] = split[1]
407	}
408}
409
410// Hooks contains configuration that is provided to the Gopls command by the
411// main package.
412type Hooks struct {
413	LicensesText         string
414	GoDiff               bool
415	ComputeEdits         diff.ComputeEdits
416	URLRegexp            *regexp.Regexp
417	GofumptFormat        func(ctx context.Context, src []byte) ([]byte, error)
418	DefaultAnalyzers     map[string]Analyzer
419	TypeErrorAnalyzers   map[string]Analyzer
420	ConvenienceAnalyzers map[string]Analyzer
421	StaticcheckAnalyzers map[string]Analyzer
422}
423
424// InternalOptions contains settings that are not intended for use by the
425// average user. These may be settings used by tests or outdated settings that
426// will soon be deprecated. Some of these settings may not even be configurable
427// by the user.
428type InternalOptions struct {
429	// LiteralCompletions controls whether literal candidates such as
430	// "&someStruct{}" are offered. Tests disable this flag to simplify
431	// their expected values.
432	LiteralCompletions bool
433
434	// VerboseWorkDoneProgress controls whether the LSP server should send
435	// progress reports for all work done outside the scope of an RPC.
436	// Used by the regression tests.
437	VerboseWorkDoneProgress bool
438
439	// The following options were previously available to users, but they
440	// really shouldn't be configured by anyone other than "power users".
441
442	// CompletionDocumentation enables documentation with completion results.
443	CompletionDocumentation bool
444
445	// CompleteUnimported enables completion for packages that you do not
446	// currently import.
447	CompleteUnimported bool
448
449	// DeepCompletion enables the ability to return completions from deep
450	// inside relevant entities, rather than just the locally accessible ones.
451	//
452	// Consider this example:
453	//
454	// ```go
455	// package main
456	//
457	// import "fmt"
458	//
459	// type wrapString struct {
460	//     str string
461	// }
462	//
463	// func main() {
464	//     x := wrapString{"hello world"}
465	//     fmt.Printf(<>)
466	// }
467	// ```
468	//
469	// At the location of the `<>` in this program, deep completion would suggest the result `x.str`.
470	DeepCompletion bool
471
472	// TempModfile controls the use of the -modfile flag in Go 1.14.
473	TempModfile bool
474}
475
476type ImportShortcut string
477
478const (
479	Both       ImportShortcut = "Both"
480	Link       ImportShortcut = "Link"
481	Definition ImportShortcut = "Definition"
482)
483
484func (s ImportShortcut) ShowLinks() bool {
485	return s == Both || s == Link
486}
487
488func (s ImportShortcut) ShowDefinition() bool {
489	return s == Both || s == Definition
490}
491
492type Matcher string
493
494const (
495	Fuzzy           Matcher = "Fuzzy"
496	CaseInsensitive Matcher = "CaseInsensitive"
497	CaseSensitive   Matcher = "CaseSensitive"
498)
499
500type SymbolMatcher string
501
502const (
503	SymbolFuzzy           SymbolMatcher = "Fuzzy"
504	SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive"
505	SymbolCaseSensitive   SymbolMatcher = "CaseSensitive"
506)
507
508type SymbolStyle string
509
510const (
511	// PackageQualifiedSymbols is package qualified symbols i.e.
512	// "pkg.Foo.Field".
513	PackageQualifiedSymbols SymbolStyle = "Package"
514	// FullyQualifiedSymbols is fully qualified symbols, i.e.
515	// "path/to/pkg.Foo.Field".
516	FullyQualifiedSymbols SymbolStyle = "Full"
517	// DynamicSymbols uses whichever qualifier results in the highest scoring
518	// match for the given symbol query. Here a "qualifier" is any "/" or "."
519	// delimited suffix of the fully qualified symbol. i.e. "to/pkg.Foo.Field" or
520	// just "Foo.Field".
521	DynamicSymbols SymbolStyle = "Dynamic"
522)
523
524type HoverKind string
525
526const (
527	SingleLine            HoverKind = "SingleLine"
528	NoDocumentation       HoverKind = "NoDocumentation"
529	SynopsisDocumentation HoverKind = "SynopsisDocumentation"
530	FullDocumentation     HoverKind = "FullDocumentation"
531
532	// Structured is an experimental setting that returns a structured hover format.
533	// This format separates the signature from the documentation, so that the client
534	// can do more manipulation of these fields.
535	//
536	// This should only be used by clients that support this behavior.
537	Structured HoverKind = "Structured"
538)
539
540type OptionResults []OptionResult
541
542type OptionResult struct {
543	Name  string
544	Value interface{}
545	Error error
546
547	State       OptionState
548	Replacement string
549}
550
551type OptionState int
552
553const (
554	OptionHandled = OptionState(iota)
555	OptionDeprecated
556	OptionUnexpected
557)
558
559type LinkTarget string
560
561func SetOptions(options *Options, opts interface{}) OptionResults {
562	var results OptionResults
563	switch opts := opts.(type) {
564	case nil:
565	case map[string]interface{}:
566		// If the user's settings contains "allExperiments", set that first,
567		// and then let them override individual settings independently.
568		var enableExperiments bool
569		for name, value := range opts {
570			if b, ok := value.(bool); name == "allExperiments" && ok && b {
571				enableExperiments = true
572				options.enableAllExperiments()
573			}
574		}
575		seen := map[string]struct{}{}
576		for name, value := range opts {
577			results = append(results, options.set(name, value, seen))
578		}
579		// Finally, enable any experimental features that are specified in
580		// maps, which allows users to individually toggle them on or off.
581		if enableExperiments {
582			options.enableAllExperimentMaps()
583		}
584	default:
585		results = append(results, OptionResult{
586			Value: opts,
587			Error: errors.Errorf("Invalid options type %T", opts),
588		})
589	}
590	return results
591}
592
593func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) {
594	// Check if the client supports snippets in completion items.
595	if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport {
596		o.InsertTextFormat = protocol.SnippetTextFormat
597	}
598	// Check if the client supports configuration messages.
599	o.ConfigurationSupported = caps.Workspace.Configuration
600	o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
601	o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration
602
603	// Check which types of content format are supported by this client.
604	if hover := caps.TextDocument.Hover; len(hover.ContentFormat) > 0 {
605		o.PreferredContentFormat = hover.ContentFormat[0]
606	}
607	// Check if the client supports only line folding.
608	fr := caps.TextDocument.FoldingRange
609	o.LineFoldingOnly = fr.LineFoldingOnly
610	// Check if the client supports hierarchical document symbols.
611	o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
612	// Check if the client supports semantic tokens
613	o.SemanticTypes = caps.TextDocument.SemanticTokens.TokenTypes
614	o.SemanticMods = caps.TextDocument.SemanticTokens.TokenModifiers
615	// we don't need Requests, as we support full functionality
616	// we don't need Formats, as there is only one, for now
617}
618
619func (o *Options) Clone() *Options {
620	result := &Options{
621		ClientOptions:   o.ClientOptions,
622		InternalOptions: o.InternalOptions,
623		Hooks: Hooks{
624			GoDiff:        o.Hooks.GoDiff,
625			ComputeEdits:  o.Hooks.ComputeEdits,
626			GofumptFormat: o.GofumptFormat,
627			URLRegexp:     o.URLRegexp,
628		},
629		ServerOptions: o.ServerOptions,
630		UserOptions:   o.UserOptions,
631	}
632	// Fully clone any slice or map fields. Only Hooks, ExperimentalOptions,
633	// and UserOptions can be modified.
634	copyStringMap := func(src map[string]bool) map[string]bool {
635		dst := make(map[string]bool)
636		for k, v := range src {
637			dst[k] = v
638		}
639		return dst
640	}
641	result.Analyses = copyStringMap(o.Analyses)
642	result.Codelenses = copyStringMap(o.Codelenses)
643
644	copySlice := func(src []string) []string {
645		dst := make([]string, len(src))
646		copy(dst, src)
647		return dst
648	}
649	result.SetEnvSlice(o.EnvSlice())
650	result.BuildFlags = copySlice(o.BuildFlags)
651	result.DirectoryFilters = copySlice(o.DirectoryFilters)
652
653	copyAnalyzerMap := func(src map[string]Analyzer) map[string]Analyzer {
654		dst := make(map[string]Analyzer)
655		for k, v := range src {
656			dst[k] = v
657		}
658		return dst
659	}
660	result.DefaultAnalyzers = copyAnalyzerMap(o.DefaultAnalyzers)
661	result.TypeErrorAnalyzers = copyAnalyzerMap(o.TypeErrorAnalyzers)
662	result.ConvenienceAnalyzers = copyAnalyzerMap(o.ConvenienceAnalyzers)
663	result.StaticcheckAnalyzers = copyAnalyzerMap(o.StaticcheckAnalyzers)
664	return result
665}
666
667func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer) {
668	o.StaticcheckAnalyzers[a.Name] = Analyzer{Analyzer: a, Enabled: true}
669}
670
671// enableAllExperiments turns on all of the experimental "off-by-default"
672// features offered by gopls. Any experimental features specified in maps
673// should be enabled in enableAllExperimentMaps.
674func (o *Options) enableAllExperiments() {
675	// There are currently no experimental features in development.
676}
677
678func (o *Options) enableAllExperimentMaps() {
679	if _, ok := o.Codelenses[CommandToggleDetails.Name]; !ok {
680		o.Codelenses[CommandToggleDetails.Name] = true
681	}
682	if _, ok := o.Analyses[unusedparams.Analyzer.Name]; !ok {
683		o.Analyses[unusedparams.Analyzer.Name] = true
684	}
685}
686
687func (o *Options) set(name string, value interface{}, seen map[string]struct{}) OptionResult {
688	// Flatten the name in case we get options with a hierarchy.
689	split := strings.Split(name, ".")
690	name = split[len(split)-1]
691
692	result := OptionResult{Name: name, Value: value}
693	if _, ok := seen[name]; ok {
694		result.errorf("duplicate configuration for %s", name)
695	}
696	seen[name] = struct{}{}
697
698	switch name {
699	case "env":
700		menv, ok := value.(map[string]interface{})
701		if !ok {
702			result.errorf("invalid type %T, expect map", value)
703			break
704		}
705		if o.Env == nil {
706			o.Env = make(map[string]string)
707		}
708		for k, v := range menv {
709			o.Env[k] = fmt.Sprint(v)
710		}
711
712	case "buildFlags":
713		iflags, ok := value.([]interface{})
714		if !ok {
715			result.errorf("invalid type %T, expect list", value)
716			break
717		}
718		flags := make([]string, 0, len(iflags))
719		for _, flag := range iflags {
720			flags = append(flags, fmt.Sprintf("%s", flag))
721		}
722		o.BuildFlags = flags
723	case "directoryFilters":
724		ifilters, ok := value.([]interface{})
725		if !ok {
726			result.errorf("invalid type %T, expect list", value)
727			break
728		}
729		var filters []string
730		for _, ifilter := range ifilters {
731			filter := fmt.Sprint(ifilter)
732			if filter[0] != '+' && filter[0] != '-' {
733				result.errorf("invalid filter %q, must start with + or -", filter)
734				return result
735			}
736			filters = append(filters, filepath.FromSlash(filter))
737		}
738		o.DirectoryFilters = filters
739	case "completionDocumentation":
740		result.setBool(&o.CompletionDocumentation)
741	case "usePlaceholders":
742		result.setBool(&o.UsePlaceholders)
743	case "deepCompletion":
744		result.setBool(&o.DeepCompletion)
745	case "completeUnimported":
746		result.setBool(&o.CompleteUnimported)
747	case "completionBudget":
748		result.setDuration(&o.CompletionBudget)
749	case "matcher":
750		if s, ok := result.asOneOf(
751			string(Fuzzy),
752			string(CaseSensitive),
753			string(CaseInsensitive),
754		); ok {
755			o.Matcher = Matcher(s)
756		}
757
758	case "symbolMatcher":
759		if s, ok := result.asOneOf(
760			string(SymbolFuzzy),
761			string(SymbolCaseInsensitive),
762			string(SymbolCaseSensitive),
763		); ok {
764			o.SymbolMatcher = SymbolMatcher(s)
765		}
766
767	case "symbolStyle":
768		if s, ok := result.asOneOf(
769			string(FullyQualifiedSymbols),
770			string(PackageQualifiedSymbols),
771			string(DynamicSymbols),
772		); ok {
773			o.SymbolStyle = SymbolStyle(s)
774		}
775
776	case "hoverKind":
777		if s, ok := result.asOneOf(
778			string(NoDocumentation),
779			string(SingleLine),
780			string(SynopsisDocumentation),
781			string(FullDocumentation),
782			string(Structured),
783		); ok {
784			o.HoverKind = HoverKind(s)
785		}
786
787	case "linkTarget":
788		result.setString(&o.LinkTarget)
789
790	case "linksInHover":
791		result.setBool(&o.LinksInHover)
792
793	case "importShortcut":
794		if s, ok := result.asOneOf(string(Both), string(Link), string(Definition)); ok {
795			o.ImportShortcut = ImportShortcut(s)
796		}
797
798	case "analyses":
799		result.setBoolMap(&o.Analyses)
800
801	case "annotations":
802		result.setAnnotationMap(&o.Annotations)
803
804	case "codelenses", "codelens":
805		var lensOverrides map[string]bool
806		result.setBoolMap(&lensOverrides)
807		if result.Error == nil {
808			if o.Codelenses == nil {
809				o.Codelenses = make(map[string]bool)
810			}
811			for lens, enabled := range lensOverrides {
812				o.Codelenses[lens] = enabled
813			}
814		}
815
816		// codelens is deprecated, but still works for now.
817		// TODO(rstambler): Remove this for the gopls/v0.7.0 release.
818		if name == "codelens" {
819			result.State = OptionDeprecated
820			result.Replacement = "codelenses"
821		}
822
823	case "staticcheck":
824		result.setBool(&o.Staticcheck)
825
826	case "local":
827		result.setString(&o.Local)
828
829	case "verboseOutput":
830		result.setBool(&o.VerboseOutput)
831
832	case "verboseWorkDoneProgress":
833		result.setBool(&o.VerboseWorkDoneProgress)
834
835	case "tempModfile":
836		result.setBool(&o.TempModfile)
837
838	case "gofumpt":
839		result.setBool(&o.Gofumpt)
840
841	case "semanticTokens":
842		result.setBool(&o.SemanticTokens)
843
844	case "expandWorkspaceToModule":
845		result.setBool(&o.ExpandWorkspaceToModule)
846
847	case "experimentalWorkspaceModule":
848		result.setBool(&o.ExperimentalWorkspaceModule)
849
850	case "experimentalDiagnosticsDelay":
851		result.setDuration(&o.ExperimentalDiagnosticsDelay)
852
853	case "experimentalPackageCacheKey":
854		result.setBool(&o.ExperimentalPackageCacheKey)
855
856	case "allowModfileModifications":
857		result.setBool(&o.AllowModfileModifications)
858
859	case "allowImplicitNetworkAccess":
860		result.setBool(&o.AllowImplicitNetworkAccess)
861
862	case "allExperiments":
863		// This setting should be handled before all of the other options are
864		// processed, so do nothing here.
865
866	// Replaced settings.
867	case "experimentalDisabledAnalyses":
868		result.State = OptionDeprecated
869		result.Replacement = "analyses"
870
871	case "disableDeepCompletion":
872		result.State = OptionDeprecated
873		result.Replacement = "deepCompletion"
874
875	case "disableFuzzyMatching":
876		result.State = OptionDeprecated
877		result.Replacement = "fuzzyMatching"
878
879	case "wantCompletionDocumentation":
880		result.State = OptionDeprecated
881		result.Replacement = "completionDocumentation"
882
883	case "wantUnimportedCompletions":
884		result.State = OptionDeprecated
885		result.Replacement = "completeUnimported"
886
887	case "fuzzyMatching":
888		result.State = OptionDeprecated
889		result.Replacement = "matcher"
890
891	case "caseSensitiveCompletion":
892		result.State = OptionDeprecated
893		result.Replacement = "matcher"
894
895	// Deprecated settings.
896	case "wantSuggestedFixes":
897		result.State = OptionDeprecated
898
899	case "noIncrementalSync":
900		result.State = OptionDeprecated
901
902	case "watchFileChanges":
903		result.State = OptionDeprecated
904
905	case "go-diff":
906		result.State = OptionDeprecated
907
908	default:
909		result.State = OptionUnexpected
910	}
911	return result
912}
913
914func (r *OptionResult) errorf(msg string, values ...interface{}) {
915	prefix := fmt.Sprintf("parsing setting %q: ", r.Name)
916	r.Error = errors.Errorf(prefix+msg, values...)
917}
918
919func (r *OptionResult) asBool() (bool, bool) {
920	b, ok := r.Value.(bool)
921	if !ok {
922		r.errorf("invalid type %T, expect bool", r.Value)
923		return false, false
924	}
925	return b, true
926}
927
928func (r *OptionResult) setBool(b *bool) {
929	if v, ok := r.asBool(); ok {
930		*b = v
931	}
932}
933
934func (r *OptionResult) setDuration(d *time.Duration) {
935	if v, ok := r.asString(); ok {
936		parsed, err := time.ParseDuration(v)
937		if err != nil {
938			r.errorf("failed to parse duration %q: %v", v, err)
939			return
940		}
941		*d = parsed
942	}
943}
944
945func (r *OptionResult) setBoolMap(bm *map[string]bool) {
946	m := r.asBoolMap()
947	*bm = m
948}
949
950func (r *OptionResult) setAnnotationMap(bm *map[Annotation]bool) {
951	all := r.asBoolMap()
952	if all == nil {
953		return
954	}
955	// Default to everything enabled by default.
956	m := make(map[Annotation]bool)
957	for k, enabled := range all {
958		a, err := asOneOf(
959			k,
960			string(Nil),
961			string(Escape),
962			string(Inline),
963			string(Bounds),
964		)
965		if err != nil {
966			// In case of an error, process any legacy values.
967			switch k {
968			case "noEscape":
969				m[Escape] = false
970				r.errorf(`"noEscape" is deprecated, set "Escape: false" instead`)
971			case "noNilcheck":
972				m[Nil] = false
973				r.errorf(`"noNilcheck" is deprecated, set "Nil: false" instead`)
974			case "noInline":
975				m[Inline] = false
976				r.errorf(`"noInline" is deprecated, set "Inline: false" instead`)
977			case "noBounds":
978				m[Bounds] = false
979				r.errorf(`"noBounds" is deprecated, set "Bounds: false" instead`)
980			default:
981				r.errorf(err.Error())
982			}
983			continue
984		}
985		m[Annotation(a)] = enabled
986	}
987	*bm = m
988}
989
990func (r *OptionResult) asBoolMap() map[string]bool {
991	all, ok := r.Value.(map[string]interface{})
992	if !ok {
993		r.errorf("invalid type %T for map[string]bool option", r.Value)
994		return nil
995	}
996	m := make(map[string]bool)
997	for a, enabled := range all {
998		if enabled, ok := enabled.(bool); ok {
999			m[a] = enabled
1000		} else {
1001			r.errorf("invalid type %T for map key %q", enabled, a)
1002			return m
1003		}
1004	}
1005	return m
1006}
1007
1008func (r *OptionResult) asString() (string, bool) {
1009	b, ok := r.Value.(string)
1010	if !ok {
1011		r.errorf("invalid type %T, expect string", r.Value)
1012		return "", false
1013	}
1014	return b, true
1015}
1016
1017func (r *OptionResult) asOneOf(options ...string) (string, bool) {
1018	s, ok := r.asString()
1019	if !ok {
1020		return "", false
1021	}
1022	s, err := asOneOf(s, options...)
1023	if err != nil {
1024		r.errorf(err.Error())
1025	}
1026	return s, err == nil
1027}
1028
1029func asOneOf(str string, options ...string) (string, error) {
1030	lower := strings.ToLower(str)
1031	for _, opt := range options {
1032		if strings.ToLower(opt) == lower {
1033			return opt, nil
1034		}
1035	}
1036	return "", fmt.Errorf("invalid option %q for enum", str)
1037}
1038
1039func (r *OptionResult) setString(s *string) {
1040	if v, ok := r.asString(); ok {
1041		*s = v
1042	}
1043}
1044
1045// EnabledAnalyzers returns all of the analyzers enabled for the given
1046// snapshot.
1047func EnabledAnalyzers(snapshot Snapshot) (analyzers []Analyzer) {
1048	for _, a := range snapshot.View().Options().DefaultAnalyzers {
1049		if a.IsEnabled(snapshot.View()) {
1050			analyzers = append(analyzers, a)
1051		}
1052	}
1053	for _, a := range snapshot.View().Options().TypeErrorAnalyzers {
1054		if a.IsEnabled(snapshot.View()) {
1055			analyzers = append(analyzers, a)
1056		}
1057	}
1058	for _, a := range snapshot.View().Options().ConvenienceAnalyzers {
1059		if a.IsEnabled(snapshot.View()) {
1060			analyzers = append(analyzers, a)
1061		}
1062	}
1063	for _, a := range snapshot.View().Options().StaticcheckAnalyzers {
1064		if a.IsEnabled(snapshot.View()) {
1065			analyzers = append(analyzers, a)
1066		}
1067	}
1068	return analyzers
1069}
1070
1071func typeErrorAnalyzers() map[string]Analyzer {
1072	return map[string]Analyzer{
1073		fillreturns.Analyzer.Name: {
1074			Analyzer:       fillreturns.Analyzer,
1075			FixesError:     fillreturns.FixesError,
1076			HighConfidence: true,
1077			Enabled:        true,
1078		},
1079		nonewvars.Analyzer.Name: {
1080			Analyzer:   nonewvars.Analyzer,
1081			FixesError: nonewvars.FixesError,
1082			Enabled:    true,
1083		},
1084		noresultvalues.Analyzer.Name: {
1085			Analyzer:   noresultvalues.Analyzer,
1086			FixesError: noresultvalues.FixesError,
1087			Enabled:    true,
1088		},
1089		undeclaredname.Analyzer.Name: {
1090			Analyzer:   undeclaredname.Analyzer,
1091			FixesError: undeclaredname.FixesError,
1092			Command:    CommandUndeclaredName,
1093			Enabled:    true,
1094		},
1095	}
1096}
1097
1098func convenienceAnalyzers() map[string]Analyzer {
1099	return map[string]Analyzer{
1100		fillstruct.Analyzer.Name: {
1101			Analyzer: fillstruct.Analyzer,
1102			Command:  CommandFillStruct,
1103			Enabled:  true,
1104		},
1105	}
1106}
1107
1108func defaultAnalyzers() map[string]Analyzer {
1109	return map[string]Analyzer{
1110		// The traditional vet suite:
1111		asmdecl.Analyzer.Name:       {Analyzer: asmdecl.Analyzer, Enabled: true},
1112		assign.Analyzer.Name:        {Analyzer: assign.Analyzer, Enabled: true},
1113		atomic.Analyzer.Name:        {Analyzer: atomic.Analyzer, Enabled: true},
1114		bools.Analyzer.Name:         {Analyzer: bools.Analyzer, Enabled: true},
1115		buildtag.Analyzer.Name:      {Analyzer: buildtag.Analyzer, Enabled: true},
1116		cgocall.Analyzer.Name:       {Analyzer: cgocall.Analyzer, Enabled: true},
1117		composite.Analyzer.Name:     {Analyzer: composite.Analyzer, Enabled: true},
1118		copylock.Analyzer.Name:      {Analyzer: copylock.Analyzer, Enabled: true},
1119		errorsas.Analyzer.Name:      {Analyzer: errorsas.Analyzer, Enabled: true},
1120		httpresponse.Analyzer.Name:  {Analyzer: httpresponse.Analyzer, Enabled: true},
1121		ifaceassert.Analyzer.Name:   {Analyzer: ifaceassert.Analyzer, Enabled: true},
1122		loopclosure.Analyzer.Name:   {Analyzer: loopclosure.Analyzer, Enabled: true},
1123		lostcancel.Analyzer.Name:    {Analyzer: lostcancel.Analyzer, Enabled: true},
1124		nilfunc.Analyzer.Name:       {Analyzer: nilfunc.Analyzer, Enabled: true},
1125		printf.Analyzer.Name:        {Analyzer: printf.Analyzer, Enabled: true},
1126		shift.Analyzer.Name:         {Analyzer: shift.Analyzer, Enabled: true},
1127		stdmethods.Analyzer.Name:    {Analyzer: stdmethods.Analyzer, Enabled: true},
1128		stringintconv.Analyzer.Name: {Analyzer: stringintconv.Analyzer, Enabled: true},
1129		structtag.Analyzer.Name:     {Analyzer: structtag.Analyzer, Enabled: true},
1130		tests.Analyzer.Name:         {Analyzer: tests.Analyzer, Enabled: true},
1131		unmarshal.Analyzer.Name:     {Analyzer: unmarshal.Analyzer, Enabled: true},
1132		unreachable.Analyzer.Name:   {Analyzer: unreachable.Analyzer, Enabled: true},
1133		unsafeptr.Analyzer.Name:     {Analyzer: unsafeptr.Analyzer, Enabled: true},
1134		unusedresult.Analyzer.Name:  {Analyzer: unusedresult.Analyzer, Enabled: true},
1135
1136		// Non-vet analyzers:
1137		atomicalign.Analyzer.Name:      {Analyzer: atomicalign.Analyzer, Enabled: true},
1138		deepequalerrors.Analyzer.Name:  {Analyzer: deepequalerrors.Analyzer, Enabled: true},
1139		fieldalignment.Analyzer.Name:   {Analyzer: fieldalignment.Analyzer, Enabled: false},
1140		shadow.Analyzer.Name:           {Analyzer: shadow.Analyzer, Enabled: false},
1141		sortslice.Analyzer.Name:        {Analyzer: sortslice.Analyzer, Enabled: true},
1142		testinggoroutine.Analyzer.Name: {Analyzer: testinggoroutine.Analyzer, Enabled: true},
1143		unusedparams.Analyzer.Name:     {Analyzer: unusedparams.Analyzer, Enabled: false},
1144
1145		// gofmt -s suite:
1146		simplifycompositelit.Analyzer.Name: {Analyzer: simplifycompositelit.Analyzer, Enabled: true, HighConfidence: true},
1147		simplifyrange.Analyzer.Name:        {Analyzer: simplifyrange.Analyzer, Enabled: true, HighConfidence: true},
1148		simplifyslice.Analyzer.Name:        {Analyzer: simplifyslice.Analyzer, Enabled: true, HighConfidence: true},
1149	}
1150}
1151
1152func urlRegexp() *regexp.Regexp {
1153	// Ensure links are matched as full words, not anywhere.
1154	re := regexp.MustCompile(`\b(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?\b`)
1155	re.Longest()
1156	return re
1157}
1158
1159type APIJSON struct {
1160	Options   map[string][]*OptionJSON
1161	Commands  []*CommandJSON
1162	Lenses    []*LensJSON
1163	Analyzers []*AnalyzerJSON
1164}
1165
1166type OptionJSON struct {
1167	Name       string
1168	Type       string
1169	Doc        string
1170	EnumKeys   EnumKeys
1171	EnumValues []EnumValue
1172	Default    string
1173	Status     string
1174	Hierarchy  string
1175}
1176
1177type EnumKeys struct {
1178	ValueType string
1179	Keys      []EnumKey
1180}
1181
1182type EnumKey struct {
1183	Name    string
1184	Doc     string
1185	Default string
1186}
1187
1188type EnumValue struct {
1189	Value string
1190	Doc   string
1191}
1192
1193type CommandJSON struct {
1194	Command string
1195	Title   string
1196	Doc     string
1197}
1198
1199type LensJSON struct {
1200	Lens  string
1201	Title string
1202	Doc   string
1203}
1204
1205type AnalyzerJSON struct {
1206	Name    string
1207	Doc     string
1208	Default bool
1209}
1210