extension/src: let gopls prompt for struct tags unless configured

With the interactive refactoring supported, the vscode-go should
start deligate the business logic to gopls (including determination
of what information needs to be collected from the user).

For adding / removing tags, vscode-go offers "promptForTags" setting
to use setting already filled in by the user previously.

When "promptForTags" is true, the vscode-go execute the command w/o
any argument and gopls will ask user questions.

When "promptForTags" is false, the vscode-go execute the command w
all the arguments from the user setting and gopls will not ask user
any questions (if the user setting is non empty).

However, the gopls today only ask for tags & transformation, to
keep the same user experience, we will also introduce two more
questions from the gopls side.

Also, remove all the default value for the adding / removing tags
as it makes hard for the extension to capture the user intention.

Fixes golang/vscode-go#4070

Change-Id: I163ac125becb0f9674c260122ca70ea67fea5d5d
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/842965
Reviewed-by: Alan Donovan <adonovan@google.com>
Auto-Submit: Hongxiang Jiang <hxjiang@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad68ce7..5954415 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
   - Diagnostics on different columns of the same line can now coexist.
 * The "Go: Generate Interface Stubs" command (`go.impl.cursor`) is now powered by `gopls` by default using interactive refactoring. Place your cursor inside the target type declaration (e.g. `type Foo struct`), and `gopls` will prompt for the interface to implement and insert the method stubs after the type declaration.
   The previous implementation remains available via the "Go: Generate Interface Stubs (legacy)" command (`go.impl.cursor.legacy`), which inserts method stubs directly at the cursor location.
