| // 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 settings |
| |
| import ( |
| "fmt" |
| "maps" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "golang.org/x/tools/gopls/internal/file" |
| "golang.org/x/tools/gopls/internal/protocol" |
| "golang.org/x/tools/gopls/internal/util/frob" |
| ) |
| |
| type Annotation string |
| |
| const ( |
| // Nil controls nil checks. |
| Nil Annotation = "nil" |
| |
| // Escape controls diagnostics about escape choices. |
| Escape Annotation = "escape" |
| |
| // Inline controls diagnostics about inlining choices. |
| Inline Annotation = "inline" |
| |
| // Bounds controls bounds checking diagnostics. |
| Bounds Annotation = "bounds" |
| ) |
| |
| // Options holds various configuration that affects Gopls execution, organized |
| // by the nature or origin of the settings. |
| // |
| // Options must be comparable with reflect.DeepEqual, and serializable with |
| // [frob.Codec]. |
| // |
| // This type defines both the logic of LSP-supplied option parsing |
| // (see [SetOptions]), and the public documentation of options in |
| // ../../doc/settings.md (generated by gopls/doc/generate). |
| // |
| // Each exported field of each embedded type such as "ClientOptions" |
| // contributes a user-visible option setting. The option name is the |
| // field name rendered in camelCase. Unlike most Go doc comments, |
| // these fields should be documented using GitHub markdown. |
| type Options struct { |
| ClientOptions |
| ServerOptions |
| UserOptions |
| InternalOptions |
| } |
| |
| // ClientOptions holds LSP-specific configuration that is provided by the |
| // client. |
| // |
| // ClientOptions must be comparable with reflect.DeepEqual. |
| type ClientOptions struct { |
| ClientInfo protocol.ClientInfo |
| InsertTextFormat protocol.InsertTextFormat |
| InsertReplaceSupported bool |
| ConfigurationSupported bool |
| DynamicConfigurationSupported bool |
| DynamicRegistrationSemanticTokensSupported bool |
| DynamicWatchedFilesSupported bool |
| RelativePatternsSupported bool |
| PreferredContentFormat protocol.MarkupKind |
| LineFoldingOnly bool |
| HierarchicalDocumentSymbolSupport bool |
| SemanticTypes []string |
| SemanticMods []string |
| RelatedInformationSupported bool |
| CompletionTags bool |
| CompletionDeprecated bool |
| SupportedResourceOperations []protocol.ResourceOperationKind |
| CodeActionResolveOptions []string |
| } |
| |
| // ServerOptions holds LSP-specific configuration that is provided by the |
| // server. |
| // |
| // ServerOptions must be comparable with reflect.DeepEqual. |
| type ServerOptions struct { |
| SupportedCodeActions map[file.Kind]map[protocol.CodeActionKind]bool |
| SupportedCommands []string |
| } |
| |
| // Note: BuildOptions must be comparable with reflect.DeepEqual. |
| type BuildOptions 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 |
| |
| // DirectoryFilters can be used to exclude unwanted directories from the |
| // workspace. By default, all directories are included. Filters are an |
| // operator, `+` to include and `-` to exclude, followed by a path prefix |
| // relative to the workspace folder. They are evaluated in order, and |
| // the last filter that applies to a path controls whether it is included. |
| // The path prefix can be empty, so an initial `-` excludes everything. |
| // |
| // DirectoryFilters also supports the `**` operator to match 0 or more directories. |
| // |
| // Examples: |
| // |
| // Exclude node_modules at current depth: `-node_modules` |
| // |
| // Exclude node_modules at any depth: `-**/node_modules` |
| // |
| // Include only project_a: `-` (exclude everything), `+project_a` |
| // |
| // Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules` |
| DirectoryFilters []string |
| |
| // TemplateExtensions gives the extensions of file names that are treated |
| // as template files. (The extension |
| // is the part of the file name after the final dot.) |
| TemplateExtensions []string |
| |
| // obsolete, no effect |
| MemoryMode string `status:"experimental"` |
| |
| // ExpandWorkspaceToModule determines which packages are considered |
| // "workspace packages" when the workspace is using modules. |
| // |
| // Workspace packages affect the scope of workspace-wide operations. Notably, |
| // gopls diagnoses all packages considered to be part of the workspace after |
| // every keystroke, so by setting "ExpandWorkspaceToModule" to false, and |
| // opening a nested workspace directory, you can reduce the amount of work |
| // gopls has to do to keep your workspace up to date. |
| ExpandWorkspaceToModule bool `status:"experimental"` |
| |
| // StandaloneTags specifies a set of build constraints that identify |
| // individual Go source files that make up the entire main package of an |
| // executable. |
| // |
| // A common example of standalone main files is the convention of using the |
| // directive `//go:build ignore` to denote files that are not intended to be |
| // included in any package, for example because they are invoked directly by |
| // the developer using `go run`. |
| // |
| // Gopls considers a file to be a standalone main file if and only if it has |
| // package name "main" and has a build directive of the exact form |
| // "//go:build tag" or "// +build tag", where tag is among the list of tags |
| // configured by this setting. Notably, if the build constraint is more |
| // complicated than a simple tag (such as the composite constraint |
| // `//go:build tag && go1.18`), the file is not considered to be a standalone |
| // main file. |
| // |
| // This setting is only supported when gopls is built with Go 1.16 or later. |
| StandaloneTags []string |
| } |
| |
| // Note: UIOptions must be comparable with reflect.DeepEqual. |
| type UIOptions struct { |
| DocumentationOptions |
| CompletionOptions |
| NavigationOptions |
| DiagnosticOptions |
| InlayHintOptions |
| |
| // Codelenses overrides the enabled/disabled state of each of gopls' |
| // sources of [Code Lenses](codelenses.md). |
| // |
| // Example Usage: |
| // |
| // ```json5 |
| // "gopls": { |
| // ... |
| // "codelenses": { |
| // "generate": false, // Don't show the `go generate` lens. |
| // "gc_details": true // Show a code lens toggling the display of gc's choices. |
| // } |
| // ... |
| // } |
| // ``` |
| Codelenses map[CodeLensSource]bool |
| |
| // SemanticTokens controls whether the LSP server will send |
| // semantic tokens to the client. |
| SemanticTokens bool `status:"experimental"` |
| |
| // NoSemanticString turns off the sending of the semantic token 'string' |
| NoSemanticString bool `status:"experimental"` |
| |
| // NoSemanticNumber turns off the sending of the semantic token 'number' |
| NoSemanticNumber bool `status:"experimental"` |
| } |
| |
| // A CodeLensSource identifies an (algorithmic) source of code lenses. |
| type CodeLensSource string |
| |
| // CodeLens sources |
| // |
| // These identifiers appear in the "codelenses" configuration setting, |
| // and in the user documentation thereof, which is generated by |
| // gopls/doc/generate/generate.go parsing this file. |
| // |
| // Doc comments should use GitHub Markdown. |
| // The first line becomes the title. |
| // |
| // (For historical reasons, each code lens source identifier typically |
| // matches the name of one of the command.Commands returned by it, |
| // but that isn't essential.) |
| const ( |
| // Toggle display of Go compiler optimization decisions |
| // |
| // This codelens source causes the `package` declaration of |
| // each file to be annotated with a command to toggle the |
| // state of the per-session variable that controls whether |
| // optimization decisions from the Go compiler (formerly known |
| // as "gc") should be displayed as diagnostics. |
| // |
| // Optimization decisions include: |
| // - whether a variable escapes, and how escape is inferred; |
| // - whether a nil-pointer check is implied or eliminated; |
| // - whether a function can be inlined. |
| // |
| // TODO(adonovan): this source is off by default because the |
| // annotation is annoying and because VS Code has a separate |
| // "Toggle gc details" command. Replace it with a Code Action |
| // ("Source action..."). |
| CodeLensGCDetails CodeLensSource = "gc_details" |
| |
| // Run `go generate` |
| // |
| // This codelens source annotates any `//go:generate` comments |
| // with commands to run `go generate` in this directory, on |
| // all directories recursively beneath this one. |
| // |
| // See [Generating code](https://go.dev/blog/generate) for |
| // more details. |
| CodeLensGenerate CodeLensSource = "generate" |
| |
| // Re-generate cgo declarations |
| // |
| // This codelens source annotates an `import "C"` declaration |
| // with a command to re-run the [cgo |
| // command](https://pkg.go.dev/cmd/cgo) to regenerate the |
| // corresponding Go declarations. |
| // |
| // Use this after editing the C code in comments attached to |
| // the import, or in C header files included by it. |
| CodeLensRegenerateCgo CodeLensSource = "regenerate_cgo" |
| |
| // Run govulncheck |
| // |
| // This codelens source annotates the `module` directive in a |
| // go.mod file with a command to run Govulncheck. |
| // |
| // [Govulncheck](https://go.dev/blog/vuln) is a static |
| // analysis tool that computes the set of functions reachable |
| // within your application, including dependencies; |
| // queries a database of known security vulnerabilities; and |
| // reports any potential problems it finds. |
| CodeLensRunGovulncheck CodeLensSource = "run_govulncheck" |
| |
| // Run tests and benchmarks |
| // |
| // This codelens source annotates each `Test` and `Benchmark` |
| // function in a `*_test.go` file with a command to run it. |
| // |
| // This source is off by default because VS Code has |
| // a client-side custom UI for testing, and because progress |
| // notifications are not a great UX for streamed test output. |
| // See: |
| // - golang/go#67400 for a discussion of this feature. |
| // - https://github.com/joaotavora/eglot/discussions/1402 |
| // for an alternative approach. |
| CodeLensTest CodeLensSource = "test" |
| |
| // Tidy go.mod file |
| // |
| // This codelens source annotates the `module` directive in a |
| // go.mod file with a command to run [`go mod |
| // tidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures |
| // that the go.mod file matches the source code in the module. |
| CodeLensTidy CodeLensSource = "tidy" |
| |
| // Update dependencies |
| // |
| // This codelens source annotates the `module` directive in a |
| // go.mod file with commands to: |
| // |
| // - check for available upgrades, |
| // - upgrade direct dependencies, and |
| // - upgrade all dependencies transitively. |
| CodeLensUpgradeDependency CodeLensSource = "upgrade_dependency" |
| |
| // Update vendor directory |
| // |
| // This codelens source annotates the `module` directive in a |
| // go.mod file with a command to run [`go mod |
| // vendor`](https://go.dev/ref/mod#go-mod-vendor), which |
| // creates or updates the directory named `vendor` in the |
| // module root so that it contains an up-to-date copy of all |
| // necessary package dependencies. |
| CodeLensVendor CodeLensSource = "vendor" |
| ) |
| |
| // Note: CompletionOptions must be comparable with reflect.DeepEqual. |
| type CompletionOptions struct { |
| // Placeholders enables placeholders for function parameters or struct |
| // fields in completion responses. |
| UsePlaceholders 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 `status:"debug"` |
| |
| // Matcher sets the algorithm that is used when calculating completion |
| // candidates. |
| Matcher Matcher `status:"advanced"` |
| |
| // ExperimentalPostfixCompletions enables artificial method snippets |
| // such as "someSlice.sort!". |
| ExperimentalPostfixCompletions bool `status:"experimental"` |
| |
| // CompleteFunctionCalls enables function call completion. |
| // |
| // When completing a statement, or when a function return type matches the |
| // expected of the expression being completed, completion may suggest call |
| // expressions (i.e. may include parentheses). |
| CompleteFunctionCalls bool |
| } |
| |
| // Note: DocumentationOptions must be comparable with reflect.DeepEqual. |
| type DocumentationOptions struct { |
| // 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 |
| |
| // LinkTarget is the base URL for links to Go package |
| // documentation returned by LSP operations such as Hover and |
| // DocumentLinks and in the CodeDescription field of each |
| // Diagnostic. |
| // |
| // 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. |
| // |
| // Modules matching the GOPRIVATE environment variable will not have |
| // documentation links in hover. |
| LinkTarget string |
| |
| // LinksInHover controls the presence of documentation links in hover markdown. |
| LinksInHover LinksInHoverEnum |
| } |
| |
| // LinksInHoverEnum has legal values: |
| // |
| // - `false`, for no links; |
| // - `true`, for links to the `linkTarget` domain; or |
| // - `"gopls"`, for links to gopls' internal documentation viewer. |
| // |
| // Note: this type has special logic in loadEnums in generate.go. |
| // Be sure to reflect enum and doc changes there! |
| type LinksInHoverEnum int |
| |
| const ( |
| LinksInHover_None LinksInHoverEnum = iota |
| LinksInHover_LinkTarget |
| LinksInHover_Gopls |
| ) |
| |
| // MarshalJSON implements the json.Marshaler interface, so that the default |
| // values are formatted correctly in documentation. (See [Options.setOne] for |
| // the flexible custom unmarshalling behavior). |
| func (l LinksInHoverEnum) MarshalJSON() ([]byte, error) { |
| switch l { |
| case LinksInHover_None: |
| return []byte("false"), nil |
| case LinksInHover_LinkTarget: |
| return []byte("true"), nil |
| case LinksInHover_Gopls: |
| return []byte(`"gopls"`), nil |
| default: |
| return nil, fmt.Errorf("invalid LinksInHover value %d", l) |
| } |
| } |
| |
| // Note: FormattingOptions must be comparable with reflect.DeepEqual. |
| type FormattingOptions struct { |
| // Local is the equivalent of the `goimports -local` flag, which puts |
| // imports beginning with this string after third-party packages. It should |
| // be the prefix of the import path whose imports should be grouped |
| // separately. |
| // |
| // It is used when tidying imports (during an LSP Organize |
| // Imports request) or when inserting new ones (for example, |
| // during completion); an LSP Formatting request merely sorts the |
| // existing imports. |
| Local string |
| |
| // Gofumpt indicates if we should run gofumpt formatting. |
| Gofumpt bool |
| } |
| |
| // Note: DiagnosticOptions must be comparable with reflect.DeepEqual. |
| type DiagnosticOptions 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 in |
| // [analyzers.md](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md). |
| // |
| // Example Usage: |
| // |
| // ```json5 |
| // ... |
| // "analyses": { |
| // "unreachable": false, // Disable the unreachable analyzer. |
| // "unusedvariable": true // Enable the unusedvariable analyzer. |
| // } |
| // ... |
| // ``` |
| Analyses map[string]bool |
| |
| // Staticcheck enables additional analyses from staticcheck.io. |
| // These analyses are documented on |
| // [Staticcheck's website](https://staticcheck.io/docs/checks/). |
| Staticcheck bool `status:"experimental"` |
| |
| // Annotations specifies the various kinds of optimization diagnostics |
| // that should be reported by the gc_details command. |
| Annotations map[Annotation]bool `status:"experimental"` |
| |
| // Vulncheck enables vulnerability scanning. |
| Vulncheck VulncheckMode `status:"experimental"` |
| |
| // DiagnosticsDelay 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"`. |
| DiagnosticsDelay time.Duration `status:"advanced"` |
| |
| // DiagnosticsTrigger controls when to run diagnostics. |
| DiagnosticsTrigger DiagnosticsTrigger `status:"experimental"` |
| |
| // AnalysisProgressReporting controls whether gopls sends progress |
| // notifications when construction of its index of analysis facts is taking a |
| // long time. Cancelling these notifications will cancel the indexing task, |
| // though it will restart after the next change in the workspace. |
| // |
| // When a package is opened for the first time and heavyweight analyses such as |
| // staticcheck are enabled, it can take a while to construct the index of |
| // analysis facts for all its dependencies. The index is cached in the |
| // filesystem, so subsequent analysis should be faster. |
| AnalysisProgressReporting bool |
| } |
| |
| type InlayHintOptions struct { |
| // Hints specify inlay hints that users want to see. A full list of hints |
| // that gopls uses can be found in |
| // [inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md). |
| Hints map[InlayHint]bool `status:"experimental"` |
| } |
| |
| // An InlayHint identifies a category of hint that may be |
| // independently requested through the "hints" setting. |
| type InlayHint string |
| |
| // This is the source from which gopls/doc/inlayHints.md is generated. |
| const ( |
| // ParameterNames controls inlay hints for parameter names: |
| // ```go |
| // parseInt(/* str: */ "123", /* radix: */ 8) |
| // ``` |
| ParameterNames InlayHint = "parameterNames" |
| |
| // AssignVariableTypes controls inlay hints for variable types in assign statements: |
| // ```go |
| // i/* int*/, j/* int*/ := 0, len(r)-1 |
| // ``` |
| AssignVariableTypes InlayHint = "assignVariableTypes" |
| |
| // ConstantValues controls inlay hints for constant values: |
| // ```go |
| // const ( |
| // KindNone Kind = iota/* = 0*/ |
| // KindPrint/* = 1*/ |
| // KindPrintf/* = 2*/ |
| // KindErrorf/* = 3*/ |
| // ) |
| // ``` |
| ConstantValues InlayHint = "constantValues" |
| |
| // RangeVariableTypes controls inlay hints for variable types in range statements: |
| // ```go |
| // for k/* int*/, v/* string*/ := range []string{} { |
| // fmt.Println(k, v) |
| // } |
| // ``` |
| RangeVariableTypes InlayHint = "rangeVariableTypes" |
| |
| // CompositeLiteralTypes controls inlay hints for composite literal types: |
| // ```go |
| // for _, c := range []struct { |
| // in, want string |
| // }{ |
| // /*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"}, |
| // } |
| // ``` |
| CompositeLiteralTypes InlayHint = "compositeLiteralTypes" |
| |
| // CompositeLiteralFieldNames inlay hints for composite literal field names: |
| // ```go |
| // {/*in: */"Hello, world", /*want: */"dlrow ,olleH"} |
| // ``` |
| CompositeLiteralFieldNames InlayHint = "compositeLiteralFields" |
| |
| // FunctionTypeParameters inlay hints for implicit type parameters on generic functions: |
| // ```go |
| // myFoo/*[int, string]*/(1, "hello") |
| // ``` |
| FunctionTypeParameters InlayHint = "functionTypeParameters" |
| ) |
| |
| type NavigationOptions struct { |
| // ImportShortcut specifies whether import statements should link to |
| // documentation or go to definitions. |
| ImportShortcut ImportShortcut |
| |
| // SymbolMatcher sets the algorithm that is used when finding workspace symbols. |
| SymbolMatcher SymbolMatcher `status:"advanced"` |
| |
| // SymbolStyle controls how symbols are qualified in symbol responses. |
| // |
| // Example Usage: |
| // |
| // ```json5 |
| // "gopls": { |
| // ... |
| // "symbolStyle": "Dynamic", |
| // ... |
| // } |
| // ``` |
| SymbolStyle SymbolStyle `status:"advanced"` |
| |
| // SymbolScope controls which packages are searched for workspace/symbol |
| // requests. When the scope is "workspace", gopls searches only workspace |
| // packages. When the scope is "all", gopls searches all loaded packages, |
| // including dependencies and the standard library. |
| SymbolScope SymbolScope |
| } |
| |
| // UserOptions holds custom Gopls configuration (not part of the LSP) that is |
| // modified by the client. |
| // |
| // UserOptions must be comparable with reflect.DeepEqual. |
| type UserOptions struct { |
| BuildOptions |
| UIOptions |
| FormattingOptions |
| |
| // VerboseOutput enables additional debug logging. |
| VerboseOutput bool `status:"debug"` |
| } |
| |
| // 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] |
| } |
| } |
| |
| // InternalOptions contains settings that are not intended for use by the |
| // average user. These may be settings used by tests or outdated settings that |
| // will soon be deprecated. Some of these settings may not even be configurable |
| // by the user. |
| // |
| // TODO(rfindley): even though these settings are not intended for |
| // modification, some of them should be surfaced in our documentation. |
| type InternalOptions struct { |
| // VerboseWorkDoneProgress controls whether the LSP server should send |
| // progress reports for all work done outside the scope of an RPC. |
| // Used by the regression tests. |
| VerboseWorkDoneProgress bool |
| |
| // The following options were previously available to users, but they |
| // really shouldn't be configured by anyone other than "power users". |
| |
| // 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 |
| |
| // ShowBugReports causes a message to be shown when the first bug is reported |
| // on the server. |
| // This option applies only during initialization. |
| ShowBugReports bool |
| |
| // SubdirWatchPatterns configures the file watching glob patterns registered |
| // by gopls. |
| // |
| // Some clients (namely VS Code) do not send workspace/didChangeWatchedFile |
| // notifications for files contained in a directory when that directory is |
| // deleted: |
| // https://github.com/microsoft/vscode/issues/109754 |
| // |
| // In this case, gopls would miss important notifications about deleted |
| // packages. To work around this, gopls registers a watch pattern for each |
| // directory containing Go files. |
| // |
| // Unfortunately, other clients experience performance problems with this |
| // many watch patterns, so there is no single behavior that works well for |
| // all clients. |
| // |
| // The "subdirWatchPatterns" setting allows configuring this behavior. Its |
| // default value of "auto" attempts to guess the correct behavior based on |
| // the client name. We'd love to avoid this specialization, but as described |
| // above there is no single value that works for all clients. |
| // |
| // If any LSP client does not behave well with the default value (for |
| // example, if like VS Code it drops file notifications), please file an |
| // issue. |
| SubdirWatchPatterns SubdirWatchPatterns |
| |
| // ReportAnalysisProgressAfter sets the duration for gopls to wait before starting |
| // progress reporting for ongoing go/analysis passes. |
| // |
| // It is intended to be used for testing only. |
| ReportAnalysisProgressAfter time.Duration |
| |
| // TelemetryPrompt controls whether gopls prompts about enabling Go telemetry. |
| // |
| // Once the prompt is answered, gopls doesn't ask again, but TelemetryPrompt |
| // can prevent the question from ever being asked in the first place. |
| TelemetryPrompt bool |
| |
| // LinkifyShowMessage controls whether the client wants gopls |
| // to linkify links in showMessage. e.g. [go.dev](https://go.dev). |
| LinkifyShowMessage bool |
| |
| // IncludeReplaceInWorkspace controls whether locally replaced modules in a |
| // go.mod file are treated like workspace modules. |
| // Or in other words, if a go.mod file with local replaces behaves like a |
| // go.work file. |
| IncludeReplaceInWorkspace bool |
| |
| // ZeroConfig enables the zero-config algorithm for workspace layout, |
| // dynamically creating build configurations for different modules, |
| // directories, and GOOS/GOARCH combinations to cover open files. |
| ZeroConfig bool |
| |
| // PullDiagnostics enables support for pull diagnostics. |
| // |
| // TODO(rfindley): make pull diagnostics robust, and remove this option, |
| // allowing pull diagnostics by default. |
| PullDiagnostics bool |
| |
| // AddTestSourceCodeAction enables support for adding test as a source code |
| // action. |
| // TODO(hxjiang): remove this option once the feature is implemented. |
| AddTestSourceCodeAction bool |
| } |
| |
| type SubdirWatchPatterns string |
| |
| const ( |
| SubdirWatchPatternsOn SubdirWatchPatterns = "on" |
| SubdirWatchPatternsOff SubdirWatchPatterns = "off" |
| SubdirWatchPatternsAuto SubdirWatchPatterns = "auto" |
| ) |
| |
| type ImportShortcut string |
| |
| const ( |
| BothShortcuts ImportShortcut = "Both" |
| LinkShortcut ImportShortcut = "Link" |
| DefinitionShortcut ImportShortcut = "Definition" |
| ) |
| |
| func (s ImportShortcut) ShowLinks() bool { |
| return s == BothShortcuts || s == LinkShortcut |
| } |
| |
| func (s ImportShortcut) ShowDefinition() bool { |
| return s == BothShortcuts || s == DefinitionShortcut |
| } |
| |
| type Matcher string |
| |
| const ( |
| Fuzzy Matcher = "Fuzzy" |
| CaseInsensitive Matcher = "CaseInsensitive" |
| CaseSensitive Matcher = "CaseSensitive" |
| ) |
| |
| // A SymbolMatcher controls the matching of symbols for workspace/symbol |
| // requests. |
| type SymbolMatcher string |
| |
| const ( |
| SymbolFuzzy SymbolMatcher = "Fuzzy" |
| SymbolFastFuzzy SymbolMatcher = "FastFuzzy" |
| SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive" |
| SymbolCaseSensitive SymbolMatcher = "CaseSensitive" |
| ) |
| |
| // A SymbolStyle controls the formatting of symbols in workspace/symbol results. |
| 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" |
| ) |
| |
| // A SymbolScope controls the search scope for workspace/symbol requests. |
| type SymbolScope string |
| |
| const ( |
| // WorkspaceSymbolScope matches symbols in workspace packages only. |
| WorkspaceSymbolScope SymbolScope = "workspace" |
| // AllSymbolScope matches symbols in any loaded package, including |
| // dependencies. |
| AllSymbolScope SymbolScope = "all" |
| ) |
| |
| 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 VulncheckMode string |
| |
| const ( |
| // Disable vulnerability analysis. |
| ModeVulncheckOff VulncheckMode = "Off" |
| // In Imports mode, `gopls` will report vulnerabilities that affect packages |
| // directly and indirectly used by the analyzed main module. |
| ModeVulncheckImports VulncheckMode = "Imports" |
| |
| // TODO: VulncheckRequire, VulncheckCallgraph |
| ) |
| |
| type DiagnosticsTrigger string |
| |
| const ( |
| // Trigger diagnostics on file edit and save. (default) |
| DiagnosticsOnEdit DiagnosticsTrigger = "Edit" |
| // Trigger diagnostics only on file save. Events like initial workspace load |
| // or configuration change will still trigger diagnostics. |
| DiagnosticsOnSave DiagnosticsTrigger = "Save" |
| // TODO: support "Manual"? |
| ) |
| |
| // Set updates *options based on the provided JSON value: |
| // null, bool, string, number, array, or object. |
| // On failure, it returns one or more non-nil errors. |
| func (o *Options) Set(value any) (errors []error) { |
| switch value := value.(type) { |
| case nil: |
| case map[string]any: |
| seen := make(map[string]struct{}) |
| for name, value := range value { |
| // Use only the last segment of a dotted name such as |
| // ui.navigation.symbolMatcher. The other segments |
| // are discarded, even without validation (!). |
| // (They are supported to enable hierarchical names |
| // in the VS Code graphical configuration UI.) |
| split := strings.Split(name, ".") |
| name = split[len(split)-1] |
| |
| if _, ok := seen[name]; ok { |
| errors = append(errors, fmt.Errorf("duplicate value for %s", name)) |
| } |
| seen[name] = struct{}{} |
| |
| if err := o.setOne(name, value); err != nil { |
| err := fmt.Errorf("setting option %q: %w", name, err) |
| errors = append(errors, err) |
| } |
| } |
| default: |
| errors = append(errors, fmt.Errorf("invalid options type %T (want JSON null or object)", value)) |
| } |
| return errors |
| } |
| |
| func (o *Options) ForClientCapabilities(clientInfo *protocol.ClientInfo, caps protocol.ClientCapabilities) { |
| if clientInfo != nil { |
| o.ClientInfo = *clientInfo |
| } |
| if caps.Workspace.WorkspaceEdit != nil { |
| o.SupportedResourceOperations = caps.Workspace.WorkspaceEdit.ResourceOperations |
| } |
| // Check if the client supports snippets in completion items. |
| if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport { |
| o.InsertTextFormat = protocol.SnippetTextFormat |
| } |
| o.InsertReplaceSupported = caps.TextDocument.Completion.CompletionItem.InsertReplaceSupport |
| // Check if the client supports configuration messages. |
| o.ConfigurationSupported = caps.Workspace.Configuration |
| o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration |
| o.DynamicRegistrationSemanticTokensSupported = caps.TextDocument.SemanticTokens.DynamicRegistration |
| o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration |
| o.RelativePatternsSupported = caps.Workspace.DidChangeWatchedFiles.RelativePatternSupport |
| |
| // Check which types of content format are supported by this client. |
| if hover := caps.TextDocument.Hover; hover != nil && len(hover.ContentFormat) > 0 { |
| o.PreferredContentFormat = hover.ContentFormat[0] |
| } |
| // Check if the client supports only line folding. |
| |
| if fr := caps.TextDocument.FoldingRange; fr != nil { |
| o.LineFoldingOnly = fr.LineFoldingOnly |
| } |
| // Check if the client supports hierarchical document symbols. |
| o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport |
| |
| // Client's semantic tokens |
| o.SemanticTypes = caps.TextDocument.SemanticTokens.TokenTypes |
| o.SemanticMods = caps.TextDocument.SemanticTokens.TokenModifiers |
| // we don't need Requests, as we support full functionality |
| // we don't need Formats, as there is only one, for now |
| |
| // Check if the client supports diagnostic related information. |
| o.RelatedInformationSupported = caps.TextDocument.PublishDiagnostics.RelatedInformation |
| // Check if the client completion support includes tags (preferred) or deprecation |
| if caps.TextDocument.Completion.CompletionItem.TagSupport != nil && |
| caps.TextDocument.Completion.CompletionItem.TagSupport.ValueSet != nil { |
| o.CompletionTags = true |
| } else if caps.TextDocument.Completion.CompletionItem.DeprecatedSupport { |
| o.CompletionDeprecated = true |
| } |
| |
| // Check if the client supports code actions resolving. |
| if caps.TextDocument.CodeAction.DataSupport && caps.TextDocument.CodeAction.ResolveSupport != nil { |
| o.CodeActionResolveOptions = caps.TextDocument.CodeAction.ResolveSupport.Properties |
| } |
| } |
| |
| var codec = frob.CodecFor[*Options]() |
| |
| func (o *Options) Clone() *Options { |
| data := codec.Encode(o) |
| var clone *Options |
| codec.Decode(data, &clone) |
| return clone |
| } |
| |
| // validateDirectoryFilter validates if the filter string |
| // - is not empty |
| // - start with either + or - |
| // - doesn't contain currently unsupported glob operators: *, ? |
| func validateDirectoryFilter(ifilter string) (string, error) { |
| filter := fmt.Sprint(ifilter) |
| if filter == "" || (filter[0] != '+' && filter[0] != '-') { |
| return "", fmt.Errorf("invalid filter %v, must start with + or -", filter) |
| } |
| segs := strings.Split(filter[1:], "/") |
| unsupportedOps := [...]string{"?", "*"} |
| for _, seg := range segs { |
| if seg != "**" { |
| for _, op := range unsupportedOps { |
| if strings.Contains(seg, op) { |
| return "", fmt.Errorf("invalid filter %v, operator %v not supported. If you want to have this operator supported, consider filing an issue.", filter, op) |
| } |
| } |
| } |
| } |
| |
| return strings.TrimRight(filepath.FromSlash(filter), "/"), nil |
| } |
| |
| // setOne updates a field of o based on the name and value. |
| // It returns an error if the value was invalid or duplicate. |
| // It is the caller's responsibility to augment the error with 'name'. |
| func (o *Options) setOne(name string, value any) error { |
| switch name { |
| case "env": |
| env, ok := value.(map[string]any) |
| if !ok { |
| return fmt.Errorf("invalid type %T (want JSON object)", value) |
| } |
| if o.Env == nil { |
| o.Env = make(map[string]string) |
| } |
| for k, v := range env { |
| // For historic compatibility, we accept int too (e.g. CGO_ENABLED=1). |
| switch v.(type) { |
| case string, int: |
| o.Env[k] = fmt.Sprint(v) |
| default: |
| return fmt.Errorf("invalid map value %T (want string)", v) |
| } |
| } |
| |
| case "buildFlags": |
| return setStringSlice(&o.BuildFlags, value) |
| |
| case "directoryFilters": |
| filterStrings, err := asStringSlice(value) |
| if err != nil { |
| return err |
| } |
| var filters []string |
| for _, filterStr := range filterStrings { |
| filter, err := validateDirectoryFilter(filterStr) |
| if err != nil { |
| return err |
| } |
| filters = append(filters, strings.TrimRight(filepath.FromSlash(filter), "/")) |
| } |
| o.DirectoryFilters = filters |
| |
| case "completionDocumentation": |
| return setBool(&o.CompletionDocumentation, value) |
| case "usePlaceholders": |
| return setBool(&o.UsePlaceholders, value) |
| case "deepCompletion": |
| return setBool(&o.DeepCompletion, value) |
| case "completeUnimported": |
| return setBool(&o.CompleteUnimported, value) |
| case "addTestSourceCodeAction": |
| return setBool(&o.AddTestSourceCodeAction, value) |
| case "completionBudget": |
| return setDuration(&o.CompletionBudget, value) |
| case "matcher": |
| return setEnum(&o.Matcher, value, |
| Fuzzy, |
| CaseSensitive, |
| CaseInsensitive) |
| |
| case "symbolMatcher": |
| return setEnum(&o.SymbolMatcher, value, |
| SymbolFuzzy, |
| SymbolFastFuzzy, |
| SymbolCaseInsensitive, |
| SymbolCaseSensitive) |
| |
| case "symbolStyle": |
| return setEnum(&o.SymbolStyle, value, |
| FullyQualifiedSymbols, |
| PackageQualifiedSymbols, |
| DynamicSymbols) |
| |
| case "symbolScope": |
| return setEnum(&o.SymbolScope, value, |
| WorkspaceSymbolScope, |
| AllSymbolScope) |
| |
| case "hoverKind": |
| return setEnum(&o.HoverKind, value, |
| NoDocumentation, |
| SingleLine, |
| SynopsisDocumentation, |
| FullDocumentation, |
| Structured) |
| |
| case "linkTarget": |
| return setString(&o.LinkTarget, value) |
| |
| case "linksInHover": |
| switch value { |
| case false: |
| o.LinksInHover = LinksInHover_None |
| case true: |
| o.LinksInHover = LinksInHover_LinkTarget |
| case "gopls": |
| o.LinksInHover = LinksInHover_Gopls |
| default: |
| return fmt.Errorf(`invalid value %s; expect false, true, or "gopls"`, |
| value) |
| } |
| |
| case "importShortcut": |
| return setEnum(&o.ImportShortcut, value, |
| BothShortcuts, |
| LinkShortcut, |
| DefinitionShortcut) |
| |
| case "analyses": |
| if err := setBoolMap(&o.Analyses, value); err != nil { |
| return err |
| } |
| if o.Analyses["fieldalignment"] { |
| return deprecatedError("the 'fieldalignment' analyzer was removed in gopls/v0.17.0; instead, hover over struct fields to see size/offset information (https://go.dev/issue/66861)") |
| } |
| |
| case "hints": |
| return setBoolMap(&o.Hints, value) |
| |
| case "annotations": |
| return setAnnotationMap(&o.Annotations, value) |
| |
| case "vulncheck": |
| return setEnum(&o.Vulncheck, value, |
| ModeVulncheckOff, |
| ModeVulncheckImports) |
| |
| case "codelenses", "codelens": |
| lensOverrides, err := asBoolMap[CodeLensSource](value) |
| if err != nil { |
| return err |
| } |
| if o.Codelenses == nil { |
| o.Codelenses = make(map[CodeLensSource]bool) |
| } |
| o.Codelenses = maps.Clone(o.Codelenses) |
| for source, enabled := range lensOverrides { |
| o.Codelenses[source] = enabled |
| } |
| |
| if name == "codelens" { |
| return deprecatedError("codelenses") |
| } |
| |
| case "staticcheck": |
| return setBool(&o.Staticcheck, value) |
| |
| case "local": |
| return setString(&o.Local, value) |
| |
| case "verboseOutput": |
| return setBool(&o.VerboseOutput, value) |
| |
| case "verboseWorkDoneProgress": |
| return setBool(&o.VerboseWorkDoneProgress, value) |
| |
| case "showBugReports": |
| return setBool(&o.ShowBugReports, value) |
| |
| case "gofumpt": |
| return setBool(&o.Gofumpt, value) |
| |
| case "completeFunctionCalls": |
| return setBool(&o.CompleteFunctionCalls, value) |
| |
| case "semanticTokens": |
| return setBool(&o.SemanticTokens, value) |
| |
| case "noSemanticString": |
| return setBool(&o.NoSemanticString, value) |
| |
| case "noSemanticNumber": |
| return setBool(&o.NoSemanticNumber, value) |
| |
| case "expandWorkspaceToModule": |
| // See golang/go#63536: we can consider deprecating |
| // expandWorkspaceToModule, but probably need to change the default |
| // behavior in that case to *not* expand to the module. |
| return setBool(&o.ExpandWorkspaceToModule, value) |
| |
| case "experimentalPostfixCompletions": |
| return setBool(&o.ExperimentalPostfixCompletions, value) |
| |
| case "templateExtensions": |
| switch value := value.(type) { |
| case []any: |
| return setStringSlice(&o.TemplateExtensions, value) |
| case nil: |
| o.TemplateExtensions = nil |
| default: |
| return fmt.Errorf("unexpected type %T (want JSON array of string)", value) |
| } |
| |
| case "diagnosticsDelay": |
| return setDuration(&o.DiagnosticsDelay, value) |
| |
| case "diagnosticsTrigger": |
| return setEnum(&o.DiagnosticsTrigger, value, |
| DiagnosticsOnEdit, |
| DiagnosticsOnSave) |
| |
| case "analysisProgressReporting": |
| return setBool(&o.AnalysisProgressReporting, value) |
| |
| case "allowImplicitNetworkAccess": |
| return deprecatedError("") |
| |
| case "standaloneTags": |
| return setStringSlice(&o.StandaloneTags, value) |
| |
| case "subdirWatchPatterns": |
| return setEnum(&o.SubdirWatchPatterns, value, |
| SubdirWatchPatternsOn, |
| SubdirWatchPatternsOff, |
| SubdirWatchPatternsAuto) |
| |
| case "reportAnalysisProgressAfter": |
| return setDuration(&o.ReportAnalysisProgressAfter, value) |
| |
| case "telemetryPrompt": |
| return setBool(&o.TelemetryPrompt, value) |
| |
| case "linkifyShowMessage": |
| return setBool(&o.LinkifyShowMessage, value) |
| |
| case "includeReplaceInWorkspace": |
| return setBool(&o.IncludeReplaceInWorkspace, value) |
| |
| case "zeroConfig": |
| return setBool(&o.ZeroConfig, value) |
| |
| case "pullDiagnostics": |
| return setBool(&o.PullDiagnostics, value) |
| |
| // deprecated and renamed settings |
| // |
| // These should never be deleted: there is essentially no cost |
| // to providing a better error message indefinitely; it's not |
| // as if we would ever want to recycle the name of a setting. |
| |
| // renamed |
| case "experimentalDisabledAnalyses": |
| return deprecatedError("analyses") |
| |
| case "disableDeepCompletion": |
| return deprecatedError("deepCompletion") |
| |
| case "disableFuzzyMatching": |
| return deprecatedError("fuzzyMatching") |
| |
| case "wantCompletionDocumentation": |
| return deprecatedError("completionDocumentation") |
| |
| case "wantUnimportedCompletions": |
| return deprecatedError("completeUnimported") |
| |
| case "fuzzyMatching": |
| return deprecatedError("matcher") |
| |
| case "caseSensitiveCompletion": |
| return deprecatedError("matcher") |
| |
| case "experimentalDiagnosticsDelay": |
| return deprecatedError("diagnosticsDelay") |
| |
| // deprecated |
| case "memoryMode": |
| return deprecatedError("") |
| |
| case "tempModFile": |
| return deprecatedError("") |
| |
| case "experimentalWorkspaceModule": |
| return deprecatedError("") |
| |
| case "experimentalTemplateSupport": |
| return deprecatedError("") |
| |
| case "experimentalWatchedFileDelay": |
| return deprecatedError("") |
| |
| case "experimentalPackageCacheKey": |
| return deprecatedError("") |
| |
| case "allowModfileModifications": |
| return deprecatedError("") |
| |
| case "allExperiments": |
| // golang/go#65548: this setting is a no-op, but we fail don't report it as |
| // deprecated, since the nightly VS Code injects it. |
| // |
| // If, in the future, VS Code stops injecting this, we could theoretically |
| // report an error here, but it also seems harmless to keep ignoring this |
| // setting forever. |
| |
| case "experimentalUseInvalidMetadata": |
| return deprecatedError("") |
| |
| case "newDiff": |
| return deprecatedError("") |
| |
| case "wantSuggestedFixes": |
| return deprecatedError("") |
| |
| case "noIncrementalSync": |
| return deprecatedError("") |
| |
| case "watchFileChanges": |
| return deprecatedError("") |
| |
| case "go-diff": |
| return deprecatedError("") |
| |
| default: |
| return fmt.Errorf("unexpected setting") |
| } |
| return nil |
| } |
| |
| // A SoftError is an error that does not affect the functionality of gopls. |
| type SoftError struct { |
| msg string |
| } |
| |
| func (e *SoftError) Error() string { |
| return e.msg |
| } |
| |
| // softErrorf reports a soft error related to the current option. |
| func softErrorf(format string, args ...any) error { |
| return &SoftError{fmt.Sprintf(format, args...)} |
| } |
| |
| // deprecatedError reports the current setting as deprecated. |
| // The optional replacement is suggested to the user. |
| func deprecatedError(replacement string) error { |
| msg := fmt.Sprintf("this setting is deprecated") |
| if replacement != "" { |
| msg = fmt.Sprintf("%s, use %q instead", msg, replacement) |
| } |
| return &SoftError{msg} |
| } |
| |
| // setT() and asT() helpers: the setT forms write to the 'dest *T' |
| // variable only on success, to reduce boilerplate in Option.set. |
| |
| func setBool(dest *bool, value any) error { |
| b, err := asBool(value) |
| if err != nil { |
| return err |
| } |
| *dest = b |
| return nil |
| } |
| |
| func asBool(value any) (bool, error) { |
| b, ok := value.(bool) |
| if !ok { |
| return false, fmt.Errorf("invalid type %T (want bool)", value) |
| } |
| return b, nil |
| } |
| |
| func setDuration(dest *time.Duration, value any) error { |
| str, err := asString(value) |
| if err != nil { |
| return err |
| } |
| parsed, err := time.ParseDuration(str) |
| if err != nil { |
| return err |
| } |
| *dest = parsed |
| return nil |
| } |
| |
| func setAnnotationMap(dest *map[Annotation]bool, value any) error { |
| all, err := asBoolMap[string](value) |
| if err != nil { |
| return err |
| } |
| if all == nil { |
| return nil |
| } |
| // Default to everything enabled by default. |
| m := make(map[Annotation]bool) |
| for k, enabled := range all { |
| var a Annotation |
| if err := setEnum(&a, k, |
| Nil, |
| Escape, |
| Inline, |
| Bounds); err != nil { |
| // In case of an error, process any legacy values. |
| switch k { |
| case "noEscape": |
| m[Escape] = false |
| return fmt.Errorf(`"noEscape" is deprecated, set "Escape: false" instead`) |
| case "noNilcheck": |
| m[Nil] = false |
| return fmt.Errorf(`"noNilcheck" is deprecated, set "Nil: false" instead`) |
| |
| case "noInline": |
| m[Inline] = false |
| return fmt.Errorf(`"noInline" is deprecated, set "Inline: false" instead`) |
| case "noBounds": |
| m[Bounds] = false |
| return fmt.Errorf(`"noBounds" is deprecated, set "Bounds: false" instead`) |
| default: |
| return err |
| } |
| } |
| m[a] = enabled |
| } |
| *dest = m |
| return nil |
| } |
| |
| func setBoolMap[K ~string](dest *map[K]bool, value any) error { |
| m, err := asBoolMap[K](value) |
| if err != nil { |
| return err |
| } |
| *dest = m |
| return nil |
| } |
| |
| func asBoolMap[K ~string](value any) (map[K]bool, error) { |
| all, ok := value.(map[string]any) |
| if !ok { |
| return nil, fmt.Errorf("invalid type %T (want JSON object)", value) |
| } |
| m := make(map[K]bool) |
| for a, enabled := range all { |
| b, ok := enabled.(bool) |
| if !ok { |
| return nil, fmt.Errorf("invalid type %T for object field %q", enabled, a) |
| } |
| m[K(a)] = b |
| } |
| return m, nil |
| } |
| |
| func setString(dest *string, value any) error { |
| str, err := asString(value) |
| if err != nil { |
| return err |
| } |
| *dest = str |
| return nil |
| } |
| |
| func asString(value any) (string, error) { |
| str, ok := value.(string) |
| if !ok { |
| return "", fmt.Errorf("invalid type %T (want string)", value) |
| } |
| return str, nil |
| } |
| |
| func setStringSlice(dest *[]string, value any) error { |
| slice, err := asStringSlice(value) |
| if err != nil { |
| return err |
| } |
| *dest = slice |
| return nil |
| } |
| |
| func asStringSlice(value any) ([]string, error) { |
| array, ok := value.([]any) |
| if !ok { |
| return nil, fmt.Errorf("invalid type %T (want JSON array of string)", value) |
| } |
| var slice []string |
| for _, elem := range array { |
| str, ok := elem.(string) |
| if !ok { |
| return nil, fmt.Errorf("invalid array element type %T (want string)", elem) |
| } |
| slice = append(slice, str) |
| } |
| return slice, nil |
| } |
| |
| func setEnum[S ~string](dest *S, value any, options ...S) error { |
| enum, err := asEnum(value, options...) |
| if err != nil { |
| return err |
| } |
| *dest = enum |
| return nil |
| } |
| |
| func asEnum[S ~string](value any, options ...S) (S, error) { |
| str, err := asString(value) |
| if err != nil { |
| return "", err |
| } |
| for _, opt := range options { |
| if strings.EqualFold(str, string(opt)) { |
| return opt, nil |
| } |
| } |
| return "", fmt.Errorf("invalid option %q for enum", str) |
| } |