package.json: add go.toolsManagement.checkForUpdates

This deprecates go.useGoProxyToCheckForToolUpdates.
And, add go.toolsManagement.checkForUpdates off mode
that avoids version check entirely.

We will start actively prompting users of this change
from the next release. We don't do that right now to avoid
prompt until dev containers update their settings and
get released.

Updates golang/vscode-go#963

Change-Id: Id49103b37e2e6e19f0313792d7d3e72648e84ce5
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/278353
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/docs/settings.md b/docs/settings.md
index c01a15e..f411e46 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -372,6 +372,14 @@
 
 Default: ``
 
+### `go.toolsManagement.checkForUpdates`
+
+Specify whether to prompt about new versions of Go and the Go tools (currently, only `gopls`) the extension depends on
+
+Allowed Values:`[proxy local off]`
+
+Default: `proxy`
+
 ### `go.trace.server`
 
 Trace the communication between VS Code and the Go language server.
@@ -392,8 +400,9 @@
 
 Default: `false`
 
-### `go.useGoProxyToCheckForToolUpdates`
+### `go.useGoProxyToCheckForToolUpdates (deprecated)`
 
+Use `go.toolsManagement.checkForUpdates` instead.
 When enabled, the extension automatically checks the Go proxy if there are updates available for Go and the Go tools (at present, only gopls) it depends on and prompts the user accordingly
 
 Default: `true`
diff --git a/package.json b/package.json
index 6c4809c..6c5a3e7 100644
--- a/package.json
+++ b/package.json
@@ -1641,10 +1641,26 @@
           "description": "The logging level the extension logs at, defaults to 'error'",
           "scope": "machine-overridable"
         },