+* The "Go: Add Tags to Struct Fields" and "Go: Remove Tags From Struct Fields" commands now prompt for the tags via `gopls` unless tags or options are configured in `go.addTags` or `go.removeTags`. The default values of `go.addTags` (previously `json` tags with `omitempty`) have been removed, and `go.removeTags` no longer removes all tags by default. If `promptForTags` is true, the settings are ignored and `gopls` always prompts. ([#4070](https://github.com/golang/vscode-go/issues/4070))
 * Replaced the `wmic`-based Windows process picker for attach debugging with the native `@vscode/win32-app-container-tokens` module, as the `wmic` tool itself is deprecated on Windows ([#3687](https://github.com/golang/vscode-go/issues/3687)).
 * Language client trace logging is now integrated with VS Code's `LogOutputChannel` log level system. To view verbose trace logs in the `gopls` output channel, in addition to setting `"go.trace.server": "verbose"`, you must also set the `gopls` channel log level to `Trace` (using the `Developer: Set Log Level...` command or channel settings).
 
diff --git a/docs/settings.md b/docs/settings.md
index a6c28b5..915cc86 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -27,23 +27,23 @@
 
 ### `go.addTags`
 
-Tags and options configured here will be used by the Add Tags command to add tags to struct fields. If promptForTags is true, then user will be prompted for tags and options. By default, json tags are added.
+Tags and options configured here will be used by the Add Tags command to add tags to struct fields. If promptForTags is true, or if neither tags nor options are configured, the user will be prompted for tags and the transformation rule.
 | Properties | Description |
 | --- | --- |
-| `options` | Comma separated tag=options pairs to be used by Go: Add Tags command <br/> Default: `"json=omitempty"` |
-| `promptForTags` | If true, Go: Add Tags command will prompt the user to provide tags, options, transform values instead of using the configured values <br/> Default: `false` |
-| `tags` | Comma separated tags to be used by Go: Add Tags command <br/> Default: `"json"` |
+| `options` | Comma separated tag=options pairs to be used by Go: Add Tags command <br/> Default: `""` |
+| `promptForTags` | If true, Go: Add Tags command will always prompt the user to provide tags, options, and transform values instead of using the configured values <br/> Default: `false` |
+| `tags` | Comma separated tags to be used by Go: Add Tags command <br/> Default: `""` |
 | `template` | Custom format used by Go: Add Tags command for the tag value to be applied <br/> Default: `""` |
-| `transform` | Transformation rule used by Go: Add Tags command to add tags <br/> Allowed Options: `snakecase`, `camelcase`, `lispcase`, `pascalcase`, `keep` <br/> Default: `"snakecase"` |
+| `transform` | Transformation rule used by Go: Add Tags command to add tags. Defaults to snakecase if empty. <br/> Allowed Options: `snakecase`, `camelcase`, `lispcase`, `pascalcase`, `keep` <br/> Default: `""` |
 
 Default:
 ```
 {
-	"options" :	"json=omitempty",
+	"options" :	"",
 	"promptForTags" :	false,
-	"tags" :	"json",
+	"tags" :	"",
 	"template" :	"",
-	"transform" :	"snakecase",
+	"transform" :	"",
 }
 ```
 ### `go.alternateTools`
@@ -334,10 +334,18 @@
 implicitly ignored.
 
 To suppress the hint, write an actual comment containing
-"ignore error" following the call statement, or explicitly
-assign the result to a blank variable. A handful of common
-functions such as `fmt.Println` are excluded from the
-check.
+one of the following strings:
+```
+ignore error
+discard error
+can't fail
+cannot fail
+```
+following the call statement, or explicitly assign the
+result to a blank variable.
+
+A handful of common functions such as `fmt.Println` are
+excluded from the check.
 
 
 Default: `false`
@@ -418,12 +426,12 @@
 ```
 ### `go.removeTags`
 
-Tags and options configured here will be used by the Remove Tags command to remove tags to struct fields. If promptForTags is true, then user will be prompted for tags and options. By default, all tags and options will be removed.
+Tags and options configured here will be used by the Remove Tags command to remove tags from struct fields. If promptForTags is true, or if neither tags nor options are configured, the user will be prompted for tags.
 | Properties | Description |
 | --- | --- |
-| `options` | Comma separated tag=options pairs to be used by Go: Remove Tags command <br/> Default: `"json=omitempty"` |
-| `promptForTags` | If true, Go: Remove Tags command will prompt the user to provide tags and options instead of using the configured values <br/> Default: `false` |
-| `tags` | Comma separated tags to be used by Go: Remove Tags command <br/> Default: `"json"` |
+| `options` | Comma separated tag=options pairs to be used by Go: Remove Tags command <br/> Default: `""` |
+| `promptForTags` | If true, Go: Remove Tags command will always prompt the user to provide tags and options instead of using the configured values <br/> Default: `false` |
+| `tags` | Comma separated tags to be used by Go: Remove Tags command <br/> Default: `""` |
 
 Default:
 ```
@@ -711,6 +719,28 @@
 
 
 Default: `0`
+### `memoryLimit`
+
+(Experimental) memoryLimit sets a soft memory limit (in bytes) for the gopls process, via
+runtime/debug.SetMemoryLimit. If non-positive (the default), no limit is set.
+
+On large workspaces, a single edit that invalidates many
+packages (for example a syntax error in a widely-imported
+package) can make the heap briefly grow well above the
+steady-state working set before the garbage collector
+catches up, spiking memory and, on memory-constrained
+machines, causing swapping. A soft limit makes the GC work
+harder to stay near the limit, trading some CPU for a lower
+memory peak.
+
+The limit is soft and may be exceeded. Set it comfortably above the
+steady-state working set, as too low a value causes excessive GC.
+
+Unlike the GOMEMLIMIT environment variable, this setting is
+strictly numeric; SI suffixes are not permitted.
+
+
+Default: `0`
 ### `ui.codelenses`
 
 codelenses overrides the enabled/disabled state of each of gopls'
@@ -956,7 +986,7 @@
 | `ST1022` | The documentation of an exported variable or constant should start with variable's name <br/> Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared. <br/> If every doc comment begins with the name of the item it describes, you can use the doc subcommand of the go tool and run the output through grep. <br/> See https://go.dev/doc/effective_go#commentary for more information on how to write good documentation. <br/> Available since     2020.1, non-default <br/> <br/> Default: `false` |
 | `ST1023` | Redundant type in variable declaration <br/> Available since     2021.1, non-default <br/> <br/> Default: `false` |
 | `any` | replace interface{} with any <br/> The any analyzer suggests replacing uses of the empty interface type, `interface{}`, with the `any` alias, which was introduced in Go 1.18. This is a purely stylistic change that makes code more readable. <br/> Default: `true` |
-| `appendclipped` | simplify append chains using slices.Concat <br/> The appendclipped analyzer suggests replacing chains of append calls with a single call to slices.Concat, which was added in Go 1.21. For example, append(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2). <br/> In the simple case of appending to a newly allocated slice, such as append([]T(nil), s...), the analyzer suggests the more concise slices.Clone(s). For byte slices, it will prefer bytes.Clone if the "bytes" package is already imported. <br/> This fix is only applied when the base of the append tower is a "clipped" slice, meaning its length and capacity are equal (e.g. x[:0:0] or []T{}). This is to avoid changing program behavior by eliminating intended side effects on the base slice's underlying array. <br/> This analyzer is currently disabled by default as the transformation does not preserve the nilness of the base slice in all cases; see https://go.dev/issue/73557. <br/> Default: `false` |
+| `appendclipped` | simplify append chains using slices.Concat <br/> The appendclipped analyzer suggests replacing chains of append calls with a single call to slices.Concat, which was added in Go 1.21. For example, append(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2). <br/> In the simple case of appending to a newly allocated slice, such as append([]T(nil), s...), the analyzer suggests the more concise slices.Clone(s). For byte slices, it will prefer bytes.Clone if the "bytes" package is already imported. <br/> Since the replacement (slices.Concat, or slices.Clone) allocates a new slice, any slices.Clone or bytes.Clone wrapping one of the operands is redundant and is removed, e.g. append(append([]T{}, slices.Clone(s)...), t...) becomes slices.Concat(s, t). The clone of os.Environ in append([]string(nil), os.Environ()...) is likewise elided. <br/> This fix is only applied when the base of the append tower is a "clipped" slice, meaning its length and capacity are equal (e.g. x[:0:0] or []T{}). This is to avoid changing program behavior by eliminating intended side effects on the base slice's underlying array. <br/> This analyzer is currently disabled by default as the transformation does not preserve the nilness of the base slice in all cases; see https://go.dev/issue/73557. <br/> Default: `false` |
 | `appends` | check for missing values after append <br/> This checker reports calls to append that pass no values to be appended to the slice. <br/> <pre>s := []string{"a", "b", "c"}<br/>_ = append(s)</pre><br/> Such calls are always no-ops and often indicate an underlying mistake. <br/> Default: `true` |
 | `asmdecl` | report mismatches between assembly files and Go declarations <br/> Default: `true` |
 | `assign` | check for useless assignments <br/> This checker reports assignments of the form x = x or a[i] = a[i]. These are almost always useless, and even when they aren't they are usually a mistake. <br/> Default: `true` |
@@ -986,6 +1016,7 @@
 | `hostport` | check format of addresses passed to net.Dial <br/> This analyzer flags code that produce network address strings using fmt.Sprintf, as in this example: <br/>     addr := fmt.Sprintf("%s:%d", host, 12345) // "will not work with IPv6"     ...     conn, err := net.Dial("tcp", addr)       // "when passed to dial here" <br/> The analyzer suggests a fix to use the correct approach, a call to net.JoinHostPort: <br/>     addr := net.JoinHostPort(host, "12345")     ...     conn, err := net.Dial("tcp", addr) <br/> A similar diagnostic and fix are produced for a format string of "%s:%s". <br/> <br/> Default: `true` |
 | `httpresponse` | check for mistakes using HTTP responses <br/> A common mistake when using the net/http package is to defer a function call to close the http.Response Body before checking the error that determines whether the response is valid: <br/> <pre>resp, err := http.Head(url)<br/>defer resp.Body.Close()<br/>if err != nil {<br/>	log.Fatal(err)<br/>}<br/>// (defer statement belongs here)</pre><br/> This checker helps uncover latent nil dereference bugs by reporting a diagnostic for such mistakes. <br/> Default: `true` |
 | `ifaceassert` | detect impossible interface-to-interface type assertions <br/> This checker flags type assertions v.(T) and corresponding type-switch cases in which the static type V of v is an interface that cannot possibly implement the target interface T. This occurs when V and T contain methods with the same name but different signatures. Example: <br/> <pre>var v interface {<br/>	Read()<br/>}<br/>_ = v.(io.Reader)</pre><br/> The Read method in v has a different signature than the Read method in io.Reader, so this assertion cannot succeed. <br/> Default: `true` |
+| `importcomment` | remove obsolete comments specifying canonical import path <br/> The importcomment analyzer removes comments specifying the canonical import path, such as <br/> <pre>package foo // import "example.com/foo"</pre><br/> The go command enforced these comments in GOPATH mode via "go get", but ignores them in module mode, so they are obsolete once the package belongs to a module. The fix removes the comment. <br/> Default: `true` |
 | `infertypeargs` | check for unnecessary type arguments in call expressions <br/> Explicit type arguments may be omitted from call expressions if they can be inferred from function arguments, or from other type arguments: <br/> <pre>func f[T any](T) {}<br/><br/><br/>func _() {<br/>	f[string]("foo") // string could be inferred<br/>}</pre><br/> <br/> Default: `true` |
 | `inline` | apply fixes based on 'go:fix inline' comment directives <br/> The inline analyzer inlines functions, constants, and type aliases that are marked for inlining. <br/> Use this command to apply (just) inline fixes en masse: <br/> <pre>$ go fix -inline ./...</pre><br/> ## Functions <br/> Given a function that is marked for inlining, like this one: <br/> <pre>//go:fix inline<br/>func Square(x int) int { return Pow(x, 2) }</pre><br/> this analyzer will recommend that calls to the function elsewhere, in the same or other packages, should be inlined. <br/> Inlining can be used to move off of a deprecated function: <br/> <pre>// Deprecated: prefer Pow(x, 2).<br/>//go:fix inline<br/>func Square(x int) int { return Pow(x, 2) }</pre><br/> It can also be used to move off of an obsolete package, as when the import path has changed or a higher major version is available: <br/> <pre>package pkg</pre><br/> <pre>import pkg2 "pkg/v2"</pre><br/> <pre>//go:fix inline<br/>func F() { pkg2.F(nil) }</pre><br/> Replacing a call pkg.F() by pkg2.F(nil) can have no effect on the program, so this mechanism provides a low-risk way to update large numbers of calls. We recommend, where possible, expressing the old API in terms of the new one to enable automatic migration. <br/> The inliner takes care to avoid behavior changes, even subtle ones, such as changes to the order in which argument expressions are evaluated. When it cannot safely eliminate all parameter variables, it may introduce a "binding declaration" of the form <br/> <pre>var params = args</pre><br/> to evaluate argument expressions in the correct order and bind them to parameter variables. Since the resulting code transformation may be stylistically suboptimal, such inlinings may be disabled by specifying the -inline.allow_binding_decl=false flag to the analyzer driver. <br/> (In cases where it is not safe to "reduce" a call—that is, to replace a call f(x) by the body of function f, suitably substituted—the inliner machinery is capable of replacing f by a function literal, func(){...}(). However, the inline analyzer discards all such "literalizations" unconditionally, again on grounds of style.) <br/> ## Constants <br/> Given a constant that is marked for inlining, like this one: <br/> <pre>//go:fix inline<br/>const Ptr = Pointer</pre><br/> this analyzer will recommend that uses of Ptr should be replaced with Pointer. <br/> As with functions, inlining can be used to replace deprecated constants and constants in obsolete packages. <br/> A constant definition can be marked for inlining only if it refers to another named constant. <br/> The "//go:fix inline" comment must appear before a single const declaration on its own, as above; before a const declaration that is part of a group, as in this case: <br/> <pre>const (<br/>   C = 1<br/>   //go:fix inline<br/>   Ptr = Pointer<br/>)</pre><br/> or before a group, applying to every constant in the group: <br/> <pre>//go:fix inline<br/>const (<br/>	Ptr = Pointer<br/>	Val = Value<br/>)</pre><br/> ## Type aliases <br/> Similar to named constants, a type alias can also be marked for inlining: <br/> <pre>//go:fix inline<br/>type A = newpkg.A</pre><br/> The analyzer will replace all references to the annotated type (A) by the type on the right-hand side of the declaration (newpkg.A). <br/> ## Tests <br/> A use of a function, named constant, or type alias X from its dedicated test (TestX), is not inlined, since the purpose of the test is to exercise X itself, even if it is deprecated and other uses of it should be inlined. This applies to benchmarks and examples too, and follows the usual conventions of test function naming. <br/> Similarly, if the symbol X is declared in a file named foo.go, any use of it within a file named foo_test.go will also not be inlined. <br/> Default: `true` |
 | `loopclosure` | check references to loop variables from within nested functions <br/> This analyzer reports places where a function literal references the iteration variable of an enclosing loop, and the loop calls the function in such a way (e.g. with go or defer) that it may outlive the loop iteration and possibly observe the wrong value of the variable. <br/> Note: An iteration variable can only outlive a loop iteration in Go versions <=1.21. In Go 1.22 and later, the loop variable lifetimes changed to create a new iteration variable per loop iteration. (See go.dev/issue/60078.) <br/> In this example, all the deferred functions run after the loop has completed, so all observe the final value of v [<go1.22]. <br/> <pre>for _, v := range list {<br/>    defer func() {<br/>        use(v) // incorrect<br/>    }()<br/>}</pre><br/> One fix is to create a new variable for each iteration of the loop: <br/> <pre>for _, v := range list {<br/>    v := v // new var per iteration<br/>    defer func() {<br/>        use(v) // ok<br/>    }()<br/>}</pre><br/> After Go version 1.22, the previous two for loops are equivalent and both are correct. <br/> The next example uses a go statement and has a similar problem [<go1.22]. In addition, it has a data race because the loop updates v concurrent with the goroutines accessing it. <br/> <pre>for _, v := range elem {<br/>    go func() {<br/>        use(v)  // incorrect, and a data race<br/>    }()<br/>}</pre><br/> A fix is the same as before. The checker also reports problems in goroutines started by golang.org/x/sync/errgroup.Group. A hard-to-spot variant of this form is common in parallel tests: <br/> <pre>func Test(t *testing.T) {<br/>    for _, test := range tests {<br/>        t.Run(test.name, func(t *testing.T) {<br/>            t.Parallel()<br/>            use(test) // incorrect, and a data race<br/>        })<br/>    }<br/>}</pre><br/> The t.Parallel() call causes the rest of the function to execute concurrent with the loop [<go1.22]. <br/> The analyzer reports references only in the last statement, as it is not deep enough to understand the effects of subsequent statements that might render the reference benign. ("Last statement" is defined recursively in compound statements such as if, switch, and select.) <br/> See: https://golang.org/doc/go_faq.html#closures_and_goroutines <br/> Default: `true` |
@@ -1001,8 +1032,10 @@
 | `omitzero` | suggest replacing omitempty with omitzero for struct fields <br/> The omitzero analyzer identifies uses of the `omitempty` JSON struct tag on fields that are themselves structs. For struct-typed fields, the `omitempty` tag has no effect on the behavior of json.Marshal and json.Unmarshal. The analyzer offers two suggestions: either remove the tag, or replace it with `omitzero` (added in Go 1.24), which correctly omits the field if the struct value is zero. <br/> However, some other serialization packages (notably kubebuilder, see https://book.kubebuilder.io/reference/markers.html) may have their own interpretation of the `json:",omitzero"` tag, so removing it may affect program behavior. For this reason, the omitzero modernizer will not make changes in any package that contains +kubebuilder annotations. <br/> Replacing `omitempty` with `omitzero` is a change in behavior. The original code would always encode the struct field, whereas the modified code will omit it if it is a zero-value. <br/> Default: `true` |
 | `plusbuild` | remove obsolete //+build comments <br/> The plusbuild analyzer suggests a fix to remove obsolete build tags of the form: <br/> <pre>//+build linux,amd64</pre><br/> in files that also contain a Go 1.18-style tag such as: <br/> <pre>//go:build linux && amd64</pre><br/> (It does not check that the old and new tags are consistent; that is the job of the 'buildtag' analyzer in the vet suite.) <br/> Default: `true` |
 | `printf` | check consistency of Printf format strings and arguments <br/> The check applies to calls of the formatting functions such as [fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of those functions such as [log.Printf]. It reports a variety of mistakes such as syntax errors in the format string and mismatches (of number and type) between the verbs and their arguments. <br/> See the documentation of the fmt package for the complete set of format operators and their operand types. <br/> Default: `true` |
+| `ptrtoerror` | detect inconsistent conversions of concrete types to error <br/> The ptrtoerror analyzer detects when a concrete type E is converted to the error interface inconsistently, both as a value of type E and as a pointer of type *E. Such inconsistency defeats attempts by client code to test for specific error types using type assertions or library functions such as [errors.As] and [errors.Is]. <br/> The analyzer also detects when both E and *E implement error but neither of those types is converted to error within the defining package, leaving the intended error form (E or *E) ambiguous. This diagnostic offers two alternative fixes to add declarations that make the intent explicit. <br/> Default: `true` |
 | `rangeint` | replace 3-clause for loops with for-range over integers <br/> The rangeint analyzer suggests replacing traditional for loops such as <br/> <pre>for i := 0; i < n; i++ { ... }</pre><br/> with the more idiomatic Go 1.22 style: <br/> <pre>for i := range n { ... }</pre><br/> This transformation is applied only if (a) the loop variable is not modified within the loop body and (b) the loop's limit expression is not modified within the loop, as `for range` evaluates its operand only once. <br/> Default: `true` |
 | `recursiveiter` | check for inefficient recursive iterators <br/> This analyzer reports when a function that returns an iterator (iter.Seq or iter.Seq2) calls itself as the operand of a range statement, as this is inefficient. <br/> When implementing an iterator (e.g. iter.Seq[T]) for a recursive data type such as a tree or linked list, it is tempting to recursively range over the iterator for each child element. <br/> Here's an example of a naive iterator over a binary tree: <br/> <pre>type tree struct {<br/>	value       int<br/>	left, right *tree<br/>}</pre><br/> <pre>func (t *tree) All() iter.Seq[int] {<br/>	return func(yield func(int) bool) {<br/>		if t != nil {<br/>			for elem := range t.left.All() { // "inefficient recursive iterator"<br/>				if !yield(elem) {<br/>					return<br/>				}<br/>			}<br/>			if !yield(t.value) {<br/>				return<br/>			}<br/>			for elem := range t.right.All() { // "inefficient recursive iterator"<br/>				if !yield(elem) {<br/>					return<br/>				}<br/>			}<br/>		}<br/>	}<br/>}</pre><br/> Though it correctly enumerates the elements of the tree, it hides a significant performance problem--two, in fact. Consider a balanced tree of N nodes. Iterating the root node will cause All to be called once on every node of the tree. This results in a chain of nested active range-over-func statements when yield(t.value) is called on a leaf node. <br/> The first performance problem is that each range-over-func statement must typically heap-allocate a variable, so iteration of the tree allocates as many variables as there are elements in the tree, for a total of O(N) allocations, all unnecessary. <br/> The second problem is that each call to yield for a leaf of the tree causes each of the enclosing range loops to receive a value, which they then immediately pass on to their respective yield function. This results in a chain of log(N) dynamic yield calls per element, a total of O(N*log N) dynamic calls overall, when only O(N) are necessary. <br/> A better implementation strategy for recursive iterators is to first define the "every" operator for your recursive data type, where every(f) reports whether an arbitrary predicate f(x) is true for every element x in the data type. For our tree, the every function would be: <br/> <pre>func (t *tree) every(f func(int) bool) bool {<br/>	return t == nil ||<br/>		t.left.every(f) && f(t.value) && t.right.every(f)<br/>}</pre><br/> For example, this use of the every operator prints whether every element in the tree is an even number: <br/> <pre>even := func(x int) bool { return x&1 == 0 }<br/>println(t.every(even))</pre><br/> Then the iterator can be simply expressed as a trivial wrapper around the every operator: <br/> <pre>func (t *tree) All() iter.Seq[int] {<br/>	return func(yield func(int) bool) {<br/>		_ = t.every(yield)<br/>	}<br/>}</pre><br/> In effect, tree.All computes whether yield returns true for each element, short-circuiting if it ever returns false, then discards the final boolean result. <br/> This has much better performance characteristics: it makes one dynamic call per element of the tree, and it doesn't heap-allocate anything. It is also clearer. <br/> Default: `true` |
+| `reflecttypeassert` | replace v.Interface().(T) with reflect.TypeAssert[T](v) <br/> This analyzer suggests fixes to replace two-valued type assertions on the result of (reflect.Value).Interface with reflect.TypeAssert, introduced in go1.25, which avoids the intermediate allocation of an interface value, for example: <br/> <pre>x, ok := v.Interface().(string)  ->  x, ok := reflect.TypeAssert[string](v)</pre><br/> No fix is offered for single-valued assertions, since they panic when the assertion fails whereas reflect.TypeAssert does not. Nor is a fix offered for a type switch. <br/> Default: `true` |
 | `reflecttypefor` | replace reflect.TypeOf(x) with TypeFor[T]() <br/> This analyzer suggests fixes to replace uses of reflect.TypeOf(x) with reflect.TypeFor, introduced in go1.22, when the desired runtime type is known at compile time, for example: <br/> <pre>reflect.TypeOf(uint32(0))        -> reflect.TypeFor[uint32]()<br/>reflect.TypeOf((*ast.File)(nil)) -> reflect.TypeFor[*ast.File]()</pre><br/> It also offers a fix to simplify the constructions below, which use reflect.TypeOf to return the runtime type for an interface type, <br/> <pre>reflect.TypeOf((*io.Reader)(nil)).Elem()</pre><br/> or: <br/> <pre>reflect.TypeOf([]io.Reader(nil)).Elem()</pre><br/> to: <br/> <pre>reflect.TypeFor[io.Reader]()</pre><br/> No fix is offered in cases when the runtime type is dynamic, such as: <br/> <pre>var r io.Reader = ...<br/>reflect.TypeOf(r)</pre><br/> or when the operand has potential side effects. <br/> Default: `true` |
 | `scannererr` | scannererr: report failure to check bufio.Scanner.Err <br/> This analyzer reports uses of bufio.Scanner in which the result of NewScanner is assigned to a local variable that is then used in a loop that calls Scanner.Scan, but lacks a final check of Scanner.Err, which is how I/O errors are reported. <br/> For example: <br/> <pre>sc := bufio.NewScanner(os.Stdin) // error: "bufio.Scanner sc is used in Scan loop without final check of sc.Err()"<br/>for sc.Scan() {<br/>	line := sc.Text()<br/>	use(line)<br/>}<br/>/* ...no use of sc.Err()... */</pre><br/> To avoid false positives, the analyzer is silent if the scanner is passed into or out of the function or assigned somewhere other than a local variable. <br/> It is not this analyzer's goal to ensure proper handling of errors in all cases, but merely the simple mistakes where the user may have been oblivious to the existence of the Scanner.Err method. <br/> The analyzer ignores calls to bufio.NewScanner whose argument is an infallible memory-backed io.Reader such as strings.Reader or bytes.Buffer. (In such cases, Scan may yet fail if a token or line is too long for the scanner's internal buffer, but this is rare.) <br/> If you know that errors are impossible for a given scanner, you can suppress the diagnostic thus: <br/> <pre>_ = sc.Err() // ignore error; neither reading nor scanning can fail</pre><br/> <br/> <br/> Default: `true` |
 | `shadow` | check for possible unintended shadowing of variables <br/> This analyzer check for shadowed variables. A shadowed variable is a variable declared in an inner scope with the same name and type as a variable in an outer scope, and where the outer variable is mentioned after the inner one is declared. <br/> (This definition can be refined; the module generates too many false positives and is not yet enabled by default.) <br/> For example: <br/> <pre>func BadRead(f *os.File, buf []byte) error {<br/>	var err error<br/>	for {<br/>		n, err := f.Read(buf) // shadows the function variable 'err'<br/>		if err != nil {<br/>			break // causes return of wrong value<br/>		}<br/>		foo(buf)<br/>	}<br/>	return err<br/>}</pre><br/> Default: `false` |
@@ -1012,18 +1045,19 @@
 | `simplifyrange` | check for range statement simplifications <br/> A range of the form: <br/> <pre>for x, _ = range v {...}</pre><br/> will be simplified to: <br/> <pre>for x = range v {...}</pre><br/> A range of the form: <br/> <pre>for _ = range v {...}</pre><br/> will be simplified to: <br/> <pre>for range v {...}</pre><br/> This is one of the simplifications that "gofmt -s" applies. <br/> This analyzer ignores generated code. <br/> Default: `true` |
 | `simplifyslice` | check for slice simplifications <br/> A slice expression of the form: <br/> <pre>s[a:len(s)]</pre><br/> will be simplified to: <br/> <pre>s[a:]</pre><br/> This is one of the simplifications that "gofmt -s" applies. <br/> This analyzer ignores generated code. <br/> Default: `true` |
 | `slicesbackward` | replace backward loops over slices with slices.Backward <br/> The slicesbackward analyzer suggests replacing manually-written backward loops of the form <br/> <pre>for i := len(s) - 1; i >= 0; i-- {<br/>    use(s[i])<br/>}</pre><br/> with the more readable Go 1.23 style using slices.Backward: <br/> <pre>for _, v := range slices.Backward(s) {<br/>    use(v)<br/>}</pre><br/> If the loop index is needed beyond just indexing into the slice, both the index and value variables are kept: <br/> <pre>for i, v := range slices.Backward(s) { ... }</pre><br/> Default: `true` |
+| `slicesclip` | replace three-index slice expressions with slices.Clip <br/> The slicesclip analyzer suggests replacing a full slice expression of the form <br/> <pre>x[:len(x):len(x)]</pre><br/> which clips the capacity of a slice to its length, with the simpler and more readable <br/> <pre>slices.Clip(x)</pre><br/> added in Go 1.21. <br/> Default: `true` |
 | `slicescontains` | replace loops with slices.Contains or slices.ContainsFunc <br/> The slicescontains analyzer simplifies loops that check for the existence of an element in a slice. It replaces them with calls to `slices.Contains` or `slices.ContainsFunc`, which were added in Go 1.21. <br/> If the expression for the target element has side effects, this transformation will cause those effects to occur only once, not once per tested slice element. <br/> Default: `true` |
 | `slicesdelete` | replace append-based slice deletion with slices.Delete <br/> The slicesdelete analyzer suggests replacing the idiom <br/> <pre>s = append(s[:i], s[j:]...)</pre><br/> with the more explicit <br/> <pre>s = slices.Delete(s, i, j)</pre><br/> introduced in Go 1.21. <br/> This analyzer is disabled by default. The `slices.Delete` function zeros the elements between the new length and the old length of the slice to prevent memory leaks, which is a subtle difference in behavior compared to the append-based idiom; see https://go.dev/issue/73686. <br/> Default: `false` |
 | `slicessort` | replace sort.Slice with slices.Sort for basic types <br/> The slicessort analyzer simplifies sorting slices of basic ordered types. It replaces <br/> <pre>sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })</pre><br/> with the simpler `slices.Sort(s)`, which was added in Go 1.21. <br/> Default: `true` |
 | `slog` | check for invalid structured logging calls <br/> The slog checker looks for calls to functions from the log/slog package that take alternating key-value pairs. It reports calls where an argument in a key position is neither a string nor a slog.Attr, and where a final key is missing its value. For example,it would report <br/> <pre>slog.Warn("message", 11, "k") // slog.Warn arg "11" should be a string or a slog.Attr</pre><br/> and <br/> <pre>slog.Info("message", "k1", v1, "k2") // call to slog.Info missing a final value</pre><br/> Default: `true` |
 | `sortslice` | check the argument type of sort.Slice <br/> sort.Slice requires an argument of a slice type. Check that the interface{} value passed to sort.Slice is actually a slice. <br/> Default: `true` |
 | `sqlrowserr` | sqlrowserr: report failure to check sql.Rows.Err <br/> This analyzer reports uses of sql.Rows in which the result of a query such as db.Query() is assigned to a local variable that is then used in a loop that calls Rows.Next, but lacks a final check of Rows.Err. This causes row iteration errors to be discarded. <br/> For example: <br/> <pre>rows, err := db.Query("select ...") // error: "sql.Rows rows is used in Next loop without final check of rows.Err()"<br/>if err != nil {<br/>	return err<br/>}<br/>defer rows.Close() // ignore error<br/>for rows.Next() {<br/>	var x int<br/>	if err := rows.Scan(&x); err != nil {<br/>		return err<br/>	}<br/>	use(x)<br/>}<br/>/* ...no use of rows.Err()... */</pre><br/> Correct usage of sql.Rows demands both a call to Rows.Close to release resources and a call to Rows.Err to report iteration errors. It is not critical to report resource cleanup errors, but it is crucial to report iteration errors as they would otherwise be indistinguishable from a smaller result. <br/> To avoid false positives, the analyzer is silent if the Rows is passed into or out of the function or assigned somewhere other than a local variable. <br/> It is not this analyzer's goal to ensure proper handling of errors in all cases, but merely the simple mistakes where the user may have been oblivious to the existence of the Rows.Err method. <br/> <br/> Default: `true` |
-| `stditerators` | use iterators instead of Len/At-style APIs <br/> This analyzer suggests a fix to replace each loop of the form: <br/> <pre>for i := 0; i < x.Len(); i++ {<br/>	use(x.At(i))<br/>}</pre><br/> or its "for elem := range x.Len()" equivalent by a range loop over an iterator offered by the same data type: <br/> <pre>for elem := range x.All() {<br/>	use(x.At(i)<br/>}</pre><br/> where x is one of various well-known types in the standard library. <br/> Default: `true` |
+| `stditerators` | use iterators instead of Len/At-style APIs <br/> This analyzer suggests a fix to replace each loop of the form: <br/> <pre>for i := 0; i < x.Len(); i++ {<br/>	use(x.At(i))<br/>}</pre><br/> or its "for elem := range x.Len()" equivalent by a range loop over an iterator offered by the same data type: <br/> <pre>for elem := range x.All() {<br/>	use(elem)<br/>}</pre><br/> where x is one of various well-known types in the standard library. <br/> Default: `true` |
 | `stdmethods` | check signature of methods of well-known interfaces <br/> Sometimes a type may be intended to satisfy an interface but may fail to do so because of a mistake in its method signature. For example, the result of this WriteTo method should be (int64, error), not error, to satisfy io.WriterTo: <br/> <pre>type myWriterTo struct{...}<br/>func (myWriterTo) WriteTo(w io.Writer) error { ... }</pre><br/> This check ensures that each method whose name matches one of several well-known interface methods from the standard library has the correct signature for that interface. <br/> Checked method names include: <br/> <pre>Format GobEncode GobDecode MarshalJSON MarshalXML<br/>Peek ReadByte ReadFrom ReadRune Scan Seek<br/>UnmarshalJSON UnreadByte UnreadRune WriteByte<br/>WriteTo</pre><br/> Default: `true` |
 | `stdversion` | report uses of too-new standard library symbols <br/> The stdversion analyzer reports references to symbols in the standard library that were introduced by a Go release higher than the one in force in the referring file. (Recall that the file's Go version is defined by the 'go' directive its module's go.mod file, or by a "//go:build go1.X" build tag at the top of the file.) <br/> The analyzer does not report a diagnostic for a reference to a "too new" field or method of a type that is itself "too new", as this may have false positives, for example if fields or methods are accessed through a type alias that is guarded by a Go version constraint. <br/> <br/> Default: `true` |
 | `stringintconv` | check for string(int) conversions <br/> This checker flags conversions of the form string(x) where x is an integer (but not byte or rune) type. Such conversions are discouraged because they return the UTF-8 representation of the Unicode code point x, and not a decimal string representation of x as one might expect. Furthermore, if x denotes an invalid code point, the conversion cannot be statically rejected. <br/> For conversions that intend on using the code point, consider replacing them with string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the string representation of the value in the desired base. <br/> Default: `true` |
 | `stringsbuilder` | replace += with strings.Builder <br/> This analyzer replaces repeated string += string concatenation operations with calls to Go 1.10's strings.Builder. <br/> For example: <br/> <pre>var s = "["<br/>for x := range seq {<br/>	s += x<br/>	s += "."<br/>}<br/>s += "]"<br/>use(s)</pre><br/> is replaced by: <br/> <pre>var s strings.Builder<br/>s.WriteString("[")<br/>for x := range seq {<br/>	s.WriteString(x)<br/>	s.WriteString(".")<br/>}<br/>s.WriteString("]")<br/>use(s.String())</pre><br/> This avoids quadratic memory allocation and improves performance. <br/> No diagnostics are issued in tests, where data sizes are often small and asymptotic performance is not a security concern. <br/> The analyzer requires that all references to s before the final uses are += operations. To avoid warning about trivial cases, at least one must appear within a loop. The variable s must be a local variable, not a global or parameter. <br/> All uses of the finished string must come after the last += operation. Each such use will be replaced by a call to strings.Builder's String method. (These may appear within an intervening loop or function literal, since even if s.String() is called repeatedly, it does not allocate memory.) <br/> Often the addend is a call to fmt.Sprintf, as in this example: <br/> <pre>var s string<br/>for x := range seq {<br/>	s += fmt.Sprintf("%v", x)<br/>}</pre><br/> which, once the suggested fix is applied, becomes: <br/> <pre>var s strings.Builder<br/>for x := range seq {<br/>	s.WriteString(fmt.Sprintf("%v", x))<br/>}</pre><br/> The WriteString call can be further simplified to the more efficient fmt.Fprintf(&s, "%v", x), avoiding the allocation of an intermediary. However, stringsbuilder does not perform this simplification; it requires staticcheck analyzer QF1012. (See https://go.dev/issue/76918.) <br/> Default: `true` |
-| `stringscut` | replace strings.Index etc. with strings.Cut <br/> This analyzer replaces certain patterns of use of [strings.Index] and string slicing by [strings.Cut], added in go1.18. <br/> For example: <br/> <pre>idx := strings.Index(s, substr)<br/>if idx >= 0 {<br/>    return s[:idx]<br/>}</pre><br/> is replaced by: <br/> <pre>before, _, ok := strings.Cut(s, substr)<br/>if ok {<br/>    return before<br/>}</pre><br/> And: <br/> <pre>idx := strings.Index(s, substr)<br/>if idx >= 0 {<br/>    return<br/>}</pre><br/> is replaced by: <br/> <pre>found := strings.Contains(s, substr)<br/>if found {<br/>    return<br/>}</pre><br/> It also handles variants using [strings.IndexByte] instead of Index, or the bytes package instead of strings. <br/> Fixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use. <br/> It also replaces [strings.SplitN](s, sep, 2)[0] and [strings.Split](s, sep)[0] with the "before" result of strings.Cut, when sep is a non-empty string constant: <br/> <pre>x := strings.SplitN(s, sep, 2)[0]</pre><br/> is replaced by: <br/> <pre>x, _, _ := strings.Cut(s, sep)</pre><br/> The fix is only offered when sep is a non-empty string literal. When sep is a variable or the empty string, the semantics differ (strings.Split(s, "")[0] returns the first character of s, but strings.Cut(s, "").before is ""), so no fix is suggested. <br/> Default: `true` |
+| `stringscut` | replace strings.Index etc. with strings.Cut <br/> This analyzer replaces certain patterns of use of [strings.Index] and string slicing by [strings.Cut], added in go1.18. It also replaces analogous uses of [strings.LastIndex] by [strings.CutLast], added in go1.27. <br/> For example: <br/> <pre>idx := strings.Index(s, substr)<br/>if idx >= 0 {<br/>    return s[:idx]<br/>}</pre><br/> is replaced by: <br/> <pre>before, _, ok := strings.Cut(s, substr)<br/>if ok {<br/>    return before<br/>}</pre><br/> And: <br/> <pre>idx := strings.LastIndex(s, substr)<br/>if idx >= 0 {<br/>    return s[:idx]<br/>}</pre><br/> is replaced by: <br/> <pre>before, _, ok := strings.CutLast(s, substr)<br/>if ok {<br/>    return before<br/>}</pre><br/> And: <br/> <pre>idx := strings.Index(s, substr)<br/>if idx >= 0 {<br/>    return<br/>}</pre><br/> is replaced by: <br/> <pre>found := strings.Contains(s, substr)<br/>if found {<br/>    return<br/>}</pre><br/> (LastIndex used only as a presence check is also rewritten to Contains.) <br/> It also handles variants using [strings.IndexByte] or [strings.LastIndexByte] instead of Index/LastIndex, or the bytes package instead of strings. <br/> Fixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use. CutLast fixes are offered only when the file's Go version is at least 1.27. <br/> It also replaces [strings.SplitN](s, sep, 2)[0] and [strings.Split](s, sep)[0] with the "before" result of strings.Cut, when sep is a non-empty string constant: <br/> <pre>x := strings.SplitN(s, sep, 2)[0]</pre><br/> is replaced by: <br/> <pre>x, _, _ := strings.Cut(s, sep)</pre><br/> The fix is only offered when sep is a non-empty string literal. When sep is a variable or the empty string, the semantics differ (strings.Split(s, "")[0] returns the first character of s, but strings.Cut(s, "").before is ""), so no fix is suggested. <br/> Default: `true` |
 | `stringscutprefix` | replace HasPrefix/TrimPrefix with CutPrefix <br/> The stringscutprefix analyzer simplifies a common pattern where code first checks for a prefix with `strings.HasPrefix` and then removes it with `strings.TrimPrefix`. It replaces this two-step process with a single call to `strings.CutPrefix`, introduced in Go 1.20. The analyzer also handles the equivalent functions in the `bytes` package. <br/> For example, this input: <br/> <pre>if strings.HasPrefix(s, prefix) {<br/>    use(strings.TrimPrefix(s, prefix))<br/>}</pre><br/> is fixed to: <br/> <pre>if after, ok := strings.CutPrefix(s, prefix); ok {<br/>    use(after)<br/>}</pre><br/> The analyzer also offers fixes to use CutSuffix in a similar way. This input: <br/> <pre>if strings.HasSuffix(s, suffix) {<br/>    use(strings.TrimSuffix(s, suffix))<br/>}</pre><br/> is fixed to: <br/> <pre>if before, ok := strings.CutSuffix(s, suffix); ok {<br/>    use(before)<br/>}</pre><br/> Default: `true` |
 | `stringsseq` | replace ranging over Split/Fields with SplitSeq/FieldsSeq <br/> The stringsseq analyzer improves the efficiency of iterating over substrings. It replaces <br/> <pre>for range strings.Split(...)</pre><br/> with the more efficient <br/> <pre>for range strings.SplitSeq(...)</pre><br/> which was added in Go 1.24 and avoids allocating a slice for the substrings. The analyzer also handles strings.Fields and the equivalent functions in the bytes package. <br/> Default: `true` |
 | `structtag` | check that struct field tags conform to reflect.StructTag.Get <br/> Also report certain struct tags (json, xml) used with unexported fields. <br/> Default: `true` |
@@ -1172,6 +1206,13 @@
 
 
 Default: `true`
+### `ui.moveDeclaration`
+
+(Experimental) moveDeclaration enables producing Move Declaration codeactions. The implementation
+is unfinished so we use this setting to gate its use.
+
+
+Default: `false`
 ### `ui.moveType`
 
 (Experimental) moveType enables producing Move Type codeactions. The implementation
diff --git a/extension/package.json b/extension/package.json
index 8d9adce..fbe830d 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -1698,16 +1698,16 @@
             "promptForTags": {
               "type": "boolean",
               "default": false,
-              "description": "If true, Go: Add Tags command will prompt the user to provide tags, options, transform values instead of using the configured values"
+              "description": "If true, Go: Add Tags command will always prompt the user to provide tags, options, and transform values instead of using the configured values"
             },
             "tags": {
               "type": "string",
-              "default": "json",
+              "default": "",
               "description": "Comma separated tags to be used by Go: Add Tags command"
             },
             "options": {
               "type": "string",
-              "default": "json=omitempty",
+              "default": "",
               "description": "Comma separated tag=options pairs to be used by Go: Add Tags command"
             },
             "transform": {
@@ -1719,8 +1719,8 @@
                 "pascalcase",
                 "keep"
               ],
-              "default": "snakecase",
-              "description": "Transformation rule used by Go: Add Tags command to add tags"
+              "default": "",
+              "description": "Transformation rule used by Go: Add Tags command to add tags. Defaults to snakecase if empty."
             },
             "template": {
               "type": "string",
@@ -1730,13 +1730,13 @@
           },
           "additionalProperties": false,
           "default": {
-            "tags": "json",
-            "options": "json=omitempty",
+            "tags": "",
+            "options": "",
             "promptForTags": false,
-            "transform": "snakecase",
+            "transform": "",
             "template": ""
           },
-          "description": "Tags and options configured here will be used by the Add Tags command to add tags to struct fields. If promptForTags is true, then user will be prompted for tags and options. By default, json tags are added.",
+          "description": "Tags and options configured here will be used by the Add Tags command to add tags to struct fields. If promptForTags is true, or if neither tags nor options are configured, the user will be prompted for tags and the transformation rule.",
           "scope": "resource"
         },
         "go.removeTags": {
@@ -1745,16 +1745,16 @@
             "promptForTags": {
               "type": "boolean",
               "default": false,
-              "description": "If true, Go: Remove Tags command will prompt the user to provide tags and options instead of using the configured values"
+              "description": "If true, Go: Remove Tags command will always prompt the user to provide tags and options instead of using the configured values"
             },
             "tags": {
               "type": "string",
-              "default": "json",
+              "default": "",
               "description": "Comma separated tags to be used by Go: Remove Tags command"
             },
             "options": {
               "type": "string",
-              "default": "json=omitempty",
+              "default": "",
               "description": "Comma separated tag=options pairs to be used by Go: Remove Tags command"
             }
           },
@@ -1764,7 +1764,7 @@
             "options": "",
             "promptForTags": false
           },
-          "description": "Tags and options configured here will be used by the Remove Tags command to remove tags to struct fields. If promptForTags is true, then user will be prompted for tags and options. By default, all tags and options will be removed.",
+          "description": "Tags and options configured here will be used by the Remove Tags command to remove tags from struct fields. If promptForTags is true, or if neither tags nor options are configured, the user will be prompted for tags.",
           "scope": "resource"
         },
         "go.playground": {
@@ -2167,6 +2167,12 @@
               "default": 0,
               "scope": "resource"
             },
+            "memoryLimit": {
+              "type": "number",
+              "markdownDescription": "(Experimental) memoryLimit sets a soft memory limit (in bytes) for the gopls process, via\nruntime/debug.SetMemoryLimit. If non-positive (the default), no limit is set.\n\nOn large workspaces, a single edit that invalidates many\npackages (for example a syntax error in a widely-imported\npackage) can make the heap briefly grow well above the\nsteady-state working set before the garbage collector\ncatches up, spiking memory and, on memory-constrained\nmachines, causing swapping. A soft limit makes the GC work\nharder to stay near the limit, trading some CPU for a lower\nmemory peak.\n\nThe limit is soft and may be exceeded. Set it comfortably above the\nsteady-state working set, as too low a value causes excessive GC.\n\nUnlike the GOMEMLIMIT environment variable, this setting is\nstrictly numeric; SI suffixes are not permitted.\n",
+              "default": 0,
+              "scope": "resource"
+            },
             "ui.codelenses": {
               "type": "object",
               "markdownDescription": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n  \"codelenses\": {\n    \"generate\": false,  // Don't show the `go generate` lens.\n  }\n...\n}\n```\n",
@@ -3041,7 +3047,7 @@
                 },
                 "appendclipped": {
                   "type": "boolean",
-                  "markdownDescription": "simplify append chains using slices.Concat\n\nThe appendclipped analyzer suggests replacing chains of append calls with a\nsingle call to slices.Concat, which was added in Go 1.21. For example,\nappend(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2).\n\nIn the simple case of appending to a newly allocated slice, such as\nappend([]T(nil), s...), the analyzer suggests the more concise slices.Clone(s).\nFor byte slices, it will prefer bytes.Clone if the \"bytes\" package is\nalready imported.\n\nThis fix is only applied when the base of the append tower is a\n\"clipped\" slice, meaning its length and capacity are equal (e.g.\nx[:0:0] or []T{}). This is to avoid changing program behavior by\neliminating intended side effects on the base slice's underlying\narray.\n\nThis analyzer is currently disabled by default as the\ntransformation does not preserve the nilness of the base slice in\nall cases; see https://go.dev/issue/73557.",
+                  "markdownDescription": "simplify append chains using slices.Concat\n\nThe appendclipped analyzer suggests replacing chains of append calls with a\nsingle call to slices.Concat, which was added in Go 1.21. For example,\nappend(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2).\n\nIn the simple case of appending to a newly allocated slice, such as\nappend([]T(nil), s...), the analyzer suggests the more concise slices.Clone(s).\nFor byte slices, it will prefer bytes.Clone if the \"bytes\" package is\nalready imported.\n\nSince the replacement (slices.Concat, or slices.Clone) allocates a new\nslice, any slices.Clone or bytes.Clone wrapping one of the operands is\nredundant and is removed, e.g. append(append([]T{}, slices.Clone(s)...),\nt...) becomes slices.Concat(s, t). The clone of os.Environ in\nappend([]string(nil), os.Environ()...) is likewise elided.\n\nThis fix is only applied when the base of the append tower is a\n\"clipped\" slice, meaning its length and capacity are equal (e.g.\nx[:0:0] or []T{}). This is to avoid changing program behavior by\neliminating intended side effects on the base slice's underlying\narray.\n\nThis analyzer is currently disabled by default as the\ntransformation does not preserve the nilness of the base slice in\nall cases; see https://go.dev/issue/73557.",
                   "default": false
                 },
                 "appends": {
@@ -3189,6 +3195,11 @@
                   "markdownDescription": "detect impossible interface-to-interface type assertions\n\nThis checker flags type assertions v.(T) and corresponding type-switch cases\nin which the static type V of v is an interface that cannot possibly implement\nthe target interface T. This occurs when V and T contain methods with the same\nname but different signatures. Example:\n\n\tvar v interface {\n\t\tRead()\n\t}\n\t_ = v.(io.Reader)\n\nThe Read method in v has a different signature than the Read method in\nio.Reader, so this assertion cannot succeed.",
                   "default": true
                 },
+                "importcomment": {
+                  "type": "boolean",
+                  "markdownDescription": "remove obsolete comments specifying canonical import path\n\nThe importcomment analyzer removes comments specifying the canonical\nimport path, such as\n\n\tpackage foo // import \"example.com/foo\"\n\nThe go command enforced these comments in GOPATH mode via \"go get\", but\nignores them in module mode, so they are obsolete once the package\nbelongs to a module. The fix removes the comment.",
+                  "default": true
+                },
                 "infertypeargs": {
                   "type": "boolean",
                   "markdownDescription": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n",
@@ -3264,6 +3275,11 @@
                   "markdownDescription": "check consistency of Printf format strings and arguments\n\nThe check applies to calls of the formatting functions such as\n[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of\nthose functions such as [log.Printf]. It reports a variety of\nmistakes such as syntax errors in the format string and mismatches\n(of number and type) between the verbs and their arguments.\n\nSee the documentation of the fmt package for the complete set of\nformat operators and their operand types.",
                   "default": true
                 },
+                "ptrtoerror": {
+                  "type": "boolean",
+                  "markdownDescription": "detect inconsistent conversions of concrete types to error\n\nThe ptrtoerror analyzer detects when a concrete type E is converted\nto the error interface inconsistently, both as a value of type E\nand as a pointer of type *E. Such inconsistency defeats attempts by\nclient code to test for specific error types using type assertions\nor library functions such as [errors.As] and [errors.Is].\n\nThe analyzer also detects when both E and *E implement error but\nneither of those types is converted to error within the defining\npackage, leaving the intended error form (E or *E) ambiguous. This\ndiagnostic offers two alternative fixes to add declarations that\nmake the intent explicit.",
+                  "default": true
+                },
                 "rangeint": {
                   "type": "boolean",
                   "markdownDescription": "replace 3-clause for loops with for-range over integers\n\nThe rangeint analyzer suggests replacing traditional for loops such\nas\n\n\tfor i := 0; i < n; i++ { ... }\n\nwith the more idiomatic Go 1.22 style:\n\n\tfor i := range n { ... }\n\nThis transformation is applied only if (a) the loop variable is not\nmodified within the loop body and (b) the loop's limit expression\nis not modified within the loop, as `for range` evaluates its\noperand only once.",
@@ -3274,6 +3290,11 @@
                   "markdownDescription": "check for inefficient recursive iterators\n\nThis analyzer reports when a function that returns an iterator\n(iter.Seq or iter.Seq2) calls itself as the operand of a range\nstatement, as this is inefficient.\n\nWhen implementing an iterator (e.g. iter.Seq[T]) for a recursive\ndata type such as a tree or linked list, it is tempting to\nrecursively range over the iterator for each child element.\n\nHere's an example of a naive iterator over a binary tree:\n\n\ttype tree struct {\n\t\tvalue       int\n\t\tleft, right *tree\n\t}\n\n\tfunc (t *tree) All() iter.Seq[int] {\n\t\treturn func(yield func(int) bool) {\n\t\t\tif t != nil {\n\t\t\t\tfor elem := range t.left.All() { // \"inefficient recursive iterator\"\n\t\t\t\t\tif !yield(elem) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !yield(t.value) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor elem := range t.right.All() { // \"inefficient recursive iterator\"\n\t\t\t\t\tif !yield(elem) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\nThough it correctly enumerates the elements of the tree, it hides a\nsignificant performance problem--two, in fact. Consider a balanced\ntree of N nodes. Iterating the root node will cause All to be\ncalled once on every node of the tree. This results in a chain of\nnested active range-over-func statements when yield(t.value) is\ncalled on a leaf node.\n\nThe first performance problem is that each range-over-func\nstatement must typically heap-allocate a variable, so iteration of\nthe tree allocates as many variables as there are elements in the\ntree, for a total of O(N) allocations, all unnecessary.\n\nThe second problem is that each call to yield for a leaf of the\ntree causes each of the enclosing range loops to receive a value,\nwhich they then immediately pass on to their respective yield\nfunction. This results in a chain of log(N) dynamic yield calls per\nelement, a total of O(N*log N) dynamic calls overall, when only\nO(N) are necessary.\n\nA better implementation strategy for recursive iterators is to\nfirst define the \"every\" operator for your recursive data type,\nwhere every(f) reports whether an arbitrary predicate f(x) is true\nfor every element x in the data type. For our tree, the every\nfunction would be:\n\n\tfunc (t *tree) every(f func(int) bool) bool {\n\t\treturn t == nil ||\n\t\t\tt.left.every(f) && f(t.value) && t.right.every(f)\n\t}\n\nFor example, this use of the every operator prints whether every\nelement in the tree is an even number:\n\n\teven := func(x int) bool { return x&1 == 0 }\n\tprintln(t.every(even))\n\nThen the iterator can be simply expressed as a trivial wrapper\naround the every operator:\n\n\tfunc (t *tree) All() iter.Seq[int] {\n\t\treturn func(yield func(int) bool) {\n\t\t\t_ = t.every(yield)\n\t\t}\n\t}\n\nIn effect, tree.All computes whether yield returns true for each\nelement, short-circuiting if it ever returns false, then discards\nthe final boolean result.\n\nThis has much better performance characteristics: it makes one\ndynamic call per element of the tree, and it doesn't heap-allocate\nanything. It is also clearer.",
                   "default": true
                 },
+                "reflecttypeassert": {
+                  "type": "boolean",
+                  "markdownDescription": "replace v.Interface().(T) with reflect.TypeAssert[T](v)\n\nThis analyzer suggests fixes to replace two-valued type assertions on\nthe result of (reflect.Value).Interface with reflect.TypeAssert,\nintroduced in go1.25, which avoids the intermediate allocation of an\ninterface value, for example:\n\n\tx, ok := v.Interface().(string)  ->  x, ok := reflect.TypeAssert[string](v)\n\nNo fix is offered for single-valued assertions, since they panic when\nthe assertion fails whereas reflect.TypeAssert does not. Nor is a fix\noffered for a type switch.",
+                  "default": true
+                },
                 "reflecttypefor": {
                   "type": "boolean",
                   "markdownDescription": "replace reflect.TypeOf(x) with TypeFor[T]()\n\nThis analyzer suggests fixes to replace uses of reflect.TypeOf(x) with\nreflect.TypeFor, introduced in go1.22, when the desired runtime type\nis known at compile time, for example:\n\n\treflect.TypeOf(uint32(0))        -> reflect.TypeFor[uint32]()\n\treflect.TypeOf((*ast.File)(nil)) -> reflect.TypeFor[*ast.File]()\n\nIt also offers a fix to simplify the constructions below, which use\nreflect.TypeOf to return the runtime type for an interface type,\n\n\treflect.TypeOf((*io.Reader)(nil)).Elem()\n\nor:\n\n\treflect.TypeOf([]io.Reader(nil)).Elem()\n\nto:\n\n\treflect.TypeFor[io.Reader]()\n\nNo fix is offered in cases when the runtime type is dynamic, such as:\n\n\tvar r io.Reader = ...\n\treflect.TypeOf(r)\n\nor when the operand has potential side effects.",
@@ -3319,6 +3340,11 @@
                   "markdownDescription": "replace backward loops over slices with slices.Backward\n\nThe slicesbackward analyzer suggests replacing manually-written backward\nloops of the form\n\n\tfor i := len(s) - 1; i >= 0; i-- {\n\t    use(s[i])\n\t}\n\nwith the more readable Go 1.23 style using slices.Backward:\n\n\tfor _, v := range slices.Backward(s) {\n\t    use(v)\n\t}\n\nIf the loop index is needed beyond just indexing into the slice, both\nthe index and value variables are kept:\n\n\tfor i, v := range slices.Backward(s) { ... }",
                   "default": true
                 },
+                "slicesclip": {
+                  "type": "boolean",
+                  "markdownDescription": "replace three-index slice expressions with slices.Clip\n\nThe slicesclip analyzer suggests replacing a full slice expression of\nthe form\n\n\tx[:len(x):len(x)]\n\nwhich clips the capacity of a slice to its length, with the simpler\nand more readable\n\n\tslices.Clip(x)\n\nadded in Go 1.21.",
+                  "default": true
+                },
                 "slicescontains": {
                   "type": "boolean",
                   "markdownDescription": "replace loops with slices.Contains or slices.ContainsFunc\n\nThe slicescontains analyzer simplifies loops that check for the existence of\nan element in a slice. It replaces them with calls to `slices.Contains` or\n`slices.ContainsFunc`, which were added in Go 1.21.\n\nIf the expression for the target element has side effects, this\ntransformation will cause those effects to occur only once, not\nonce per tested slice element.",
@@ -3351,7 +3377,7 @@
                 },
                 "stditerators": {
                   "type": "boolean",
-                  "markdownDescription": "use iterators instead of Len/At-style APIs\n\nThis analyzer suggests a fix to replace each loop of the form:\n\n\tfor i := 0; i < x.Len(); i++ {\n\t\tuse(x.At(i))\n\t}\n\nor its \"for elem := range x.Len()\" equivalent by a range loop over an\niterator offered by the same data type:\n\n\tfor elem := range x.All() {\n\t\tuse(x.At(i)\n\t}\n\nwhere x is one of various well-known types in the standard library.",
+                  "markdownDescription": "use iterators instead of Len/At-style APIs\n\nThis analyzer suggests a fix to replace each loop of the form:\n\n\tfor i := 0; i < x.Len(); i++ {\n\t\tuse(x.At(i))\n\t}\n\nor its \"for elem := range x.Len()\" equivalent by a range loop over an\niterator offered by the same data type:\n\n\tfor elem := range x.All() {\n\t\tuse(elem)\n\t}\n\nwhere x is one of various well-known types in the standard library.",
                   "default": true
                 },
                 "stdmethods": {
@@ -3376,7 +3402,7 @@
                 },
                 "stringscut": {
                   "type": "boolean",
-                  "markdownDescription": "replace strings.Index etc. with strings.Cut\n\nThis analyzer replaces certain patterns of use of [strings.Index] and string slicing by [strings.Cut], added in go1.18.\n\nFor example:\n\n\tidx := strings.Index(s, substr)\n\tif idx >= 0 {\n\t    return s[:idx]\n\t}\n\nis replaced by:\n\n\tbefore, _, ok := strings.Cut(s, substr)\n\tif ok {\n\t    return before\n\t}\n\nAnd:\n\n\tidx := strings.Index(s, substr)\n\tif idx >= 0 {\n\t    return\n\t}\n\nis replaced by:\n\n\tfound := strings.Contains(s, substr)\n\tif found {\n\t    return\n\t}\n\nIt also handles variants using [strings.IndexByte] instead of Index, or the bytes package instead of strings.\n\nFixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use.\n\nIt also replaces [strings.SplitN](s, sep, 2)[0] and [strings.Split](s, sep)[0] with the \"before\" result of strings.Cut, when sep is a non-empty string constant:\n\n\tx := strings.SplitN(s, sep, 2)[0]\n\nis replaced by:\n\n\tx, _, _ := strings.Cut(s, sep)\n\nThe fix is only offered when sep is a non-empty string literal. When sep is a variable or the empty string, the semantics differ (strings.Split(s, \"\")[0] returns the first character of s, but strings.Cut(s, \"\").before is \"\"), so no fix is suggested.",
+                  "markdownDescription": "replace strings.Index etc. with strings.Cut\n\nThis analyzer replaces certain patterns of use of [strings.Index] and string slicing by [strings.Cut], added in go1.18.\nIt also replaces analogous uses of [strings.LastIndex] by [strings.CutLast], added in go1.27.\n\nFor example:\n\n\tidx := strings.Index(s, substr)\n\tif idx >= 0 {\n\t    return s[:idx]\n\t}\n\nis replaced by:\n\n\tbefore, _, ok := strings.Cut(s, substr)\n\tif ok {\n\t    return before\n\t}\n\nAnd:\n\n\tidx := strings.LastIndex(s, substr)\n\tif idx >= 0 {\n\t    return s[:idx]\n\t}\n\nis replaced by:\n\n\tbefore, _, ok := strings.CutLast(s, substr)\n\tif ok {\n\t    return before\n\t}\n\nAnd:\n\n\tidx := strings.Index(s, substr)\n\tif idx >= 0 {\n\t    return\n\t}\n\nis replaced by:\n\n\tfound := strings.Contains(s, substr)\n\tif found {\n\t    return\n\t}\n\n(LastIndex used only as a presence check is also rewritten to Contains.)\n\nIt also handles variants using [strings.IndexByte] or [strings.LastIndexByte]\ninstead of Index/LastIndex, or the bytes package instead of strings.\n\nFixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use.\nCutLast fixes are offered only when the file's Go version is at least 1.27.\n\nIt also replaces [strings.SplitN](s, sep, 2)[0] and [strings.Split](s, sep)[0] with the \"before\" result of strings.Cut, when sep is a non-empty string constant:\n\n\tx := strings.SplitN(s, sep, 2)[0]\n\nis replaced by:\n\n\tx, _, _ := strings.Cut(s, sep)\n\nThe fix is only offered when sep is a non-empty string literal. When sep is a variable or the empty string, the semantics differ (strings.Split(s, \"\")[0] returns the first character of s, but strings.Cut(s, \"\").before is \"\"), so no fix is suggested.",
                   "default": true
                 },
                 "stringscutprefix": {
@@ -3591,6 +3617,12 @@
               "default": true,
               "scope": "resource"
             },
+            "ui.moveDeclaration": {
+              "type": "boolean",
+              "markdownDescription": "(Experimental) moveDeclaration enables producing Move Declaration codeactions. The implementation\nis unfinished so we use this setting to gate its use.\n",
+              "default": false,
+              "scope": "resource"
+            },
             "ui.moveType": {
               "type": "boolean",
               "markdownDescription": "(Experimental) moveType enables producing Move Type codeactions. The implementation\nis unfinished so we use this setting to gate its use.\n",
@@ -3764,7 +3796,7 @@
         },
         "go.inlayHints.ignoredError": {
           "type": "boolean",
-          "markdownDescription": "`\"ignoredError\"` inlay hints for implicitly discarded errors:\n```go\n\tf.Close()« // ignore error»\n```\nThis check inserts an `// ignore error` hint following any\nstatement that is a function call whose error result is\nimplicitly ignored.\n\nTo suppress the hint, write an actual comment containing\n\"ignore error\" following the call statement, or explicitly\nassign the result to a blank variable. A handful of common\nfunctions such as `fmt.Println` are excluded from the\ncheck.\n",
+          "markdownDescription": "`\"ignoredError\"` inlay hints for implicitly discarded errors:\n```go\n\tf.Close()« // ignore error»\n```\nThis check inserts an `// ignore error` hint following any\nstatement that is a function call whose error result is\nimplicitly ignored.\n\nTo suppress the hint, write an actual comment containing\none of the following strings:\n```\nignore error\ndiscard error\ncan't fail\ncannot fail\n```\nfollowing the call statement, or explicitly assign the\nresult to a blank variable.\n\nA handful of common functions such as `fmt.Println` are\nexcluded from the check.\n",
           "default": false
         }
       }
diff --git a/extension/src/goModifytags.ts b/extension/src/goModifytags.ts
index 5d6c1a6..ffa1749 100644
--- a/extension/src/goModifytags.ts
+++ b/extension/src/goModifytags.ts
@@ -14,7 +14,8 @@
 
 // Interface for the arguments passed to gopls.modify_tags command. URI and range
 // are required parameters collected by the extension based on the open editor,
-// and the rest of the args are collected by user input or user settings.
+// and the rest of the args are collected from user settings. gopls prompts the
+// user for the tags if neither the tags nor the options are specified.
 interface GoModifyTagsArgs {
 	URI: string;
 	range: vscode.Range;
@@ -25,17 +26,15 @@
 	removeOptions?: string;
 	transform?: string;
 	valueFormat?: string;
-	clear?: boolean;
-	clearOptions?: boolean;
 }
 
 // Interface for settings configuration for adding and removing tags
 interface GoTagsConfig {
-	[key: string]: any;
-	tags: string;
-	options: string;
-	promptForTags: boolean;
-	template: string;
+	tags?: string;
+	options?: string;
+	promptForTags?: boolean;
+	transform?: string;
+	template?: string;
 }
 
 export const addTags: CommandFactory = () => async (uri: vscode.Uri) => {
@@ -53,21 +52,15 @@
 	// Introduced since gopls v0.23.0, but older gopls will ignore this field.
 	args.modification = 'add';
 
-	const [tags, options, transformValue, template] = await getTagsAndOptions(getGoConfig()?.addTags);
-	if (!tags && !options) {
-		return;
-	}
-	if (tags) {
-		args.add = tags;
-	}
-	if (options) {
-		args.addOptions = options;
-	}
-	if (transformValue) {
-		args.transform = transformValue;
-	}
-	if (template) {
-		args.valueFormat = template;
+	// If promptForTags is set, ignore the settings so that gopls prompts the
+	// user. Otherwise, pass the settings along; gopls prompts only if neither
+	// tags nor options are set.
+	const config = getGoConfig().get<GoTagsConfig>('addTags');
+	if (!config?.promptForTags) {
+		args.add = config?.tags;
+		args.addOptions = config?.options;
+		args.transform = config?.transform;
+		args.valueFormat = config?.template;
 	}
 	await vscode.commands.executeCommand(GOPLS_MODIFY_TAGS_COMMAND, args);
 };
@@ -87,16 +80,13 @@
 	// Introduced since gopls v0.23.0, but older gopls will ignore this field.
 	args.modification = 'remove';
 
-	const [tags, options] = await getTagsAndOptions(getGoConfig()?.removeTags);
-	if (!tags && !options) {
-		args.clear = true;
-		args.clearOptions = true;
-	}
-	if (tags) {
-		args.remove = tags;
-	}
-	if (options) {
-		args.removeOptions = options;
+	// If promptForTags is set, ignore the settings so that gopls prompts the
+	// user. Otherwise, pass the settings along; gopls prompts only if neither
+	// tags nor options are set.
+	const config = getGoConfig().get<GoTagsConfig>('removeTags');
+	if (!config?.promptForTags) {
+		args.remove = config?.tags;
+		args.removeOptions = config?.options;
 	}
 	await vscode.commands.executeCommand(GOPLS_MODIFY_TAGS_COMMAND, args);
 };
@@ -118,27 +108,3 @@
 	};
 	return args;
 }
-
-async function getTagsAndOptions(config: GoTagsConfig): Promise<(string | undefined)[]> {
-	if (!config.promptForTags) {
-		return Promise.resolve([config.tags, config.options, config.transform, config.template]);
-	}
-
-	const inputTags = await vscode.window.showInputBox({
-		value: config.tags,
-		prompt: 'Enter comma separated tag names'
-	});
-	const inputOptions = await vscode.window.showInputBox({
-		value: config.options,
-		prompt: 'Enter comma separated options'
-	});
-	const transformOption = await vscode.window.showInputBox({
-		value: config.transform,
-		prompt: 'Enter transform value'
-	});
-	const template = await vscode.window.showInputBox({
-		value: config.template,
-		prompt: 'Enter template value'
-	});
-	return [inputTags, inputOptions, transformOption, template];
-}
diff --git a/extension/test/gopls/interactive.test.ts b/extension/test/gopls/interactive.test.ts
index 6c989ef..139f8d6 100644
--- a/extension/test/gopls/interactive.test.ts
+++ b/extension/test/gopls/interactive.test.ts
@@ -5,11 +5,13 @@
 
 import assert from 'assert';
 import path = require('path');
+import semver = require('semver');
 import sinon = require('sinon');
 import vscode = require('vscode');
 import { Env } from './goplsTestEnv.utils';
+import * as config from '../../src/config';
 import { updateGoVarsFromConfig } from '../../src/goInstallTools';
-import { addTags } from '../../src/goModifytags';
+import { addTags, removeTags } from '../../src/goModifytags';
 import { goplsImpl, legacyImpl } from '../../src/goImpl';
 import { MockExtensionContext } from '../mocks/MockContext';
 
@@ -29,6 +31,21 @@
 		sandbox.restore();
 	});
 
+	// stubTagsSetting overrides the "go.<section>" setting with the given value.
+	// Other settings are read as usual.
+	function stubTagsSetting(section: 'addTags' | 'removeTags', value: object) {
+		const getGoConfig = config.getGoConfig;
+		sandbox.stub(config, 'getGoConfig').callsFake((uri?: vscode.Uri) => {
+			const goConfig = getGoConfig(uri);
+			return Object.create(goConfig, {
+				get: {
+					value: (key: string, defaultValue?: unknown) =>
+						key === section ? value : goConfig.get(key, defaultValue)
+				}
+			});
+		});
+	}
+
 	suiteSetup(async () => {
 		await updateGoVarsFromConfig({});
 		const uri = vscode.Uri.file(path.join(testdataDir, 'interactive.go'));
@@ -130,9 +147,7 @@
 
 	// Regression test for golang/vscode-go#4070.
 	//
-	// TODO: Add tests with settings in place. Currently, with no custom settings,
-	// we expect prompts to be shown; when settings are configured, no prompts
-	// should be expected.
+	// Without custom settings, gopls prompts for the tags and the transform.
 	test('Add struct tags via addTags', async () => {
 		const ctx = MockExtensionContext.new();
 		const editor = await vscode.window.showTextDocument(document);
@@ -148,7 +163,90 @@
 		await addTags(ctx, env.goCtx)(editor.document.uri);
 
 		const docText = document.getText();
-		assert.match(docText, /Foo string `json:"foo,omitempty" xml:"foo"`/);
+		assert.match(docText, /Foo string `json:"foo" xml:"foo"`/);
+		ctx.teardown();
+	});
+
+	// With custom "go.addTags" settings, the settings are used without prompting.
+	test('Add struct tags via addTags with settings', async function () {
+		// gopls before v0.24.0 prompts even if the arguments specify the tags.
+		const goplsVersion = semver.coerce(env.goCtx.serverInfo?.Version);
+		if (!goplsVersion || semver.lt(goplsVersion, '0.24.0')) {
+			this.skip();
+		}
+
+		const ctx = MockExtensionContext.new();
+		const editor = await vscode.window.showTextDocument(document);
+
+		// type Foo struct {
+		//	Foo string //@loc(editor.selection, "Foo string")
+		// }
+		editor.selection = new vscode.Selection(3, 1, 3, 11);
+
+		stubTagsSetting('addTags', {
+			tags: 'yaml',
+			options: 'yaml=omitempty',
+			promptForTags: false,
+			transform: 'snakecase',
+			template: ''
+		});
+		sandbox.stub(vscode.window, 'showInputBox').rejects(new Error('unexpected showInputBox call'));
+		sandbox.stub(vscode.window, 'showQuickPick').rejects(new Error('unexpected showQuickPick call'));
+
+		await addTags(ctx, env.goCtx)(editor.document.uri);
+
+		const docText = document.getText();
+		assert.match(docText, /Foo string `yaml:"foo,omitempty"`/);
+		ctx.teardown();
+	});
+
+	// With promptForTags, gopls prompts even if tags are configured.
+	test('Add struct tags via addTags with promptForTags', async () => {
+		const ctx = MockExtensionContext.new();
+		const editor = await vscode.window.showTextDocument(document);
+
+		// type Foo struct {
+		//	Foo string //@loc(editor.selection, "Foo string")
+		// }
+		editor.selection = new vscode.Selection(3, 1, 3, 11);
+
+		stubTagsSetting('addTags', {
+			tags: 'yaml',
+			options: 'yaml=omitempty',
+			promptForTags: true,
+			transform: 'snakecase',
+			template: ''
+		});
+		sandbox.stub(vscode.window, 'showInputBox').resolves('json,xml');
+		sandbox.stub(vscode.window, 'showQuickPick').resolves({ value: 'camelcase', label: 'camelCase' } as any);
+
+		await addTags(ctx, env.goCtx)(editor.document.uri);
+
+		const docText = document.getText();
+		assert.match(docText, /Foo string `json:"foo" xml:"foo"`/);
+		ctx.teardown();
+	});
+
+	// Without custom settings, gopls prompts for the tags to remove instead of
+	// removing all tags.
+	test('Remove struct tags via removeTags', async () => {
+		const ctx = MockExtensionContext.new();
+		const editor = await vscode.window.showTextDocument(document);
+
+		// type Foo struct {
+		//	Foo string `json:"foo" xml:"foo"`
+		// }
+		const edit = new vscode.WorkspaceEdit();
+		edit.insert(document.uri, new vscode.Position(3, 11), ' `json:"foo" xml:"foo"`');
+		assert.ok(await vscode.workspace.applyEdit(edit), 'failed to add struct tags');
+		editor.selection = new vscode.Selection(3, 1, 3, 11);
+
+		sandbox.stub(vscode.window, 'showInputBox').resolves('xml');
+
+		await removeTags(ctx, env.goCtx)(editor.document.uri);
+
+		const docText = document.getText();
+		assert.match(docText, /Foo string `json:"foo"`/);
 		ctx.teardown();
 	});