cuelang.org/go@v0.10.1/internal/golangorgx/gopls/settings/settings.go (about)

     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  
     5  package settings
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"path/filepath"
    11  	"regexp"
    12  	"runtime"
    13  	"strings"
    14  	"time"
    15  
    16  	"cuelang.org/go/internal/golangorgx/gopls/file"
    17  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    18  	"golang.org/x/tools/go/analysis"
    19  )
    20  
    21  type Annotation string
    22  
    23  const (
    24  	// Nil controls nil checks.
    25  	Nil Annotation = "nil"
    26  
    27  	// Escape controls diagnostics about escape choices.
    28  	Escape Annotation = "escape"
    29  
    30  	// Inline controls diagnostics about inlining choices.
    31  	Inline Annotation = "inline"
    32  
    33  	// Bounds controls bounds checking diagnostics.
    34  	Bounds Annotation = "bounds"
    35  )
    36  
    37  // Options holds various configuration that affects Gopls execution, organized
    38  // by the nature or origin of the settings.
    39  type Options struct {
    40  	ClientOptions
    41  	ServerOptions
    42  	UserOptions
    43  	InternalOptions
    44  	Hooks
    45  }
    46  
    47  // IsAnalyzerEnabled reports whether an analyzer with the given name is
    48  // enabled.
    49  //
    50  // TODO(rfindley): refactor to simplify this function. We no longer need the
    51  // different categories of analyzer.
    52  func (opts *Options) IsAnalyzerEnabled(name string) bool {
    53  	for _, amap := range []map[string]*Analyzer{opts.DefaultAnalyzers, opts.StaticcheckAnalyzers} {
    54  		for _, analyzer := range amap {
    55  			if analyzer.Analyzer.Name == name && analyzer.IsEnabled(opts) {
    56  				return true
    57  			}
    58  		}
    59  	}
    60  	return false
    61  }
    62  
    63  // ClientOptions holds LSP-specific configuration that is provided by the
    64  // client.
    65  type ClientOptions struct {
    66  	ClientInfo                                 *protocol.ClientInfo
    67  	InsertTextFormat                           protocol.InsertTextFormat
    68  	ConfigurationSupported                     bool
    69  	DynamicConfigurationSupported              bool
    70  	DynamicRegistrationSemanticTokensSupported bool
    71  	DynamicWatchedFilesSupported               bool
    72  	RelativePatternsSupported                  bool
    73  	PreferredContentFormat                     protocol.MarkupKind
    74  	LineFoldingOnly                            bool
    75  	HierarchicalDocumentSymbolSupport          bool
    76  	SemanticTypes                              []string
    77  	SemanticMods                               []string
    78  	RelatedInformationSupported                bool
    79  	CompletionTags                             bool
    80  	CompletionDeprecated                       bool
    81  	SupportedResourceOperations                []protocol.ResourceOperationKind
    82  	CodeActionResolveOptions                   []string
    83  }
    84  
    85  // ServerOptions holds LSP-specific configuration that is provided by the
    86  // server.
    87  type ServerOptions struct {
    88  	SupportedCodeActions map[file.Kind]map[protocol.CodeActionKind]bool
    89  	SupportedCommands    []string
    90  }
    91  
    92  type BuildOptions struct {
    93  	// BuildFlags is the set of flags passed on to the build system when invoked.
    94  	// It is applied to queries like `go list`, which is used when discovering files.
    95  	// The most common use is to set `-tags`.
    96  	BuildFlags []string
    97  
    98  	// Env adds environment variables to external commands run by `gopls`, most notably `go list`.
    99  	Env map[string]string
   100  
   101  	// DirectoryFilters can be used to exclude unwanted directories from the
   102  	// workspace. By default, all directories are included. Filters are an
   103  	// operator, `+` to include and `-` to exclude, followed by a path prefix
   104  	// relative to the workspace folder. They are evaluated in order, and
   105  	// the last filter that applies to a path controls whether it is included.
   106  	// The path prefix can be empty, so an initial `-` excludes everything.
   107  	//
   108  	// DirectoryFilters also supports the `**` operator to match 0 or more directories.
   109  	//
   110  	// Examples:
   111  	//
   112  	// Exclude node_modules at current depth: `-node_modules`
   113  	//
   114  	// Exclude node_modules at any depth: `-**/node_modules`
   115  	//
   116  	// Include only project_a: `-` (exclude everything), `+project_a`
   117  	//
   118  	// Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`
   119  	DirectoryFilters []string
   120  
   121  	// TemplateExtensions gives the extensions of file names that are treateed
   122  	// as template files. (The extension
   123  	// is the part of the file name after the final dot.)
   124  	TemplateExtensions []string
   125  
   126  	// obsolete, no effect
   127  	MemoryMode string `status:"experimental"`
   128  
   129  	// ExpandWorkspaceToModule determines which packages are considered
   130  	// "workspace packages" when the workspace is using modules.
   131  	//
   132  	// Workspace packages affect the scope of workspace-wide operations. Notably,
   133  	// gopls diagnoses all packages considered to be part of the workspace after
   134  	// every keystroke, so by setting "ExpandWorkspaceToModule" to false, and
   135  	// opening a nested workspace directory, you can reduce the amount of work
   136  	// gopls has to do to keep your workspace up to date.
   137  	ExpandWorkspaceToModule bool `status:"experimental"`
   138  
   139  	// AllowModfileModifications disables -mod=readonly, allowing imports from
   140  	// out-of-scope modules. This option will eventually be removed.
   141  	AllowModfileModifications bool `status:"experimental"`
   142  
   143  	// AllowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module
   144  	// downloads rather than requiring user action. This option will eventually
   145  	// be removed.
   146  	AllowImplicitNetworkAccess bool `status:"experimental"`
   147  
   148  	// StandaloneTags specifies a set of build constraints that identify
   149  	// individual Go source files that make up the entire main package of an
   150  	// executable.
   151  	//
   152  	// A common example of standalone main files is the convention of using the
   153  	// directive `//go:build ignore` to denote files that are not intended to be
   154  	// included in any package, for example because they are invoked directly by
   155  	// the developer using `go run`.
   156  	//
   157  	// Gopls considers a file to be a standalone main file if and only if it has
   158  	// package name "main" and has a build directive of the exact form
   159  	// "//go:build tag" or "// +build tag", where tag is among the list of tags
   160  	// configured by this setting. Notably, if the build constraint is more
   161  	// complicated than a simple tag (such as the composite constraint
   162  	// `//go:build tag && go1.18`), the file is not considered to be a standalone
   163  	// main file.
   164  	//
   165  	// This setting is only supported when gopls is built with Go 1.16 or later.
   166  	StandaloneTags []string
   167  }
   168  
   169  type UIOptions struct {
   170  	DocumentationOptions
   171  	CompletionOptions
   172  	NavigationOptions
   173  	DiagnosticOptions
   174  	InlayHintOptions
   175  
   176  	// Codelenses overrides the enabled/disabled state of code lenses. See the
   177  	// "Code Lenses" section of the
   178  	// [Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#code-lenses)
   179  	// for the list of supported lenses.
   180  	//
   181  	// Example Usage:
   182  	//
   183  	// ```json5
   184  	// "gopls": {
   185  	// ...
   186  	//   "codelenses": {
   187  	//     "generate": false,  // Don't show the `go generate` lens.
   188  	//     "gc_details": true  // Show a code lens toggling the display of gc's choices.
   189  	//   }
   190  	// ...
   191  	// }
   192  	// ```
   193  	Codelenses map[string]bool
   194  
   195  	// SemanticTokens controls whether the LSP server will send
   196  	// semantic tokens to the client.
   197  	SemanticTokens bool `status:"experimental"`
   198  
   199  	// NoSemanticString turns off the sending of the semantic token 'string'
   200  	NoSemanticString bool `status:"experimental"`
   201  
   202  	// NoSemanticNumber  turns off the sending of the semantic token 'number'
   203  	NoSemanticNumber bool `status:"experimental"`
   204  }
   205  
   206  type CompletionOptions struct {
   207  	// Placeholders enables placeholders for function parameters or struct
   208  	// fields in completion responses.
   209  	UsePlaceholders bool
   210  
   211  	// CompletionBudget is the soft latency goal for completion requests. Most
   212  	// requests finish in a couple milliseconds, but in some cases deep
   213  	// completions can take much longer. As we use up our budget we
   214  	// dynamically reduce the search scope to ensure we return timely
   215  	// results. Zero means unlimited.
   216  	CompletionBudget time.Duration `status:"debug"`
   217  
   218  	// Matcher sets the algorithm that is used when calculating completion
   219  	// candidates.
   220  	Matcher Matcher `status:"advanced"`
   221  
   222  	// ExperimentalPostfixCompletions enables artificial method snippets
   223  	// such as "someSlice.sort!".
   224  	ExperimentalPostfixCompletions bool `status:"experimental"`
   225  
   226  	// CompleteFunctionCalls enables function call completion.
   227  	//
   228  	// When completing a statement, or when a function return type matches the
   229  	// expected of the expression being completed, completion may suggest call
   230  	// expressions (i.e. may include parentheses).
   231  	CompleteFunctionCalls bool
   232  }
   233  
   234  type DocumentationOptions struct {
   235  	// HoverKind controls the information that appears in the hover text.
   236  	// SingleLine and Structured are intended for use only by authors of editor plugins.
   237  	HoverKind HoverKind
   238  
   239  	// LinkTarget controls where documentation links go.
   240  	// It might be one of:
   241  	//
   242  	// * `"godoc.org"`
   243  	// * `"pkg.go.dev"`
   244  	//
   245  	// If company chooses to use its own `godoc.org`, its address can be used as well.
   246  	//
   247  	// Modules matching the GOPRIVATE environment variable will not have
   248  	// documentation links in hover.
   249  	LinkTarget string
   250  
   251  	// LinksInHover toggles the presence of links to documentation in hover.
   252  	LinksInHover bool
   253  }
   254  
   255  type FormattingOptions struct {
   256  	// Local is the equivalent of the `goimports -local` flag, which puts
   257  	// imports beginning with this string after third-party packages. It should
   258  	// be the prefix of the import path whose imports should be grouped
   259  	// separately.
   260  	Local string
   261  
   262  	// Gofumpt indicates if we should run gofumpt formatting.
   263  	Gofumpt bool
   264  }
   265  
   266  type DiagnosticOptions struct {
   267  	// Analyses specify analyses that the user would like to enable or disable.
   268  	// A map of the names of analysis passes that should be enabled/disabled.
   269  	// A full list of analyzers that gopls uses can be found in
   270  	// [analyzers.md](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).
   271  	//
   272  	// Example Usage:
   273  	//
   274  	// ```json5
   275  	// ...
   276  	// "analyses": {
   277  	//   "unreachable": false, // Disable the unreachable analyzer.
   278  	//   "unusedvariable": true  // Enable the unusedvariable analyzer.
   279  	// }
   280  	// ...
   281  	// ```
   282  	Analyses map[string]bool
   283  
   284  	// Staticcheck enables additional analyses from staticcheck.io.
   285  	// These analyses are documented on
   286  	// [Staticcheck's website](https://staticcheck.io/docs/checks/).
   287  	Staticcheck bool `status:"experimental"`
   288  
   289  	// Annotations specifies the various kinds of optimization diagnostics
   290  	// that should be reported by the gc_details command.
   291  	Annotations map[Annotation]bool `status:"experimental"`
   292  
   293  	// DiagnosticsDelay controls the amount of time that gopls waits
   294  	// after the most recent file modification before computing deep diagnostics.
   295  	// Simple diagnostics (parsing and type-checking) are always run immediately
   296  	// on recently modified packages.
   297  	//
   298  	// This option must be set to a valid duration string, for example `"250ms"`.
   299  	DiagnosticsDelay time.Duration `status:"advanced"`
   300  
   301  	// DiagnosticsTrigger controls when to run diagnostics.
   302  	DiagnosticsTrigger DiagnosticsTrigger `status:"experimental"`
   303  
   304  	// AnalysisProgressReporting controls whether gopls sends progress
   305  	// notifications when construction of its index of analysis facts is taking a
   306  	// long time. Cancelling these notifications will cancel the indexing task,
   307  	// though it will restart after the next change in the workspace.
   308  	//
   309  	// When a package is opened for the first time and heavyweight analyses such as
   310  	// staticcheck are enabled, it can take a while to construct the index of
   311  	// analysis facts for all its dependencies. The index is cached in the
   312  	// filesystem, so subsequent analysis should be faster.
   313  	AnalysisProgressReporting bool
   314  }
   315  
   316  type InlayHintOptions struct {
   317  	// Hints specify inlay hints that users want to see. A full list of hints
   318  	// that gopls uses can be found in
   319  	// [inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).
   320  	Hints map[string]bool `status:"experimental"`
   321  }
   322  
   323  type NavigationOptions struct {
   324  	// ImportShortcut specifies whether import statements should link to
   325  	// documentation or go to definitions.
   326  	ImportShortcut ImportShortcut
   327  
   328  	// SymbolMatcher sets the algorithm that is used when finding workspace symbols.
   329  	SymbolMatcher SymbolMatcher `status:"advanced"`
   330  
   331  	// SymbolStyle controls how symbols are qualified in symbol responses.
   332  	//
   333  	// Example Usage:
   334  	//
   335  	// ```json5
   336  	// "gopls": {
   337  	// ...
   338  	//   "symbolStyle": "Dynamic",
   339  	// ...
   340  	// }
   341  	// ```
   342  	SymbolStyle SymbolStyle `status:"advanced"`
   343  
   344  	// SymbolScope controls which packages are searched for workspace/symbol
   345  	// requests. The default value, "workspace", searches only workspace
   346  	// packages. The legacy behavior, "all", causes all loaded packages to be
   347  	// searched, including dependencies; this is more expensive and may return
   348  	// unwanted results.
   349  	SymbolScope SymbolScope
   350  }
   351  
   352  // UserOptions holds custom Gopls configuration (not part of the LSP) that is
   353  // modified by the client.
   354  type UserOptions struct {
   355  	BuildOptions
   356  	UIOptions
   357  	FormattingOptions
   358  
   359  	// VerboseOutput enables additional debug logging.
   360  	VerboseOutput bool `status:"debug"`
   361  }
   362  
   363  // EnvSlice returns Env as a slice of k=v strings.
   364  func (u *UserOptions) EnvSlice() []string {
   365  	var result []string
   366  	for k, v := range u.Env {
   367  		result = append(result, fmt.Sprintf("%v=%v", k, v))
   368  	}
   369  	return result
   370  }
   371  
   372  // SetEnvSlice sets Env from a slice of k=v strings.
   373  func (u *UserOptions) SetEnvSlice(env []string) {
   374  	u.Env = map[string]string{}
   375  	for _, kv := range env {
   376  		split := strings.SplitN(kv, "=", 2)
   377  		if len(split) != 2 {
   378  			continue
   379  		}
   380  		u.Env[split[0]] = split[1]
   381  	}
   382  }
   383  
   384  // Hooks contains configuration that is provided to the Gopls command by the
   385  // main package.
   386  type Hooks struct {
   387  	// LicensesText holds third party licenses for software used by gopls.
   388  	LicensesText string
   389  
   390  	// Whether staticcheck is supported.
   391  	StaticcheckSupported bool
   392  
   393  	// URLRegexp is used to find potential URLs in comments/strings.
   394  	//
   395  	// Not all matches are shown to the user: if the matched URL is not detected
   396  	// as valid, it will be skipped.
   397  	URLRegexp *regexp.Regexp
   398  
   399  	// GofumptFormat allows the gopls module to wire-in a call to
   400  	// gofumpt/format.Source. langVersion and modulePath are used for some
   401  	// Gofumpt formatting rules -- see the Gofumpt documentation for details.
   402  	GofumptFormat func(ctx context.Context, langVersion, modulePath string, src []byte) ([]byte, error)
   403  
   404  	DefaultAnalyzers     map[string]*Analyzer
   405  	StaticcheckAnalyzers map[string]*Analyzer
   406  }
   407  
   408  // InternalOptions contains settings that are not intended for use by the
   409  // average user. These may be settings used by tests or outdated settings that
   410  // will soon be deprecated. Some of these settings may not even be configurable
   411  // by the user.
   412  //
   413  // TODO(rfindley): even though these settings are not intended for
   414  // modification, some of them should be surfaced in our documentation.
   415  type InternalOptions struct {
   416  	// VerboseWorkDoneProgress controls whether the LSP server should send
   417  	// progress reports for all work done outside the scope of an RPC.
   418  	// Used by the regression tests.
   419  	VerboseWorkDoneProgress bool
   420  
   421  	// The following options were previously available to users, but they
   422  	// really shouldn't be configured by anyone other than "power users".
   423  
   424  	// CompletionDocumentation enables documentation with completion results.
   425  	CompletionDocumentation bool
   426  
   427  	// CompleteUnimported enables completion for packages that you do not
   428  	// currently import.
   429  	CompleteUnimported bool
   430  
   431  	// DeepCompletion enables the ability to return completions from deep
   432  	// inside relevant entities, rather than just the locally accessible ones.
   433  	//
   434  	// Consider this example:
   435  	//
   436  	// ```go
   437  	// package main
   438  	//
   439  	// import "fmt"
   440  	//
   441  	// type wrapString struct {
   442  	//     str string
   443  	// }
   444  	//
   445  	// func main() {
   446  	//     x := wrapString{"hello world"}
   447  	//     fmt.Printf(<>)
   448  	// }
   449  	// ```
   450  	//
   451  	// At the location of the `<>` in this program, deep completion would suggest
   452  	// the result `x.str`.
   453  	DeepCompletion bool
   454  
   455  	// ShowBugReports causes a message to be shown when the first bug is reported
   456  	// on the server.
   457  	// This option applies only during initialization.
   458  	ShowBugReports bool
   459  
   460  	// SubdirWatchPatterns configures the file watching glob patterns registered
   461  	// by gopls.
   462  	//
   463  	// Some clients (namely VS Code) do not send workspace/didChangeWatchedFile
   464  	// notifications for files contained in a directory when that directory is
   465  	// deleted:
   466  	// https://github.com/microsoft/vscode/issues/109754
   467  	//
   468  	// In this case, gopls would miss important notifications about deleted
   469  	// packages. To work around this, gopls registers a watch pattern for each
   470  	// directory containing Go files.
   471  	//
   472  	// Unfortunately, other clients experience performance problems with this
   473  	// many watch patterns, so there is no single behavior that works well for
   474  	// all clients.
   475  	//
   476  	// The "subdirWatchPatterns" setting allows configuring this behavior. Its
   477  	// default value of "auto" attempts to guess the correct behavior based on
   478  	// the client name. We'd love to avoid this specialization, but as described
   479  	// above there is no single value that works for all clients.
   480  	//
   481  	// If any LSP client does not behave well with the default value (for
   482  	// example, if like VS Code it drops file notifications), please file an
   483  	// issue.
   484  	SubdirWatchPatterns SubdirWatchPatterns
   485  
   486  	// ReportAnalysisProgressAfter sets the duration for gopls to wait before starting
   487  	// progress reporting for ongoing go/analysis passes.
   488  	//
   489  	// It is intended to be used for testing only.
   490  	ReportAnalysisProgressAfter time.Duration
   491  
   492  	// TelemetryPrompt controls whether gopls prompts about enabling Go telemetry.
   493  	//
   494  	// Once the prompt is answered, gopls doesn't ask again, but TelemetryPrompt
   495  	// can prevent the question from ever being asked in the first place.
   496  	TelemetryPrompt bool
   497  
   498  	// LinkifyShowMessage controls whether the client wants gopls
   499  	// to linkify links in showMessage. e.g. [go.dev](https://go.dev).
   500  	LinkifyShowMessage bool
   501  
   502  	// IncludeReplaceInWorkspace controls whether locally replaced modules in a
   503  	// go.mod file are treated like workspace modules.
   504  	// Or in other words, if a go.mod file with local replaces behaves like a
   505  	// go.work file.
   506  	IncludeReplaceInWorkspace bool
   507  
   508  	// ZeroConfig enables the zero-config algorithm for workspace layout,
   509  	// dynamically creating build configurations for different modules,
   510  	// directories, and GOOS/GOARCH combinations to cover open files.
   511  	ZeroConfig bool
   512  }
   513  
   514  type SubdirWatchPatterns string
   515  
   516  const (
   517  	SubdirWatchPatternsOn   SubdirWatchPatterns = "on"
   518  	SubdirWatchPatternsOff  SubdirWatchPatterns = "off"
   519  	SubdirWatchPatternsAuto SubdirWatchPatterns = "auto"
   520  )
   521  
   522  type ImportShortcut string
   523  
   524  const (
   525  	BothShortcuts      ImportShortcut = "Both"
   526  	LinkShortcut       ImportShortcut = "Link"
   527  	DefinitionShortcut ImportShortcut = "Definition"
   528  )
   529  
   530  func (s ImportShortcut) ShowLinks() bool {
   531  	return s == BothShortcuts || s == LinkShortcut
   532  }
   533  
   534  func (s ImportShortcut) ShowDefinition() bool {
   535  	return s == BothShortcuts || s == DefinitionShortcut
   536  }
   537  
   538  type Matcher string
   539  
   540  const (
   541  	Fuzzy           Matcher = "Fuzzy"
   542  	CaseInsensitive Matcher = "CaseInsensitive"
   543  	CaseSensitive   Matcher = "CaseSensitive"
   544  )
   545  
   546  // A SymbolMatcher controls the matching of symbols for workspace/symbol
   547  // requests.
   548  type SymbolMatcher string
   549  
   550  const (
   551  	SymbolFuzzy           SymbolMatcher = "Fuzzy"
   552  	SymbolFastFuzzy       SymbolMatcher = "FastFuzzy"
   553  	SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive"
   554  	SymbolCaseSensitive   SymbolMatcher = "CaseSensitive"
   555  )
   556  
   557  // A SymbolStyle controls the formatting of symbols in workspace/symbol results.
   558  type SymbolStyle string
   559  
   560  const (
   561  	// PackageQualifiedSymbols is package qualified symbols i.e.
   562  	// "pkg.Foo.Field".
   563  	PackageQualifiedSymbols SymbolStyle = "Package"
   564  	// FullyQualifiedSymbols is fully qualified symbols, i.e.
   565  	// "path/to/pkg.Foo.Field".
   566  	FullyQualifiedSymbols SymbolStyle = "Full"
   567  	// DynamicSymbols uses whichever qualifier results in the highest scoring
   568  	// match for the given symbol query. Here a "qualifier" is any "/" or "."
   569  	// delimited suffix of the fully qualified symbol. i.e. "to/pkg.Foo.Field" or
   570  	// just "Foo.Field".
   571  	DynamicSymbols SymbolStyle = "Dynamic"
   572  )
   573  
   574  // A SymbolScope controls the search scope for workspace/symbol requests.
   575  type SymbolScope string
   576  
   577  const (
   578  	// WorkspaceSymbolScope matches symbols in workspace packages only.
   579  	WorkspaceSymbolScope SymbolScope = "workspace"
   580  	// AllSymbolScope matches symbols in any loaded package, including
   581  	// dependencies.
   582  	AllSymbolScope SymbolScope = "all"
   583  )
   584  
   585  type HoverKind string
   586  
   587  const (
   588  	SingleLine            HoverKind = "SingleLine"
   589  	NoDocumentation       HoverKind = "NoDocumentation"
   590  	SynopsisDocumentation HoverKind = "SynopsisDocumentation"
   591  	FullDocumentation     HoverKind = "FullDocumentation"
   592  
   593  	// Structured is an experimental setting that returns a structured hover format.
   594  	// This format separates the signature from the documentation, so that the client
   595  	// can do more manipulation of these fields.
   596  	//
   597  	// This should only be used by clients that support this behavior.
   598  	Structured HoverKind = "Structured"
   599  )
   600  
   601  type DiagnosticsTrigger string
   602  
   603  const (
   604  	// Trigger diagnostics on file edit and save. (default)
   605  	DiagnosticsOnEdit DiagnosticsTrigger = "Edit"
   606  	// Trigger diagnostics only on file save. Events like initial workspace load
   607  	// or configuration change will still trigger diagnostics.
   608  	DiagnosticsOnSave DiagnosticsTrigger = "Save"
   609  	// TODO: support "Manual"?
   610  )
   611  
   612  type OptionResults []OptionResult
   613  
   614  type OptionResult struct {
   615  	Name  string
   616  	Value any
   617  	Error error
   618  }
   619  
   620  func SetOptions(options *Options, opts any) OptionResults {
   621  	var results OptionResults
   622  	switch opts := opts.(type) {
   623  	case nil:
   624  	case map[string]any:
   625  		// If the user's settings contains "allExperiments", set that first,
   626  		// and then let them override individual settings independently.
   627  		var enableExperiments bool
   628  		for name, value := range opts {
   629  			if b, ok := value.(bool); name == "allExperiments" && ok && b {
   630  				enableExperiments = true
   631  				options.EnableAllExperiments()
   632  			}
   633  		}
   634  		seen := map[string]struct{}{}
   635  		for name, value := range opts {
   636  			results = append(results, options.set(name, value, seen))
   637  		}
   638  		// Finally, enable any experimental features that are specified in
   639  		// maps, which allows users to individually toggle them on or off.
   640  		if enableExperiments {
   641  			options.enableAllExperimentMaps()
   642  		}
   643  	default:
   644  		results = append(results, OptionResult{
   645  			Value: opts,
   646  			Error: fmt.Errorf("Invalid options type %T", opts),
   647  		})
   648  	}
   649  	return results
   650  }
   651  
   652  func (o *Options) ForClientCapabilities(clientName *protocol.ClientInfo, caps protocol.ClientCapabilities) {
   653  	o.ClientInfo = clientName
   654  	// Check if the client supports snippets in completion items.
   655  	if caps.Workspace.WorkspaceEdit != nil {
   656  		o.SupportedResourceOperations = caps.Workspace.WorkspaceEdit.ResourceOperations
   657  	}
   658  	if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport {
   659  		o.InsertTextFormat = protocol.SnippetTextFormat
   660  	}
   661  	// Check if the client supports configuration messages.
   662  	o.ConfigurationSupported = caps.Workspace.Configuration
   663  	o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
   664  	o.DynamicRegistrationSemanticTokensSupported = caps.TextDocument.SemanticTokens.DynamicRegistration
   665  	o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration
   666  	o.RelativePatternsSupported = caps.Workspace.DidChangeWatchedFiles.RelativePatternSupport
   667  
   668  	// Check which types of content format are supported by this client.
   669  	if hover := caps.TextDocument.Hover; hover != nil && len(hover.ContentFormat) > 0 {
   670  		o.PreferredContentFormat = hover.ContentFormat[0]
   671  	}
   672  	// Check if the client supports only line folding.
   673  
   674  	if fr := caps.TextDocument.FoldingRange; fr != nil {
   675  		o.LineFoldingOnly = fr.LineFoldingOnly
   676  	}
   677  	// Check if the client supports hierarchical document symbols.
   678  	o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
   679  
   680  	// Client's semantic tokens
   681  	o.SemanticTypes = caps.TextDocument.SemanticTokens.TokenTypes
   682  	o.SemanticMods = caps.TextDocument.SemanticTokens.TokenModifiers
   683  	// we don't need Requests, as we support full functionality
   684  	// we don't need Formats, as there is only one, for now
   685  
   686  	// Check if the client supports diagnostic related information.
   687  	o.RelatedInformationSupported = caps.TextDocument.PublishDiagnostics.RelatedInformation
   688  	// Check if the client completion support includes tags (preferred) or deprecation
   689  	if caps.TextDocument.Completion.CompletionItem.TagSupport != nil &&
   690  		caps.TextDocument.Completion.CompletionItem.TagSupport.ValueSet != nil {
   691  		o.CompletionTags = true
   692  	} else if caps.TextDocument.Completion.CompletionItem.DeprecatedSupport {
   693  		o.CompletionDeprecated = true
   694  	}
   695  
   696  	// Check if the client supports code actions resolving.
   697  	if caps.TextDocument.CodeAction.DataSupport && caps.TextDocument.CodeAction.ResolveSupport != nil {
   698  		o.CodeActionResolveOptions = caps.TextDocument.CodeAction.ResolveSupport.Properties
   699  	}
   700  }
   701  
   702  func (o *Options) Clone() *Options {
   703  	// TODO(rfindley): has this function gone stale? It appears that there are
   704  	// settings that are incorrectly cloned here (such as TemplateExtensions).
   705  	result := &Options{
   706  		ClientOptions:   o.ClientOptions,
   707  		InternalOptions: o.InternalOptions,
   708  		Hooks: Hooks{
   709  			StaticcheckSupported: o.StaticcheckSupported,
   710  			GofumptFormat:        o.GofumptFormat,
   711  			URLRegexp:            o.URLRegexp,
   712  		},
   713  		ServerOptions: o.ServerOptions,
   714  		UserOptions:   o.UserOptions,
   715  	}
   716  	// Fully clone any slice or map fields. Only Hooks, ExperimentalOptions,
   717  	// and UserOptions can be modified.
   718  	copyStringMap := func(src map[string]bool) map[string]bool {
   719  		dst := make(map[string]bool)
   720  		for k, v := range src {
   721  			dst[k] = v
   722  		}
   723  		return dst
   724  	}
   725  	result.Analyses = copyStringMap(o.Analyses)
   726  	result.Codelenses = copyStringMap(o.Codelenses)
   727  
   728  	copySlice := func(src []string) []string {
   729  		dst := make([]string, len(src))
   730  		copy(dst, src)
   731  		return dst
   732  	}
   733  	result.SetEnvSlice(o.EnvSlice())
   734  	result.BuildFlags = copySlice(o.BuildFlags)
   735  	result.DirectoryFilters = copySlice(o.DirectoryFilters)
   736  	result.StandaloneTags = copySlice(o.StandaloneTags)
   737  
   738  	copyAnalyzerMap := func(src map[string]*Analyzer) map[string]*Analyzer {
   739  		dst := make(map[string]*Analyzer)
   740  		for k, v := range src {
   741  			dst[k] = v
   742  		}
   743  		return dst
   744  	}
   745  	result.DefaultAnalyzers = copyAnalyzerMap(o.DefaultAnalyzers)
   746  	result.StaticcheckAnalyzers = copyAnalyzerMap(o.StaticcheckAnalyzers)
   747  	return result
   748  }
   749  
   750  func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer, enabled bool, severity protocol.DiagnosticSeverity) {
   751  	o.StaticcheckAnalyzers[a.Name] = &Analyzer{
   752  		Analyzer: a,
   753  		Enabled:  enabled,
   754  		Severity: severity,
   755  	}
   756  }
   757  
   758  // EnableAllExperiments turns on all of the experimental "off-by-default"
   759  // features offered by gopls. Any experimental features specified in maps
   760  // should be enabled in enableAllExperimentMaps.
   761  func (o *Options) EnableAllExperiments() {
   762  	o.SemanticTokens = true
   763  }
   764  
   765  func (o *Options) enableAllExperimentMaps() {
   766  }
   767  
   768  // validateDirectoryFilter validates if the filter string
   769  // - is not empty
   770  // - start with either + or -
   771  // - doesn't contain currently unsupported glob operators: *, ?
   772  func validateDirectoryFilter(ifilter string) (string, error) {
   773  	filter := fmt.Sprint(ifilter)
   774  	if filter == "" || (filter[0] != '+' && filter[0] != '-') {
   775  		return "", fmt.Errorf("invalid filter %v, must start with + or -", filter)
   776  	}
   777  	segs := strings.Split(filter[1:], "/")
   778  	unsupportedOps := [...]string{"?", "*"}
   779  	for _, seg := range segs {
   780  		if seg != "**" {
   781  			for _, op := range unsupportedOps {
   782  				if strings.Contains(seg, op) {
   783  					return "", fmt.Errorf("invalid filter %v, operator %v not supported. If you want to have this operator supported, consider filing an issue.", filter, op)
   784  				}
   785  			}
   786  		}
   787  	}
   788  
   789  	return strings.TrimRight(filepath.FromSlash(filter), "/"), nil
   790  }
   791  
   792  func (o *Options) set(name string, value interface{}, seen map[string]struct{}) OptionResult {
   793  	// Flatten the name in case we get options with a hierarchy.
   794  	split := strings.Split(name, ".")
   795  	name = split[len(split)-1]
   796  
   797  	result := OptionResult{Name: name, Value: value}
   798  	if _, ok := seen[name]; ok {
   799  		result.parseErrorf("duplicate configuration for %s", name)
   800  	}
   801  	seen[name] = struct{}{}
   802  
   803  	switch name {
   804  	case "env":
   805  		menv, ok := value.(map[string]interface{})
   806  		if !ok {
   807  			result.parseErrorf("invalid type %T, expect map", value)
   808  			break
   809  		}
   810  		if o.Env == nil {
   811  			o.Env = make(map[string]string)
   812  		}
   813  		for k, v := range menv {
   814  			o.Env[k] = fmt.Sprint(v)
   815  		}
   816  
   817  	case "buildFlags":
   818  		// TODO(rfindley): use asStringSlice.
   819  		iflags, ok := value.([]interface{})
   820  		if !ok {
   821  			result.parseErrorf("invalid type %T, expect list", value)
   822  			break
   823  		}
   824  		flags := make([]string, 0, len(iflags))
   825  		for _, flag := range iflags {
   826  			flags = append(flags, fmt.Sprintf("%s", flag))
   827  		}
   828  		o.BuildFlags = flags
   829  
   830  	case "directoryFilters":
   831  		// TODO(rfindley): use asStringSlice.
   832  		ifilters, ok := value.([]interface{})
   833  		if !ok {
   834  			result.parseErrorf("invalid type %T, expect list", value)
   835  			break
   836  		}
   837  		var filters []string
   838  		for _, ifilter := range ifilters {
   839  			filter, err := validateDirectoryFilter(fmt.Sprintf("%v", ifilter))
   840  			if err != nil {
   841  				result.parseErrorf("%v", err)
   842  				return result
   843  			}
   844  			filters = append(filters, strings.TrimRight(filepath.FromSlash(filter), "/"))
   845  		}
   846  		o.DirectoryFilters = filters
   847  
   848  	case "memoryMode":
   849  		result.deprecated("")
   850  	case "completionDocumentation":
   851  		result.setBool(&o.CompletionDocumentation)
   852  	case "usePlaceholders":
   853  		result.setBool(&o.UsePlaceholders)
   854  	case "deepCompletion":
   855  		result.setBool(&o.DeepCompletion)
   856  	case "completeUnimported":
   857  		result.setBool(&o.CompleteUnimported)
   858  	case "completionBudget":
   859  		result.setDuration(&o.CompletionBudget)
   860  	case "matcher":
   861  		if s, ok := result.asOneOf(
   862  			string(Fuzzy),
   863  			string(CaseSensitive),
   864  			string(CaseInsensitive),
   865  		); ok {
   866  			o.Matcher = Matcher(s)
   867  		}
   868  
   869  	case "symbolMatcher":
   870  		if s, ok := result.asOneOf(
   871  			string(SymbolFuzzy),
   872  			string(SymbolFastFuzzy),
   873  			string(SymbolCaseInsensitive),
   874  			string(SymbolCaseSensitive),
   875  		); ok {
   876  			o.SymbolMatcher = SymbolMatcher(s)
   877  		}
   878  
   879  	case "symbolStyle":
   880  		if s, ok := result.asOneOf(
   881  			string(FullyQualifiedSymbols),
   882  			string(PackageQualifiedSymbols),
   883  			string(DynamicSymbols),
   884  		); ok {
   885  			o.SymbolStyle = SymbolStyle(s)
   886  		}
   887  
   888  	case "symbolScope":
   889  		if s, ok := result.asOneOf(
   890  			string(WorkspaceSymbolScope),
   891  			string(AllSymbolScope),
   892  		); ok {
   893  			o.SymbolScope = SymbolScope(s)
   894  		}
   895  
   896  	case "hoverKind":
   897  		if s, ok := result.asOneOf(
   898  			string(NoDocumentation),
   899  			string(SingleLine),
   900  			string(SynopsisDocumentation),
   901  			string(FullDocumentation),
   902  			string(Structured),
   903  		); ok {
   904  			o.HoverKind = HoverKind(s)
   905  		}
   906  
   907  	case "linkTarget":
   908  		result.setString(&o.LinkTarget)
   909  
   910  	case "linksInHover":
   911  		result.setBool(&o.LinksInHover)
   912  
   913  	case "importShortcut":
   914  		if s, ok := result.asOneOf(string(BothShortcuts), string(LinkShortcut), string(DefinitionShortcut)); ok {
   915  			o.ImportShortcut = ImportShortcut(s)
   916  		}
   917  
   918  	case "analyses":
   919  		result.setBoolMap(&o.Analyses)
   920  
   921  	case "hints":
   922  		result.setBoolMap(&o.Hints)
   923  
   924  	case "annotations":
   925  		result.setAnnotationMap(&o.Annotations)
   926  
   927  	case "codelenses", "codelens":
   928  		var lensOverrides map[string]bool
   929  		result.setBoolMap(&lensOverrides)
   930  		if result.Error == nil {
   931  			if o.Codelenses == nil {
   932  				o.Codelenses = make(map[string]bool)
   933  			}
   934  			for lens, enabled := range lensOverrides {
   935  				o.Codelenses[lens] = enabled
   936  			}
   937  		}
   938  
   939  		// codelens is deprecated, but still works for now.
   940  		// TODO(rstambler): Remove this for the gopls/v0.7.0 release.
   941  		if name == "codelens" {
   942  			result.deprecated("codelenses")
   943  		}
   944  
   945  	case "staticcheck":
   946  		if v, ok := result.asBool(); ok {
   947  			o.Staticcheck = v
   948  			if v && !o.StaticcheckSupported {
   949  				result.Error = fmt.Errorf("applying setting %q: staticcheck is not supported at %s;"+
   950  					" rebuild gopls with a more recent version of Go", result.Name, runtime.Version())
   951  			}
   952  		}
   953  
   954  	case "local":
   955  		result.setString(&o.Local)
   956  
   957  	case "verboseOutput":
   958  		result.setBool(&o.VerboseOutput)
   959  
   960  	case "verboseWorkDoneProgress":
   961  		result.setBool(&o.VerboseWorkDoneProgress)
   962  
   963  	case "tempModFile":
   964  		result.deprecated("")
   965  
   966  	case "showBugReports":
   967  		result.setBool(&o.ShowBugReports)
   968  
   969  	case "gofumpt":
   970  		if v, ok := result.asBool(); ok {
   971  			o.Gofumpt = v
   972  			if v && o.GofumptFormat == nil {
   973  				result.Error = fmt.Errorf("applying setting %q: gofumpt is not supported at %s;"+
   974  					" rebuild gopls with a more recent version of Go", result.Name, runtime.Version())
   975  			}
   976  		}
   977  	case "completeFunctionCalls":
   978  		result.setBool(&o.CompleteFunctionCalls)
   979  
   980  	case "semanticTokens":
   981  		result.setBool(&o.SemanticTokens)
   982  
   983  	case "noSemanticString":
   984  		result.setBool(&o.NoSemanticString)
   985  
   986  	case "noSemanticNumber":
   987  		result.setBool(&o.NoSemanticNumber)
   988  
   989  	case "expandWorkspaceToModule":
   990  		// See golang/go#63536: we can consider deprecating
   991  		// expandWorkspaceToModule, but probably need to change the default
   992  		// behavior in that case to *not* expand to the module.
   993  		result.setBool(&o.ExpandWorkspaceToModule)
   994  
   995  	case "experimentalPostfixCompletions":
   996  		result.setBool(&o.ExperimentalPostfixCompletions)
   997  
   998  	case "experimentalWorkspaceModule":
   999  		result.deprecated("")
  1000  
  1001  	case "experimentalTemplateSupport": // TODO(pjw): remove after June 2022
  1002  		result.deprecated("")
  1003  
  1004  	case "templateExtensions":
  1005  		if iexts, ok := value.([]interface{}); ok {
  1006  			ans := []string{}
  1007  			for _, x := range iexts {
  1008  				ans = append(ans, fmt.Sprint(x))
  1009  			}
  1010  			o.TemplateExtensions = ans
  1011  			break
  1012  		}
  1013  		if value == nil {
  1014  			o.TemplateExtensions = nil
  1015  			break
  1016  		}
  1017  		result.parseErrorf("unexpected type %T not []string", value)
  1018  
  1019  	case "experimentalDiagnosticsDelay":
  1020  		result.deprecated("diagnosticsDelay")
  1021  
  1022  	case "diagnosticsDelay":
  1023  		result.setDuration(&o.DiagnosticsDelay)
  1024  
  1025  	case "diagnosticsTrigger":
  1026  		if s, ok := result.asOneOf(
  1027  			string(DiagnosticsOnEdit),
  1028  			string(DiagnosticsOnSave),
  1029  		); ok {
  1030  			o.DiagnosticsTrigger = DiagnosticsTrigger(s)
  1031  		}
  1032  
  1033  	case "analysisProgressReporting":
  1034  		result.setBool(&o.AnalysisProgressReporting)
  1035  
  1036  	case "experimentalWatchedFileDelay":
  1037  		result.deprecated("")
  1038  
  1039  	case "experimentalPackageCacheKey":
  1040  		result.deprecated("")
  1041  
  1042  	case "allowModfileModifications":
  1043  		result.softErrorf("gopls setting \"allowModfileModifications\" is deprecated.\nPlease comment on https://go.dev/issue/65546 if this impacts your workflow.")
  1044  		result.setBool(&o.AllowModfileModifications)
  1045  
  1046  	case "allowImplicitNetworkAccess":
  1047  		result.setBool(&o.AllowImplicitNetworkAccess)
  1048  
  1049  	case "experimentalUseInvalidMetadata":
  1050  		result.deprecated("")
  1051  
  1052  	case "standaloneTags":
  1053  		result.setStringSlice(&o.StandaloneTags)
  1054  
  1055  	case "allExperiments":
  1056  		// This setting should be handled before all of the other options are
  1057  		// processed, so do nothing here.
  1058  
  1059  	case "newDiff":
  1060  		result.deprecated("")
  1061  
  1062  	case "subdirWatchPatterns":
  1063  		if s, ok := result.asOneOf(
  1064  			string(SubdirWatchPatternsOn),
  1065  			string(SubdirWatchPatternsOff),
  1066  			string(SubdirWatchPatternsAuto),
  1067  		); ok {
  1068  			o.SubdirWatchPatterns = SubdirWatchPatterns(s)
  1069  		}
  1070  
  1071  	case "reportAnalysisProgressAfter":
  1072  		result.setDuration(&o.ReportAnalysisProgressAfter)
  1073  
  1074  	case "telemetryPrompt":
  1075  		result.setBool(&o.TelemetryPrompt)
  1076  
  1077  	case "linkifyShowMessage":
  1078  		result.setBool(&o.LinkifyShowMessage)
  1079  
  1080  	case "includeReplaceInWorkspace":
  1081  		result.setBool(&o.IncludeReplaceInWorkspace)
  1082  
  1083  	case "zeroConfig":
  1084  		result.setBool(&o.ZeroConfig)
  1085  
  1086  	// Replaced settings.
  1087  	case "experimentalDisabledAnalyses":
  1088  		result.deprecated("analyses")
  1089  
  1090  	case "disableDeepCompletion":
  1091  		result.deprecated("deepCompletion")
  1092  
  1093  	case "disableFuzzyMatching":
  1094  		result.deprecated("fuzzyMatching")
  1095  
  1096  	case "wantCompletionDocumentation":
  1097  		result.deprecated("completionDocumentation")
  1098  
  1099  	case "wantUnimportedCompletions":
  1100  		result.deprecated("completeUnimported")
  1101  
  1102  	case "fuzzyMatching":
  1103  		result.deprecated("matcher")
  1104  
  1105  	case "caseSensitiveCompletion":
  1106  		result.deprecated("matcher")
  1107  
  1108  	// Deprecated settings.
  1109  	case "wantSuggestedFixes":
  1110  		result.deprecated("")
  1111  
  1112  	case "noIncrementalSync":
  1113  		result.deprecated("")
  1114  
  1115  	case "watchFileChanges":
  1116  		result.deprecated("")
  1117  
  1118  	case "go-diff":
  1119  		result.deprecated("")
  1120  
  1121  	default:
  1122  		result.unexpected()
  1123  	}
  1124  	return result
  1125  }
  1126  
  1127  // parseErrorf reports an error parsing the current configuration value.
  1128  func (r *OptionResult) parseErrorf(msg string, values ...interface{}) {
  1129  	if false {
  1130  		_ = fmt.Sprintf(msg, values...) // this causes vet to check this like printf
  1131  	}
  1132  	prefix := fmt.Sprintf("parsing setting %q: ", r.Name)
  1133  	r.Error = fmt.Errorf(prefix+msg, values...)
  1134  }
  1135  
  1136  // A SoftError is an error that does not affect the functionality of gopls.
  1137  type SoftError struct {
  1138  	msg string
  1139  }
  1140  
  1141  func (e *SoftError) Error() string {
  1142  	return e.msg
  1143  }
  1144  
  1145  // deprecated reports the current setting as deprecated. If 'replacement' is
  1146  // non-nil, it is suggested to the user.
  1147  func (r *OptionResult) deprecated(replacement string) {
  1148  	msg := fmt.Sprintf("gopls setting %q is deprecated", r.Name)
  1149  	if replacement != "" {
  1150  		msg = fmt.Sprintf("%s, use %q instead", msg, replacement)
  1151  	}
  1152  	r.Error = &SoftError{msg}
  1153  }
  1154  
  1155  // softErrorf reports a soft error related to the current option.
  1156  func (r *OptionResult) softErrorf(format string, args ...any) {
  1157  	r.Error = &SoftError{fmt.Sprintf(format, args...)}
  1158  }
  1159  
  1160  // unexpected reports that the current setting is not known to gopls.
  1161  func (r *OptionResult) unexpected() {
  1162  	r.Error = fmt.Errorf("unexpected gopls setting %q", r.Name)
  1163  }
  1164  
  1165  func (r *OptionResult) asBool() (bool, bool) {
  1166  	b, ok := r.Value.(bool)
  1167  	if !ok {
  1168  		r.parseErrorf("invalid type %T, expect bool", r.Value)
  1169  		return false, false
  1170  	}
  1171  	return b, true
  1172  }
  1173  
  1174  func (r *OptionResult) setBool(b *bool) {
  1175  	if v, ok := r.asBool(); ok {
  1176  		*b = v
  1177  	}
  1178  }
  1179  
  1180  func (r *OptionResult) setDuration(d *time.Duration) {
  1181  	if v, ok := r.asString(); ok {
  1182  		parsed, err := time.ParseDuration(v)
  1183  		if err != nil {
  1184  			r.parseErrorf("failed to parse duration %q: %v", v, err)
  1185  			return
  1186  		}
  1187  		*d = parsed
  1188  	}
  1189  }
  1190  
  1191  func (r *OptionResult) setBoolMap(bm *map[string]bool) {
  1192  	m := r.asBoolMap()
  1193  	*bm = m
  1194  }
  1195  
  1196  func (r *OptionResult) setAnnotationMap(bm *map[Annotation]bool) {
  1197  	all := r.asBoolMap()
  1198  	if all == nil {
  1199  		return
  1200  	}
  1201  	// Default to everything enabled by default.
  1202  	m := make(map[Annotation]bool)
  1203  	for k, enabled := range all {
  1204  		a, err := asOneOf(
  1205  			k,
  1206  			string(Nil),
  1207  			string(Escape),
  1208  			string(Inline),
  1209  			string(Bounds),
  1210  		)
  1211  		if err != nil {
  1212  			// In case of an error, process any legacy values.
  1213  			switch k {
  1214  			case "noEscape":
  1215  				m[Escape] = false
  1216  				r.parseErrorf(`"noEscape" is deprecated, set "Escape: false" instead`)
  1217  			case "noNilcheck":
  1218  				m[Nil] = false
  1219  				r.parseErrorf(`"noNilcheck" is deprecated, set "Nil: false" instead`)
  1220  			case "noInline":
  1221  				m[Inline] = false
  1222  				r.parseErrorf(`"noInline" is deprecated, set "Inline: false" instead`)
  1223  			case "noBounds":
  1224  				m[Bounds] = false
  1225  				r.parseErrorf(`"noBounds" is deprecated, set "Bounds: false" instead`)
  1226  			default:
  1227  				r.parseErrorf("%v", err)
  1228  			}
  1229  			continue
  1230  		}
  1231  		m[Annotation(a)] = enabled
  1232  	}
  1233  	*bm = m
  1234  }
  1235  
  1236  func (r *OptionResult) asBoolMap() map[string]bool {
  1237  	all, ok := r.Value.(map[string]interface{})
  1238  	if !ok {
  1239  		r.parseErrorf("invalid type %T for map[string]bool option", r.Value)
  1240  		return nil
  1241  	}
  1242  	m := make(map[string]bool)
  1243  	for a, enabled := range all {
  1244  		if e, ok := enabled.(bool); ok {
  1245  			m[a] = e
  1246  		} else {
  1247  			r.parseErrorf("invalid type %T for map key %q", enabled, a)
  1248  			return m
  1249  		}
  1250  	}
  1251  	return m
  1252  }
  1253  
  1254  func (r *OptionResult) asString() (string, bool) {
  1255  	b, ok := r.Value.(string)
  1256  	if !ok {
  1257  		r.parseErrorf("invalid type %T, expect string", r.Value)
  1258  		return "", false
  1259  	}
  1260  	return b, true
  1261  }
  1262  
  1263  func (r *OptionResult) asStringSlice() ([]string, bool) {
  1264  	iList, ok := r.Value.([]interface{})
  1265  	if !ok {
  1266  		r.parseErrorf("invalid type %T, expect list", r.Value)
  1267  		return nil, false
  1268  	}
  1269  	var list []string
  1270  	for _, elem := range iList {
  1271  		s, ok := elem.(string)
  1272  		if !ok {
  1273  			r.parseErrorf("invalid element type %T, expect string", elem)
  1274  			return nil, false
  1275  		}
  1276  		list = append(list, s)
  1277  	}
  1278  	return list, true
  1279  }
  1280  
  1281  func (r *OptionResult) asOneOf(options ...string) (string, bool) {
  1282  	s, ok := r.asString()
  1283  	if !ok {
  1284  		return "", false
  1285  	}
  1286  	s, err := asOneOf(s, options...)
  1287  	if err != nil {
  1288  		r.parseErrorf("%v", err)
  1289  	}
  1290  	return s, err == nil
  1291  }
  1292  
  1293  func asOneOf(str string, options ...string) (string, error) {
  1294  	lower := strings.ToLower(str)
  1295  	for _, opt := range options {
  1296  		if strings.ToLower(opt) == lower {
  1297  			return opt, nil
  1298  		}
  1299  	}
  1300  	return "", fmt.Errorf("invalid option %q for enum", str)
  1301  }
  1302  
  1303  func (r *OptionResult) setString(s *string) {
  1304  	if v, ok := r.asString(); ok {
  1305  		*s = v
  1306  	}
  1307  }
  1308  
  1309  func (r *OptionResult) setStringSlice(s *[]string) {
  1310  	if v, ok := r.asStringSlice(); ok {
  1311  		*s = v
  1312  	}
  1313  }
  1314  
  1315  func analyzers() map[string]*Analyzer {
  1316  	return map[string]*Analyzer{}
  1317  }
  1318  
  1319  func urlRegexp() *regexp.Regexp {
  1320  	// Ensure links are matched as full words, not anywhere.
  1321  	re := regexp.MustCompile(`\b(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?\b`)
  1322  	re.Longest()
  1323  	return re
  1324  }