Repository: microsoft/vscode (TypeScript)
Date: 2022-09-29 19:42:45+00:00
Task type: Bug Fix

Issue:
Cannot change enablement of workspace disabled extension
<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
<!-- 🔧 Launch with `code --disable-extensions` to check. -->
Does this issue occur when all extensions are disabled?: Irrelevant

<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
<!-- 📣 Issues caused by an extension need to be reported directly to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->
- VS Code Version: 1.70
- OS Version: MacOS 12.5

For this example I used [GitHub Pull Requests and Issues](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github), which can only be enabled globally and not per workspace. However, I seem to be able to disable it for a workspace, after which I cannot re-enable it.

Steps to Reproduce:

1. Download and enable an extension that can only be enabled or disabled globally (e.g., [GitHub Pull Requests and Issues](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github))
2. Disable the extension for the workspace.
3. Attempt to re-enable the extension for the workspace and notice the message: `Cannot change enablement of GitHub Authentication extension in workspace because it contributes authentication providers`



Issue discussion:
/assign @sandy081 
Thanks for creating this issue! It looks like you may be using an old version of VS Code, the latest stable release is 1.70.0. Please try upgrading to the latest version and checking whether this issue remains.

Happy Coding!

Solution patch:
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
--- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -1587,14 +1587,15 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
 			for (const extension of toCheck) {
 				checked.push(extension);
 			}
-			const extensionsToDisable = installed.filter(i => {
+			const extensionsToEanbleOrDisable = installed.filter(i => {
 				if (checked.indexOf(i) !== -1) {
 					return false;
 				}
-				if (i.enablementState === enablementState) {
+				const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;
+				const isExtensionEnabled = i.enablementState === EnablementState.EnabledGlobally || i.enablementState === EnablementState.EnabledWorkspace;
+				if (enable === isExtensionEnabled) {
 					return false;
 				}
-				const enable = enablementState === EnablementState.EnabledGlobally || enablementState === EnablementState.EnabledWorkspace;
 				return (enable || !i.isBuiltin) // Include all Extensions for enablement and only non builtin extensions for disablement
 					&& (options.dependencies || options.pack)
 					&& extensions.some(extension =>
@@ -1602,10 +1603,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
 						|| (options.pack && extension.extensionPack.some(id => areSameExtensions({ id }, i.identifier)))
 					);
 			});
-			if (extensionsToDisable.length) {
-				extensionsToDisable.push(...this.getExtensionsRecursively(extensionsToDisable, installed, enablementState, options, checked));
+			if (extensionsToEanbleOrDisable.length) {
+				extensionsToEanbleOrDisable.push(...this.getExtensionsRecursively(extensionsToEanbleOrDisable, installed, enablementState, options, checked));
 			}
-			return extensionsToDisable;
+			return extensionsToEanbleOrDisable;
 		}
 		return [];
 	}
diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts
--- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts
+++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts
@@ -236,7 +236,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
 			checked.push(extension);
 		}
 
-		const extensionsToDisable: IExtension[] = [];
+		const extensionsToEnable: IExtension[] = [];
 		for (const extension of allExtensions) {
 			// Extension is already checked
 			if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) {
@@ -244,8 +244,8 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
 			}
 
 			const enablementStateOfExtension = this.getEnablementState(extension);
-			// Extension enablement state is same as the end enablement state
-			if (enablementStateOfExtension === enablementState) {
+			// Extension is enabled
+			if (this.isEnabledEnablementState(enablementStateOfExtension)) {
 				continue;
 			}
 
@@ -254,11 +254,11 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
 				(options.dependencies && e.manifest.extensionDependencies?.some(id => areSameExtensions({ id }, extension.identifier)))
 				|| (options.pack && e.manifest.extensionPack?.some(id => areSameExtensions({ id }, extension.identifier))))) {
 
-				const index = extensionsToDisable.findIndex(e => areSameExtensions(e.identifier, extension.identifier));
+				const index = extensionsToEnable.findIndex(e => areSameExtensions(e.identifier, extension.identifier));
 
 				// Extension is not aded to the disablement list so add it
 				if (index === -1) {
-					extensionsToDisable.push(extension);
+					extensionsToEnable.push(extension);
 				}
 
 				// Extension is there already in the disablement list.
@@ -266,17 +266,17 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
 					try {
 						// Replace only if the enablement state can be changed
 						this.throwErrorIfEnablementStateCannotBeChanged(extension, enablementStateOfExtension, true);
-						extensionsToDisable.splice(index, 1, extension);
+						extensionsToEnable.splice(index, 1, extension);
 					} catch (error) { /*Do not add*/ }
 				}
 			}
 		}
 
-		if (extensionsToDisable.length) {
-			extensionsToDisable.push(...this.getExtensionsToEnableRecursively(extensionsToDisable, allExtensions, enablementState, options, checked));
+		if (extensionsToEnable.length) {
+			extensionsToEnable.push(...this.getExtensionsToEnableRecursively(extensionsToEnable, allExtensions, enablementState, options, checked));
 		}
 
-		return extensionsToDisable;
+		return extensionsToEnable;
 	}
 
 	private _setUserEnablementState(extension: IExtension, newState: EnablementState): Promise<boolean> {


Test patch:
diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts
--- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts
+++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts
@@ -402,6 +402,16 @@ suite('ExtensionEnablementService Test', () => {
 		assert.strictEqual(testObject.getEnablementState(dep), EnablementState.EnabledGlobally);
 	});
 
+	test('test enable an extension in workspace with a dependency extension that has auth providers', async () => {
+		installed.push(...[aLocalExtension2('pub.a', { extensionDependencies: ['pub.b'] }), aLocalExtension('pub.b', { authentication: [{ id: 'a', label: 'a' }] })]);
+		const target = installed[0];
+		await (<TestExtensionEnablementService>testObject).waitUntilInitialized();
+		await testObject.setEnablement([target], EnablementState.DisabledWorkspace);
+		await testObject.setEnablement([target], EnablementState.EnabledWorkspace);
+		assert.ok(testObject.isEnabled(target));
+		assert.strictEqual(testObject.getEnablementState(target), EnablementState.EnabledWorkspace);
+	});
+
 	test('test enable an extension also enables packed extensions', async () => {
 		installed.push(...[aLocalExtension2('pub.a', { extensionPack: ['pub.b'] }), aLocalExtension('pub.b')]);
 		const target = installed[0];