extension/src: add modification field to gopls.modify_tags

+ regression test

For golang/vscode-go#4070

Change-Id: I823d3d04a44f01e87a80259ce7fc8cd53cee7f6a
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/815202
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>
Reviewed-by: Madeline Kalil <mkalil@google.com>
diff --git a/extension/src/goModifytags.ts b/extension/src/goModifytags.ts
index 7ef101e..1b274b0 100644
--- a/extension/src/goModifytags.ts
+++ b/extension/src/goModifytags.ts
@@ -18,6 +18,7 @@
 interface GoModifyTagsArgs {
 	URI: string;
 	range: vscode.Range;
+	modification?: 'add' | 'remove';
 	add?: string;
 	addOptions?: string;
 	remove?: string;
@@ -48,6 +49,10 @@
 	if (!args) {
 		return;
 	}
+
+	// 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;
@@ -78,6 +83,10 @@
 	if (!args) {
 		return;
 	}
+
+	// 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;
@@ -89,7 +98,7 @@
 	if (options) {
 		args.removeOptions = options;
 	}
-	vscode.commands.executeCommand(COMMAND, args);
+	await vscode.commands.executeCommand(COMMAND, args);
 };
 
 // getCommonArgs produces the args used for calling the gopls.modify_tags command.
diff --git a/extension/test/gopls/interactive.test.ts b/extension/test/gopls/interactive.test.ts
index 5aedaf4..066e80b 100644
--- a/extension/test/gopls/interactive.test.ts
+++ b/extension/test/gopls/interactive.test.ts
@@ -9,6 +9,8 @@
 import vscode = require('vscode');
 import { Env } from './goplsTestEnv.utils';
 import { updateGoVarsFromConfig } from '../../src/goInstallTools';
+import { addTags } from '../../src/goModifytags';
+import { MockExtensionContext } from '../mocks/MockContext';
 
 suite('Interactive Refactoring', function () {
 	this.timeout(30000);
@@ -19,7 +21,9 @@
 	const testdataDir = path.join(projectDir, 'test', 'testdata', 'interactive');
 	const env = new Env();
 
-	this.afterEach(function () {
+	this.afterEach(async function () {
+		// Revert any unsaved document changes made during refactoring tests.
+		await vscode.commands.executeCommand('workbench.action.files.revert');
 		env.flushTrace(this.currentTest?.state === 'failed');
 		sandbox.restore();
 	});
@@ -122,4 +126,24 @@
 		assert.match(docText, /func \(f \*Foo\) Temporary\(\) bool/);
 		assert.match(docText, /func \(f \*Foo\) Timeout\(\) bool/);
 	});
+
+	// 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.
+	test('Add struct tags via addTags', async () => {
+		const ctx = MockExtensionContext.new();
+		const editor = await vscode.window.showTextDocument(document);
+		editor.selection = new vscode.Selection(3, 1, 3, 11);
+
+		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,omitempty" xml:"foo"`/);
+		ctx.teardown();
+	});
 });