+        "go.toolsManagement.checkForUpdates": {
+          "type": "string",
+          "default": "proxy",
+          "enum": [
+            "proxy",
+            "local",
+            "off"
+          ],
+          "enumDescriptions": [
+            "keeps notified of new releases by checking the Go module proxy (GOPROXY)",
+            "checks only the minimum tools versions required by the extension",
+            "completely disables version check (not recommended)"
+          ],
+          "markdownDescription": "Specify whether to prompt about new versions of Go and the Go tools (currently, only `gopls`) the extension depends on"
+        },
         "go.useGoProxyToCheckForToolUpdates": {
           "type": "boolean",
           "default": true,
-          "description": "When enabled, the extension automatically checks the Go proxy if there are updates available for Go and the Go tools (at present, only gopls) it depends on and prompts the user accordingly"
+          "description": "When enabled, the extension automatically checks the Go proxy if there are updates available for Go and the Go tools (at present, only gopls) it depends on and prompts the user accordingly",
+          "markdownDeprecationMessage": "Use `go.toolsManagement.checkForUpdates` instead."
         },
         "go.gotoSymbol.includeImports": {
           "type": "boolean",
diff --git a/src/goEnvironmentStatus.ts b/src/goEnvironmentStatus.ts
index b96fd03..1f6073b 100644
--- a/src/goEnvironmentStatus.ts
+++ b/src/goEnvironmentStatus.ts
@@ -17,7 +17,9 @@
 import { logVerbose } from './goLogging';
 import { addGoStatus, goEnvStatusbarItem, outputChannel, removeGoStatus } from './goStatus';
 import { getFromGlobalState, getFromWorkspaceState, updateGlobalState, updateWorkspaceState } from './stateUtils';
-import { getBinPath, getGoConfig, getGoVersion, getTempFilePath, GoVersion, rmdirRecursive } from './util';
+import {
+	getBinPath, getCheckForToolsUpdatesConfig, getGoConfig, getGoVersion,
+	getTempFilePath, GoVersion, rmdirRecursive } from './util';
 import {
 	correctBinname,
 	executableFileExists,
@@ -517,7 +519,8 @@
 
 export async function offerToInstallLatestGoVersion() {
 	const goConfig = getGoConfig();
-	if (!goConfig['useGoProxyToCheckForToolUpdates']) {
+	const checkForUpdate = getCheckForToolsUpdatesConfig(goConfig);
+	if (checkForUpdate === 'off' || checkForUpdate === 'local') {  // 'proxy' or misconfiguration..
 		return;
 	}
 
diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts
index afebeef..8ef78b9 100644
--- a/src/goLanguageServer.ts
+++ b/src/goLanguageServer.ts
@@ -57,6 +57,7 @@
 import { getFromGlobalState, updateGlobalState } from './stateUtils';
 import {
 	getBinPath,
+	getCheckForToolsUpdatesConfig,
 	getCurrentGoPath,
 	getGoConfig,
 	getGoplsConfig,
@@ -77,7 +78,7 @@
 		diagnostics: boolean;
 		documentLink: boolean;
 	};
-	checkForUpdates: boolean;
+	checkForUpdates: string;
 }
 
 // Global variables used for management of the language client.
@@ -698,7 +699,7 @@
 			documentLink: goConfig['languageServerExperimentalFeatures']['documentLink']
 		},
 		env: toolExecutionEnvironment(),
-		checkForUpdates: goConfig['useGoProxyToCheckForToolUpdates']
+		checkForUpdates: getCheckForToolsUpdatesConfig(goConfig),
 	};
 	// Don't look for the path if the server is not enabled.
 	if (!cfg.enabled) {
@@ -785,7 +786,7 @@
 	cfg: LanguageServerConfig,
 ): Promise<semver.SemVer> {
 	// Only support updating gopls for now.
-	if (tool.name !== 'gopls') {
+	if (tool.name !== 'gopls' || cfg.checkForUpdates === 'off') {
 		return null;
 	}
 
@@ -800,7 +801,7 @@
 	}
 
 	// Get the latest gopls version. If it is for nightly, using the prereleased version is ok.
-	let latestVersion = cfg.checkForUpdates ? await getLatestGoplsVersion(tool) : tool.latestVersion;
+	let latestVersion = cfg.checkForUpdates === 'local' ? tool.latestVersion : await getLatestGoplsVersion(tool);
 
 	// If we failed to get the gopls version, pick the one we know to be latest at the time of this extension's last update
 	if (!latestVersion) {
diff --git a/src/util.ts b/src/util.ts
index 9f9476c..248d569 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -166,6 +166,22 @@
 	return getConfig('gopls');
 }
 
+// getCheckForToolsUpdatesConfig returns go.toolsManagement.checkForUpdates configuration.
+export function getCheckForToolsUpdatesConfig(gocfg: vscode.WorkspaceConfiguration) {
+	// useGoProxyToCheckForToolUpdates deprecation
+	// TODO: Step 1. mark as deprecated in Dec 2020 release, and update dev containers.
+	//       Step 2. prompt users to switch config. Jan 2020
+	//       Step 3. delete useGoProxyToCheckForToolUpdates support. Feb 2020
+	const legacyCfg = gocfg.get('useGoProxyToCheckForToolUpdates');
+	if (legacyCfg === false) {
+		const cfg = gocfg.inspect('toolsManagement.checkForUpdates');
+		if (cfg.globalValue === undefined && cfg.workspaceValue === undefined) {
+			return 'local';
+		}
+	}
+	return gocfg.get('toolsManagement.checkForUpdates') as string;
+}
+
 function getConfig(section: string, uri?: vscode.Uri) {
 	if (!uri) {
 		if (vscode.window.activeTextEditor) {
diff --git a/test/gopls/update.test.ts b/test/gopls/update.test.ts
index 2c838ff..dcfe27f 100644
--- a/test/gopls/update.test.ts
+++ b/test/gopls/update.test.ts
@@ -4,11 +4,78 @@
  *--------------------------------------------------------*/
 
 import * as assert from 'assert';
+import { defaultCipherList } from 'constants';
 import moment = require('moment');
 import semver = require('semver');
 import sinon = require('sinon');
+import * as vscode from 'vscode';
 import * as lsp from '../../src/goLanguageServer';
 import { getTool, Tool } from '../../src/goTools';
+import { getCheckForToolsUpdatesConfig as getCheckForToolUpdatesConfig, getGoConfig } from '../../src/util';
+
+suite('getCheckForToolUpdatesConfig tests', () => {
+	const CHECK_FOR_UPDATES = 'toolsManagement.checkForUpdates';
+	const LEGACY_CHECK_FOR_UPDATES = 'useGoProxyToCheckForToolUpdates';
+	const defaultConfigInspector = getGoConfig().inspect(CHECK_FOR_UPDATES);
+
+	test('default is as expected', () => {
+		const {key, defaultValue, globalValue, workspaceValue} = defaultConfigInspector;
+		assert.deepStrictEqual(
+			{ key, defaultValue, globalValue, workspaceValue },
+			{ key: `go.${CHECK_FOR_UPDATES}`, defaultValue : 'proxy', globalValue: undefined, workspaceValue: undefined},
+			CHECK_FOR_UPDATES);
+		assert.strictEqual(getGoConfig().get(LEGACY_CHECK_FOR_UPDATES), true, LEGACY_CHECK_FOR_UPDATES);
+	});
+
+	// wrapper class of vscode.WorkspaceConfiguration - the object returned by
+	// vscode.getConfiguration is read-only, and doesn't allow property modification
+	// so working with sinon directly doesn't seem possible.
+	class TestWorkspaceConfiguration implements vscode.WorkspaceConfiguration {
+		constructor(private _wrapped: vscode.WorkspaceConfiguration) {}
+		public get<T>(params: string) { return this._wrapped.get<T>(params); }
+		public has(params: string) { return this._wrapped.has(params); }
+		public inspect<T>(params: string) { return this._wrapped.inspect<T>(params); }
+		public update<T>(
+			section: string, value: any,
+			configurationTarget?: vscode.ConfigurationTarget | boolean, overrideInLanguage?: boolean) {
+				return this._wrapped.update(section, value, configurationTarget, overrideInLanguage);
+		}
+		[key: string]: any;
+	}
+
+	teardown(() => { sinon.restore(); });
+
+	test('default checkForUpdates returns proxy', () => {
+		const gocfg = getGoConfig();
+		assert.strictEqual(getCheckForToolUpdatesConfig(gocfg), 'proxy');
+	});
+	test('local when new config is not set and legacy config is set to false', () => {
+		const gocfg = new TestWorkspaceConfiguration(getGoConfig());
+		sinon.stub(gocfg, 'get')
+			.withArgs(LEGACY_CHECK_FOR_UPDATES).returns(false);
+
+		assert.strictEqual(getCheckForToolUpdatesConfig(gocfg), 'local');
+	});
+	test('proxy when new config is "proxy" and legacy config is set to false', () => {
+		const gocfg = new TestWorkspaceConfiguration(getGoConfig());
+		sinon.stub(gocfg, 'get')
+			.withArgs(LEGACY_CHECK_FOR_UPDATES).returns(false)
+			.withArgs(CHECK_FOR_UPDATES).returns('proxy');
+		sinon.stub(gocfg, 'inspect').withArgs(CHECK_FOR_UPDATES).returns(
+			Object.assign({}, defaultConfigInspector, {	globalValue: 'proxy' }));
+
+		assert.strictEqual(getCheckForToolUpdatesConfig(gocfg), 'proxy');
+	});
+	test('off when new config (workspace) is "off" and legacy config is set to false', () => {
+		const gocfg = new TestWorkspaceConfiguration(getGoConfig());
+		sinon.stub(gocfg, 'get')
+			.withArgs(LEGACY_CHECK_FOR_UPDATES).returns(false)
+			.withArgs(CHECK_FOR_UPDATES).returns('off');
+		sinon.stub(gocfg, 'inspect').withArgs(CHECK_FOR_UPDATES).returns(
+			Object.assign({}, defaultConfigInspector, {	workspaceValue: 'off' }));
+		assert.strictEqual(getCheckForToolUpdatesConfig(gocfg), 'off');
+	});
+});
 
 suite('gopls update tests', () => {
 	test('prompt for update', async () => {
@@ -105,7 +172,7 @@
 				enabled: true,
 				path: 'bad/path/to/gopls',
 				version: '',
-				checkForUpdates: true,
+				checkForUpdates: 'proxy',
 				env: {},
 				features: {
 					diagnostics: true,