--- /dev/null
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package source
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/asmdecl"
+ "golang.org/x/tools/go/analysis/passes/assign"
+ "golang.org/x/tools/go/analysis/passes/atomic"
+ "golang.org/x/tools/go/analysis/passes/atomicalign"
+ "golang.org/x/tools/go/analysis/passes/bools"
+ "golang.org/x/tools/go/analysis/passes/buildtag"
+ "golang.org/x/tools/go/analysis/passes/cgocall"
+ "golang.org/x/tools/go/analysis/passes/composite"
+ "golang.org/x/tools/go/analysis/passes/copylock"
+ "golang.org/x/tools/go/analysis/passes/deepequalerrors"
+ "golang.org/x/tools/go/analysis/passes/errorsas"
+ "golang.org/x/tools/go/analysis/passes/httpresponse"
+ "golang.org/x/tools/go/analysis/passes/loopclosure"
+ "golang.org/x/tools/go/analysis/passes/lostcancel"
+ "golang.org/x/tools/go/analysis/passes/nilfunc"
+ "golang.org/x/tools/go/analysis/passes/printf"
+ "golang.org/x/tools/go/analysis/passes/shift"
+ "golang.org/x/tools/go/analysis/passes/sortslice"
+ "golang.org/x/tools/go/analysis/passes/stdmethods"
+ "golang.org/x/tools/go/analysis/passes/structtag"
+ "golang.org/x/tools/go/analysis/passes/testinggoroutine"
+ "golang.org/x/tools/go/analysis/passes/tests"
+ "golang.org/x/tools/go/analysis/passes/unmarshal"
+ "golang.org/x/tools/go/analysis/passes/unreachable"
+ "golang.org/x/tools/go/analysis/passes/unsafeptr"
+ "golang.org/x/tools/go/analysis/passes/unusedresult"
+ "golang.org/x/tools/internal/lsp/analysis/fillreturns"
+ "golang.org/x/tools/internal/lsp/analysis/fillstruct"
+ "golang.org/x/tools/internal/lsp/analysis/nonewvars"
+ "golang.org/x/tools/internal/lsp/analysis/noresultvalues"
+ "golang.org/x/tools/internal/lsp/analysis/simplifycompositelit"
+ "golang.org/x/tools/internal/lsp/analysis/simplifyrange"
+ "golang.org/x/tools/internal/lsp/analysis/simplifyslice"
+ "golang.org/x/tools/internal/lsp/analysis/undeclaredname"
+ "golang.org/x/tools/internal/lsp/analysis/unusedparams"
+ "golang.org/x/tools/internal/lsp/diff"
+ "golang.org/x/tools/internal/lsp/diff/myers"
+ "golang.org/x/tools/internal/lsp/protocol"
+ errors "golang.org/x/xerrors"
+)
+
+var (
+ optionsOnce sync.Once
+ defaultOptions *Options
+)
+
+//go:generate go run golang.org/x/tools/internal/lsp/source/genapijson -output api_json.go
+
+// DefaultOptions is the options that are used for Gopls execution independent
+// of any externally provided configuration (LSP initialization, command
+// invokation, etc.).
+func DefaultOptions() *Options {
+ optionsOnce.Do(func() {
+ var commands []string
+ for _, c := range Commands {
+ commands = append(commands, c.ID())
+ }
+ defaultOptions = &Options{
+ ClientOptions: ClientOptions{
+ InsertTextFormat: protocol.PlainTextTextFormat,
+ PreferredContentFormat: protocol.Markdown,
+ ConfigurationSupported: true,
+ DynamicConfigurationSupported: true,
+ DynamicWatchedFilesSupported: true,
+ LineFoldingOnly: false,
+ HierarchicalDocumentSymbolSupport: true,
+ },
+ ServerOptions: ServerOptions{
+ SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
+ Go: {
+ protocol.SourceFixAll: true,
+ protocol.SourceOrganizeImports: true,
+ protocol.QuickFix: true,
+ protocol.RefactorRewrite: true,
+ protocol.RefactorExtract: true,
+ },
+ Mod: {
+ protocol.SourceOrganizeImports: true,
+ },
+ Sum: {},
+ },
+ SupportedCommands: commands,
+ },
+ UserOptions: UserOptions{
+ HoverKind: FullDocumentation,
+ LinkTarget: "pkg.go.dev",
+ },
+ DebuggingOptions: DebuggingOptions{
+ CompletionBudget: 100 * time.Millisecond,
+ },
+ ExperimentalOptions: ExperimentalOptions{
+ TempModfile: true,
+ ExpandWorkspaceToModule: true,
+ Codelens: map[string]bool{
+ CommandGenerate.Name: true,
+ CommandRegenerateCgo.Name: true,
+ CommandTidy.Name: true,
+ CommandToggleDetails.Name: false,
+ CommandUpgradeDependency.Name: true,
+ CommandVendor.Name: true,
+ },
+ LinksInHover: true,
+ CompleteUnimported: true,
+ CompletionDocumentation: true,
+ DeepCompletion: true,
+ ImportShortcut: Both,
+ Matcher: Fuzzy,
+ SymbolMatcher: SymbolFuzzy,
+ SymbolStyle: PackageQualifiedSymbols,
+ },
+ InternalOptions: InternalOptions{
+ LiteralCompletions: true,
+ },
+ Hooks: Hooks{
+ ComputeEdits: myers.ComputeEdits,
+ URLRegexp: urlRegexp(),
+ DefaultAnalyzers: defaultAnalyzers(),
+ TypeErrorAnalyzers: typeErrorAnalyzers(),
+ ConvenienceAnalyzers: convenienceAnalyzers(),
+ StaticcheckAnalyzers: map[string]Analyzer{},
+ GoDiff: true,
+ },
+ }
+ })
+ return defaultOptions
+}
+
+// Options holds various configuration that affects Gopls execution, organized
+// by the nature or origin of the settings.
+type Options struct {
+ ClientOptions
+ ServerOptions
+ UserOptions
+ DebuggingOptions
+ ExperimentalOptions
+ InternalOptions
+ Hooks
+}
+
+// ClientOptions holds LSP-specific configuration that is provided by the
+// client.
+type ClientOptions struct {
+ InsertTextFormat protocol.InsertTextFormat
+ ConfigurationSupported bool
+ DynamicConfigurationSupported bool
+ DynamicWatchedFilesSupported bool
+ PreferredContentFormat protocol.MarkupKind
+ LineFoldingOnly bool
+ HierarchicalDocumentSymbolSupport bool
+}
+
+// ServerOptions holds LSP-specific configuration that is provided by the
+// server.
+type ServerOptions struct {
+ SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool
+ SupportedCommands []string
+}
+
+// UserOptions holds custom Gopls configuration (not part of the LSP) that is
+// modified by the client.
+type UserOptions struct {
+ // BuildFlags is the set of flags passed on to the build system when invoked.
+ // It is applied to queries like `go list`, which is used when discovering files.
+ // The most common use is to set `-tags`.
+ BuildFlags []string
+
+ // Env adds environment variables to external commands run by `gopls`, most notably `go list`.
+ Env map[string]string
+
+ // HoverKind controls the information that appears in the hover text.
+ // SingleLine and Structured are intended for use only by authors of editor plugins.
+ HoverKind HoverKind
+
+ // Placeholders enables placeholders for function parameters or struct fields in completion responses.
+ UsePlaceholders bool
+
+ // LinkTarget controls where documentation links go.
+ // It might be one of:
+ //
+ // * `"godoc.org"`
+ // * `"pkg.go.dev"`
+ //
+ // If company chooses to use its own `godoc.org`, its address can be used as well.
+ LinkTarget string
+
+ // Local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.
+ // It should be the prefix of the import path whose imports should be grouped separately.
+ Local string
+
+ // Gofumpt indicates if we should run gofumpt formatting.
+ Gofumpt bool
+}
+
+// EnvSlice returns Env as a slice of k=v strings.
+func (u *UserOptions) EnvSlice() []string {
+ var result []string
+ for k, v := range u.Env {
+ result = append(result, fmt.Sprintf("%v=%v", k, v))
+ }
+ return result
+}
+
+// SetEnvSlice sets Env from a slice of k=v strings.
+func (u *UserOptions) SetEnvSlice(env []string) {
+ u.Env = map[string]string{}
+ for _, kv := range env {
+ split := strings.SplitN(kv, "=", 2)
+ if len(split) != 2 {
+ continue
+ }
+ u.Env[split[0]] = split[1]
+ }
+}
+
+// Hooks contains configuration that is provided to the Gopls command by the
+// main package.
+type Hooks struct {
+ GoDiff bool
+ ComputeEdits diff.ComputeEdits
+ URLRegexp *regexp.Regexp
+ GofumptFormat func(ctx context.Context, src []byte) ([]byte, error)
+ DefaultAnalyzers map[string]Analyzer
+ TypeErrorAnalyzers map[string]Analyzer
+ ConvenienceAnalyzers map[string]Analyzer
+ StaticcheckAnalyzers map[string]Analyzer
+}
+
+// ExperimentalOptions defines configuration for features under active
+// development. WARNING: This configuration will be changed in the future. It
+// only exists while these features are under development.
+type ExperimentalOptions struct {
+ // Analyses specify analyses that the user would like to enable or disable.
+ // A map of the names of analysis passes that should be enabled/disabled.
+ // A full list of analyzers that gopls uses can be found [here](analyzers.md)
+ //
+ // Example Usage:
+ // ```json5
+ // ...
+ // "analyses": {
+ // "unreachable": false, // Disable the unreachable analyzer.
+ // "unusedparams": true // Enable the unusedparams analyzer.
+ // }
+ // ...
+ // ```
+ Analyses map[string]bool
+
+ // Codelens overrides the enabled/disabled state of code lenses. See the "Code Lenses"
+ // section of settings.md for the list of supported lenses.
+ //
+ // Example Usage:
+ // ```json5
+ // "gopls": {
+ // ...
+ // "codelens": {
+ // "generate": false, // Don't show the `go generate` lens.
+ // "gc_details": true // Show a code lens toggling the display of gc's choices.
+ // }
+ // ...
+ // }
+ // ```
+ Codelens map[string]bool
+
+ // CompletionDocumentation enables documentation with completion results.
+ CompletionDocumentation bool
+
+ // CompleteUnimported enables completion for packages that you do not currently import.
+ CompleteUnimported bool
+
+ // DeepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.
+ //
+ // Consider this example:
+ //
+ // ```go
+ // package main
+ //
+ // import "fmt"
+ //
+ // type wrapString struct {
+ // str string
+ // }
+ //
+ // func main() {
+ // x := wrapString{"hello world"}
+ // fmt.Printf(<>)
+ // }
+ // ```
+ //
+ // At the location of the `<>` in this program, deep completion would suggest the result `x.str`.
+ DeepCompletion bool
+
+ // Matcher sets the algorithm that is used when calculating completion candidates.
+ Matcher Matcher
+
+ // Annotations suppress various kinds of optimization diagnostics
+ // that would be reported by the gc_details command.
+ // * noNilcheck suppresses display of nilchecks.
+ // * noEscape suppresses escape choices.
+ // * noInline suppresses inlining choices.
+ // * noBounds suppresses bounds checking diagnostics.
+ Annotations map[string]bool
+
+ // Staticcheck enables additional analyses from staticcheck.io.
+ Staticcheck bool
+
+ // SymbolMatcher sets the algorithm that is used when finding workspace symbols.
+ SymbolMatcher SymbolMatcher
+
+ // SymbolStyle controls how symbols are qualified in symbol responses.
+ //
+ // Example Usage:
+ // ```json5
+ // "gopls": {
+ // ...
+ // "symbolStyle": "dynamic",
+ // ...
+ // }
+ // ```
+ SymbolStyle SymbolStyle
+
+ // LinksInHover toggles the presence of links to documentation in hover.
+ LinksInHover bool
+
+ // TempModfile controls the use of the -modfile flag in Go 1.14.
+ TempModfile bool
+
+ // ImportShortcut specifies whether import statements should link to
+ // documentation or go to definitions.
+ ImportShortcut ImportShortcut
+
+ // VerboseWorkDoneProgress controls whether the LSP server should send
+ // progress reports for all work done outside the scope of an RPC.
+ VerboseWorkDoneProgress bool
+
+ // SemanticTokens controls whether the LSP server will send
+ // semantic tokens to the client.
+ SemanticTokens bool
+
+ // ExpandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the
+ // modules containing the workspace folders. Set this to false to avoid loading
+ // your entire module. This is particularly useful for those working in a monorepo.
+ ExpandWorkspaceToModule bool
+
+ // ExperimentalWorkspaceModule opts a user into the experimental support
+ // for multi-module workspaces.
+ ExperimentalWorkspaceModule bool
+
+ // ExperimentalDiagnosticsDelay controls the amount of time that gopls waits
+ // after the most recent file modification before computing deep diagnostics.
+ // Simple diagnostics (parsing and type-checking) are always run immediately
+ // on recently modified packages.
+ //
+ // This option must be set to a valid duration string, for example `"250ms"`.
+ ExperimentalDiagnosticsDelay time.Duration
+
+ // ExperimentalPackageCacheKey controls whether to use a coarser cache key
+ // for package type information to increase cache hits. This setting removes
+ // the user's environment, build flags, and working directory from the cache
+ // key, which should be a safe change as all relevant inputs into the type
+ // checking pass are already hashed into the key. This is temporarily guarded
+ // by an experiment because caching behavior is subtle and difficult to
+ // comprehensively test.
+ ExperimentalPackageCacheKey bool
+}
+
+// DebuggingOptions should not affect the logical execution of Gopls, but may
+// be altered for debugging purposes.
+type DebuggingOptions struct {
+ // VerboseOutput enables additional debug logging.
+ VerboseOutput bool
+
+ // CompletionBudget is the soft latency goal for completion requests. Most
+ // requests finish in a couple milliseconds, but in some cases deep
+ // completions can take much longer. As we use up our budget we
+ // dynamically reduce the search scope to ensure we return timely
+ // results. Zero means unlimited.
+ CompletionBudget time.Duration
+}
+
+// InternalOptions contains settings that are not exposed to the user for various
+// reasons, e.g. settings used by tests.
+type InternalOptions struct {
+ // LiteralCompletions controls whether literal candidates such as
+ // "&someStruct{}" are offered. Tests disable this flag to simplify
+ // their expected values.
+ LiteralCompletions bool
+}
+
+type ImportShortcut string
+
+const (
+ Both ImportShortcut = "Both"
+ Link ImportShortcut = "Link"
+ Definition ImportShortcut = "Definition"
+)
+
+func (s ImportShortcut) ShowLinks() bool {
+ return s == Both || s == Link
+}
+
+func (s ImportShortcut) ShowDefinition() bool {
+ return s == Both || s == Definition
+}
+
+type Matcher string
+
+const (
+ Fuzzy Matcher = "Fuzzy"
+ CaseInsensitive Matcher = "CaseInsensitive"
+ CaseSensitive Matcher = "CaseSensitive"
+)
+
+type SymbolMatcher string
+
+const (
+ SymbolFuzzy SymbolMatcher = "Fuzzy"
+ SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive"
+ SymbolCaseSensitive SymbolMatcher = "CaseSensitive"
+)
+
+type SymbolStyle string
+
+const (
+ // PackageQualifiedSymbols is package qualified symbols i.e.
+ // "pkg.Foo.Field".
+ PackageQualifiedSymbols SymbolStyle = "Package"
+ // FullyQualifiedSymbols is fully qualified symbols, i.e.
+ // "path/to/pkg.Foo.Field".
+ FullyQualifiedSymbols SymbolStyle = "Full"
+ // DynamicSymbols uses whichever qualifier results in the highest scoring
+ // match for the given symbol query. Here a "qualifier" is any "/" or "."
+ // delimited suffix of the fully qualified symbol. i.e. "to/pkg.Foo.Field" or
+ // just "Foo.Field".
+ DynamicSymbols SymbolStyle = "Dynamic"
+)
+
+type HoverKind string
+
+const (
+ SingleLine HoverKind = "SingleLine"
+ NoDocumentation HoverKind = "NoDocumentation"
+ SynopsisDocumentation HoverKind = "SynopsisDocumentation"
+ FullDocumentation HoverKind = "FullDocumentation"
+
+ // Structured is an experimental setting that returns a structured hover format.
+ // This format separates the signature from the documentation, so that the client
+ // can do more manipulation of these fields.
+ //
+ // This should only be used by clients that support this behavior.
+ Structured HoverKind = "Structured"
+)
+
+type OptionResults []OptionResult
+
+type OptionResult struct {
+ Name string
+ Value interface{}
+ Error error
+
+ State OptionState
+ Replacement string
+}
+
+type OptionState int
+
+const (
+ OptionHandled = OptionState(iota)
+ OptionDeprecated
+ OptionUnexpected
+)
+
+type LinkTarget string
+
+func SetOptions(options *Options, opts interface{}) OptionResults {
+ var results OptionResults
+ switch opts := opts.(type) {
+ case nil:
+ case map[string]interface{}:
+ // If the user's settings contains "allExperiments", set that first,
+ // and then let them override individual settings independently.
+ var enableExperiments bool
+ for name, value := range opts {
+ if b, ok := value.(bool); name == "allExperiments" && ok && b {
+ enableExperiments = true
+ options.enableAllExperiments()
+ }
+ }
+ for name, value := range opts {
+ results = append(results, options.set(name, value))
+ }
+ // Finally, enable any experimental features that are specified in
+ // maps, which allows users to individually toggle them on or off.
+ if enableExperiments {
+ options.enableAllExperimentMaps()
+ }
+ default:
+ results = append(results, OptionResult{
+ Value: opts,
+ Error: errors.Errorf("Invalid options type %T", opts),
+ })
+ }
+ return results
+}
+
+func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) {
+ // Check if the client supports snippets in completion items.
+ if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport {
+ o.InsertTextFormat = protocol.SnippetTextFormat
+ }
+ // Check if the client supports configuration messages.
+ o.ConfigurationSupported = caps.Workspace.Configuration
+ o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
+ o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration
+
+ // Check which types of content format are supported by this client.
+ if hover := caps.TextDocument.Hover; len(hover.ContentFormat) > 0 {
+ o.PreferredContentFormat = hover.ContentFormat[0]
+ }
+ // Check if the client supports only line folding.
+ fr := caps.TextDocument.FoldingRange
+ o.LineFoldingOnly = fr.LineFoldingOnly
+ // Check if the client supports hierarchical document symbols.
+ o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
+}
+
+func (o *Options) Clone() *Options {
+ result := &Options{
+ ClientOptions: o.ClientOptions,
+ DebuggingOptions: o.DebuggingOptions,
+ ExperimentalOptions: o.ExperimentalOptions,
+ InternalOptions: o.InternalOptions,
+ Hooks: Hooks{
+ GoDiff: o.Hooks.GoDiff,
+ ComputeEdits: o.Hooks.ComputeEdits,
+ GofumptFormat: o.GofumptFormat,
+ URLRegexp: o.URLRegexp,
+ },
+ ServerOptions: o.ServerOptions,
+ UserOptions: o.UserOptions,
+ }
+ // Fully clone any slice or map fields. Only Hooks, ExperimentalOptions,
+ // and UserOptions can be modified.
+ copyStringMap := func(src map[string]bool) map[string]bool {
+ dst := make(map[string]bool)
+ for k, v := range src {
+ dst[k] = v
+ }
+ return dst
+ }
+ result.Analyses = copyStringMap(o.Analyses)
+ result.Annotations = copyStringMap(o.Annotations)
+ result.Codelens = copyStringMap(o.Codelens)
+
+ copySlice := func(src []string) []string {
+ dst := make([]string, len(src))
+ copy(dst, src)
+ return dst
+ }
+ result.SetEnvSlice(o.EnvSlice())
+ result.BuildFlags = copySlice(o.BuildFlags)
+
+ copyAnalyzerMap := func(src map[string]Analyzer) map[string]Analyzer {
+ dst := make(map[string]Analyzer)
+ for k, v := range src {
+ dst[k] = v
+ }
+ return dst
+ }
+ result.DefaultAnalyzers = copyAnalyzerMap(o.DefaultAnalyzers)
+ result.TypeErrorAnalyzers = copyAnalyzerMap(o.TypeErrorAnalyzers)
+ result.ConvenienceAnalyzers = copyAnalyzerMap(o.ConvenienceAnalyzers)
+ result.StaticcheckAnalyzers = copyAnalyzerMap(o.StaticcheckAnalyzers)
+ return result
+}
+
+func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer) {
+ o.StaticcheckAnalyzers[a.Name] = Analyzer{Analyzer: a, Enabled: true}
+}
+
+// enableAllExperiments turns on all of the experimental "off-by-default"
+// features offered by gopls.
+// Any experimental features specified in maps should be enabled in
+// enableAllExperimentMaps.
+func (o *Options) enableAllExperiments() {
+ o.ExperimentalDiagnosticsDelay = 200 * time.Millisecond
+ o.ExperimentalPackageCacheKey = true
+ o.SymbolStyle = DynamicSymbols
+}
+
+func (o *Options) enableAllExperimentMaps() {
+ if _, ok := o.Codelens[CommandToggleDetails.Name]; !ok {
+ o.Codelens[CommandToggleDetails.Name] = true
+ }
+ if _, ok := o.Analyses[unusedparams.Analyzer.Name]; !ok {
+ o.Analyses[unusedparams.Analyzer.Name] = true
+ }
+}
+
+func (o *Options) set(name string, value interface{}) OptionResult {
+ result := OptionResult{Name: name, Value: value}
+ switch name {
+ case "env":
+ menv, ok := value.(map[string]interface{})
+ if !ok {
+ result.errorf("invalid type %T, expect map", value)
+ break
+ }
+ for k, v := range menv {
+ o.Env[k] = fmt.Sprint(v)
+ }
+
+ case "buildFlags":
+ iflags, ok := value.([]interface{})
+ if !ok {
+ result.errorf("invalid type %T, expect list", value)
+ break
+ }
+ flags := make([]string, 0, len(iflags))
+ for _, flag := range iflags {
+ flags = append(flags, fmt.Sprintf("%s", flag))
+ }
+ o.BuildFlags = flags
+
+ case "completionDocumentation":
+ result.setBool(&o.CompletionDocumentation)
+ case "usePlaceholders":
+ result.setBool(&o.UsePlaceholders)
+ case "deepCompletion":
+ result.setBool(&o.DeepCompletion)
+ case "completeUnimported":
+ result.setBool(&o.CompleteUnimported)
+ case "completionBudget":
+ result.setDuration(&o.CompletionBudget)
+ case "matcher":
+ if s, ok := result.asOneOf(
+ string(Fuzzy),
+ string(CaseSensitive),
+ string(CaseInsensitive),
+ ); ok {
+ o.Matcher = Matcher(s)
+ }
+
+ case "symbolMatcher":
+ if s, ok := result.asOneOf(
+ string(SymbolFuzzy),
+ string(SymbolCaseInsensitive),
+ string(SymbolCaseSensitive),
+ ); ok {
+ o.SymbolMatcher = SymbolMatcher(s)
+ }
+
+ case "symbolStyle":
+ if s, ok := result.asOneOf(
+ string(FullyQualifiedSymbols),
+ string(PackageQualifiedSymbols),
+ string(DynamicSymbols),
+ ); ok {
+ o.SymbolStyle = SymbolStyle(s)
+ }
+
+ case "hoverKind":
+ if s, ok := result.asOneOf(
+ string(NoDocumentation),
+ string(SingleLine),
+ string(SynopsisDocumentation),
+ string(FullDocumentation),
+ string(Structured),
+ ); ok {
+ o.HoverKind = HoverKind(s)
+ }
+
+ case "linkTarget":
+ result.setString(&o.LinkTarget)
+
+ case "linksInHover":
+ result.setBool(&o.LinksInHover)
+
+ case "importShortcut":
+ if s, ok := result.asOneOf(string(Both), string(Link), string(Definition)); ok {
+ o.ImportShortcut = ImportShortcut(s)
+ }
+
+ case "analyses":
+ result.setBoolMap(&o.Analyses)
+
+ case "annotations":
+ result.setBoolMap(&o.Annotations)
+ for k := range o.Annotations {
+ switch k {
+ case "noEscape", "noNilcheck", "noInline", "noBounds":
+ continue
+ default:
+ result.Name += ":" + k // put mistake(s) in the message
+ result.State = OptionUnexpected
+ }
+ }
+
+ case "codelens":
+ var lensOverrides map[string]bool
+ result.setBoolMap(&lensOverrides)
+ if result.Error == nil {
+ if o.Codelens == nil {
+ o.Codelens = make(map[string]bool)
+ }
+ for lens, enabled := range lensOverrides {
+ o.Codelens[lens] = enabled
+ }
+ }
+
+ case "staticcheck":
+ result.setBool(&o.Staticcheck)
+
+ case "local":
+ result.setString(&o.Local)
+
+ case "verboseOutput":
+ result.setBool(&o.VerboseOutput)
+
+ case "verboseWorkDoneProgress":
+ result.setBool(&o.VerboseWorkDoneProgress)
+
+ case "tempModfile":
+ result.setBool(&o.TempModfile)
+
+ case "gofumpt":
+ result.setBool(&o.Gofumpt)
+
+ case "semanticTokens":
+ result.setBool(&o.SemanticTokens)
+
+ case "expandWorkspaceToModule":
+ result.setBool(&o.ExpandWorkspaceToModule)
+
+ case "experimentalWorkspaceModule":
+ result.setBool(&o.ExperimentalWorkspaceModule)
+
+ case "experimentalDiagnosticsDelay":
+ result.setDuration(&o.ExperimentalDiagnosticsDelay)
+
+ case "experimentalPackageCacheKey":
+ result.setBool(&o.ExperimentalPackageCacheKey)
+
+ case "allExperiments":
+ // This setting should be handled before all of the other options are
+ // processed, so do nothing here.
+
+ // Replaced settings.
+ case "experimentalDisabledAnalyses":
+ result.State = OptionDeprecated
+ result.Replacement = "analyses"
+
+ case "disableDeepCompletion":
+ result.State = OptionDeprecated
+ result.Replacement = "deepCompletion"
+
+ case "disableFuzzyMatching":
+ result.State = OptionDeprecated
+ result.Replacement = "fuzzyMatching"
+
+ case "wantCompletionDocumentation":
+ result.State = OptionDeprecated
+ result.Replacement = "completionDocumentation"
+
+ case "wantUnimportedCompletions":
+ result.State = OptionDeprecated
+ result.Replacement = "completeUnimported"
+
+ case "fuzzyMatching":
+ result.State = OptionDeprecated
+ result.Replacement = "matcher"
+
+ case "caseSensitiveCompletion":
+ result.State = OptionDeprecated
+ result.Replacement = "matcher"
+
+ // Deprecated settings.
+ case "wantSuggestedFixes":
+ result.State = OptionDeprecated
+
+ case "noIncrementalSync":
+ result.State = OptionDeprecated
+
+ case "watchFileChanges":
+ result.State = OptionDeprecated
+
+ case "go-diff":
+ result.State = OptionDeprecated
+
+ default:
+ result.State = OptionUnexpected
+ }
+ return result
+}
+
+func (r *OptionResult) errorf(msg string, values ...interface{}) {
+ prefix := fmt.Sprintf("parsing setting %q: ", r.Name)
+ r.Error = errors.Errorf(prefix+msg, values...)
+}
+
+func (r *OptionResult) asBool() (bool, bool) {
+ b, ok := r.Value.(bool)
+ if !ok {
+ r.errorf("invalid type %T, expect bool", r.Value)
+ return false, false
+ }
+ return b, true
+}
+
+func (r *OptionResult) setBool(b *bool) {
+ if v, ok := r.asBool(); ok {
+ *b = v
+ }
+}
+
+func (r *OptionResult) setDuration(d *time.Duration) {
+ if v, ok := r.asString(); ok {
+ parsed, err := time.ParseDuration(v)
+ if err != nil {
+ r.errorf("failed to parse duration %q: %v", v, err)
+ return
+ }
+ *d = parsed
+ }
+}
+
+func (r *OptionResult) setBoolMap(bm *map[string]bool) {
+ all, ok := r.Value.(map[string]interface{})
+ if !ok {
+ r.errorf("invalid type %T for map[string]bool option", r.Value)
+ return
+ }
+ m := make(map[string]bool)
+ for a, enabled := range all {
+ if enabled, ok := enabled.(bool); ok {
+ m[a] = enabled
+ } else {
+ r.errorf("invalid type %T for map key %q", enabled, a)
+ return
+ }
+ }
+ *bm = m
+}
+
+func (r *OptionResult) asString() (string, bool) {
+ b, ok := r.Value.(string)
+ if !ok {
+ r.errorf("invalid type %T, expect string", r.Value)
+ return "", false
+ }
+ return b, true
+}
+
+func (r *OptionResult) asOneOf(options ...string) (string, bool) {
+ s, ok := r.asString()
+ if !ok {
+ return "", false
+ }
+ lower := strings.ToLower(s)
+ for _, opt := range options {
+ if strings.ToLower(opt) == lower {
+ return opt, true
+ }
+ }
+ r.errorf("invalid option %q for enum", r.Value)
+ return "", false
+}
+
+func (r *OptionResult) setString(s *string) {
+ if v, ok := r.asString(); ok {
+ *s = v
+ }
+}
+
+// EnabledAnalyzers returns all of the analyzers enabled for the given
+// snapshot.
+func EnabledAnalyzers(snapshot Snapshot) (analyzers []Analyzer) {
+ for _, a := range snapshot.View().Options().DefaultAnalyzers {
+ if a.IsEnabled(snapshot.View()) {
+ analyzers = append(analyzers, a)
+ }
+ }
+ for _, a := range snapshot.View().Options().TypeErrorAnalyzers {
+ if a.IsEnabled(snapshot.View()) {
+ analyzers = append(analyzers, a)
+ }
+ }
+ for _, a := range snapshot.View().Options().ConvenienceAnalyzers {
+ if a.IsEnabled(snapshot.View()) {
+ analyzers = append(analyzers, a)
+ }
+ }
+ for _, a := range snapshot.View().Options().StaticcheckAnalyzers {
+ if a.IsEnabled(snapshot.View()) {
+ analyzers = append(analyzers, a)
+ }
+ }
+ return analyzers
+}
+
+func typeErrorAnalyzers() map[string]Analyzer {
+ return map[string]Analyzer{
+ fillreturns.Analyzer.Name: {
+ Analyzer: fillreturns.Analyzer,
+ FixesError: fillreturns.FixesError,
+ HighConfidence: true,
+ Enabled: true,
+ },
+ nonewvars.Analyzer.Name: {
+ Analyzer: nonewvars.Analyzer,
+ FixesError: nonewvars.FixesError,
+ Enabled: true,
+ },
+ noresultvalues.Analyzer.Name: {
+ Analyzer: noresultvalues.Analyzer,
+ FixesError: noresultvalues.FixesError,
+ Enabled: true,
+ },
+ undeclaredname.Analyzer.Name: {
+ Analyzer: undeclaredname.Analyzer,
+ FixesError: undeclaredname.FixesError,
+ Command: CommandUndeclaredName,
+ Enabled: true,
+ },
+ }
+}
+
+func convenienceAnalyzers() map[string]Analyzer {
+ return map[string]Analyzer{
+ fillstruct.Analyzer.Name: {
+ Analyzer: fillstruct.Analyzer,
+ Command: CommandFillStruct,
+ Enabled: true,
+ },
+ }
+}
+
+func defaultAnalyzers() map[string]Analyzer {
+ return map[string]Analyzer{
+ // The traditional vet suite:
+ asmdecl.Analyzer.Name: {Analyzer: asmdecl.Analyzer, Enabled: true},
+ assign.Analyzer.Name: {Analyzer: assign.Analyzer, Enabled: true},
+ atomic.Analyzer.Name: {Analyzer: atomic.Analyzer, Enabled: true},
+ atomicalign.Analyzer.Name: {Analyzer: atomicalign.Analyzer, Enabled: true},
+ bools.Analyzer.Name: {Analyzer: bools.Analyzer, Enabled: true},
+ buildtag.Analyzer.Name: {Analyzer: buildtag.Analyzer, Enabled: true},
+ cgocall.Analyzer.Name: {Analyzer: cgocall.Analyzer, Enabled: true},
+ composite.Analyzer.Name: {Analyzer: composite.Analyzer, Enabled: true},
+ copylock.Analyzer.Name: {Analyzer: copylock.Analyzer, Enabled: true},
+ errorsas.Analyzer.Name: {Analyzer: errorsas.Analyzer, Enabled: true},
+ httpresponse.Analyzer.Name: {Analyzer: httpresponse.Analyzer, Enabled: true},
+ loopclosure.Analyzer.Name: {Analyzer: loopclosure.Analyzer, Enabled: true},
+ lostcancel.Analyzer.Name: {Analyzer: lostcancel.Analyzer, Enabled: true},
+ nilfunc.Analyzer.Name: {Analyzer: nilfunc.Analyzer, Enabled: true},
+ printf.Analyzer.Name: {Analyzer: printf.Analyzer, Enabled: true},
+ shift.Analyzer.Name: {Analyzer: shift.Analyzer, Enabled: true},
+ stdmethods.Analyzer.Name: {Analyzer: stdmethods.Analyzer, Enabled: true},
+ structtag.Analyzer.Name: {Analyzer: structtag.Analyzer, Enabled: true},
+ tests.Analyzer.Name: {Analyzer: tests.Analyzer, Enabled: true},
+ unmarshal.Analyzer.Name: {Analyzer: unmarshal.Analyzer, Enabled: true},
+ unreachable.Analyzer.Name: {Analyzer: unreachable.Analyzer, Enabled: true},
+ unsafeptr.Analyzer.Name: {Analyzer: unsafeptr.Analyzer, Enabled: true},
+ unusedresult.Analyzer.Name: {Analyzer: unusedresult.Analyzer, Enabled: true},
+
+ // Non-vet analyzers:
+ deepequalerrors.Analyzer.Name: {Analyzer: deepequalerrors.Analyzer, Enabled: true},
+ sortslice.Analyzer.Name: {Analyzer: sortslice.Analyzer, Enabled: true},
+ testinggoroutine.Analyzer.Name: {Analyzer: testinggoroutine.Analyzer, Enabled: true},
+ unusedparams.Analyzer.Name: {Analyzer: unusedparams.Analyzer, Enabled: false},
+
+ // gofmt -s suite:
+ simplifycompositelit.Analyzer.Name: {Analyzer: simplifycompositelit.Analyzer, Enabled: true, HighConfidence: true},
+ simplifyrange.Analyzer.Name: {Analyzer: simplifyrange.Analyzer, Enabled: true, HighConfidence: true},
+ simplifyslice.Analyzer.Name: {Analyzer: simplifyslice.Analyzer, Enabled: true, HighConfidence: true},
+ }
+}
+
+func urlRegexp() *regexp.Regexp {
+ // Ensure links are matched as full words, not anywhere.
+ re := regexp.MustCompile(`\b(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?\b`)
+ re.Longest()
+ return re
+}
+
+type APIJSON struct {
+ Options map[string][]*OptionJSON
+ Commands []*CommandJSON
+ Lenses []*LensJSON
+}
+
+type OptionJSON struct {
+ Name string
+ Type string
+ Doc string
+ EnumValues []EnumValue
+ Default string
+}
+
+type EnumValue struct {
+ Value string
+ Doc string
+}
+
+type CommandJSON struct {
+ Command string
+ Title string
+ Doc string
+}
+
+type LensJSON struct {
+ Lens string
+ Title string
+ Doc string
+}