Repository: microsoft/vscode (TypeScript)
Date: 2020-11-02 06:59:38+00:00
Task type: Bug Fix

Issue:
configurationDefaults contribution changes JSON auto-complete behavior
Reported originally by @JacksonKearl where GitLens seemed to be breaking JSON auto-complete behavior -- causing an extra `"` being added at the end.

![recording (16)](https://user-images.githubusercontent.com/641685/97644218-2495ba00-1a20-11eb-9442-b1e44789c4d4.gif)

I tracked it down to this contribution causing the issue

```json
"configurationDefaults": {
	"[json]": {
		"gitlens.codeLens.scopes": [
			"document"
		]
	}
}
```

I was able to reproduce this with a clean vscode user-dir/extensions-dir and a simple extension with that contribution


Issue discussion:

That JSON extension has `[json]` configurationDefault for the `editor.suggest.insertMode` setting:
https://github.com/microsoft/vscode/blob/master/extensions/json-language-features/package.json#L109

That configurationDefault is lost when the gitlens extension also makes a  `[json]` configurationDefault 

The bug is that all default overrides for the same language need to be merged, not set.
https://github.com/microsoft/vscode/blob/master/src/vs/platform/configuration/common/configurationRegistry.ts#L240

Solution patch:
diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts
--- a/src/vs/platform/configuration/common/configurationRegistry.ts
+++ b/src/vs/platform/configuration/common/configurationRegistry.ts
@@ -227,9 +227,9 @@ class ConfigurationRegistry implements IConfigurationRegistry {
 		for (const defaultConfiguration of defaultConfigurations) {
 			for (const key in defaultConfiguration) {
 				properties.push(key);
-				this.defaultValues[key] = defaultConfiguration[key];
 
 				if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
+					this.defaultValues[key] = { ...(this.defaultValues[key] || {}), ...defaultConfiguration[key] };
 					const property: IConfigurationPropertySchema = {
 						type: 'object',
 						default: this.defaultValues[key],
@@ -240,6 +240,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
 					this.configurationProperties[key] = property;
 					this.defaultLanguageConfigurationOverridesNode.properties![key] = property;
 				} else {
+					this.defaultValues[key] = defaultConfiguration[key];
 					const property = this.configurationProperties[key];
 					if (property) {
 						this.updatePropertyDefaultValue(key, property);


Test patch:
diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts
new file mode 100644
--- /dev/null
+++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts
@@ -0,0 +1,53 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as assert from 'assert';
+import { Registry } from 'vs/platform/registry/common/platform';
+import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
+
+suite('ConfigurationRegistry', () => {
+
+	const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
+
+	test('configuration override', async () => {
+		configurationRegistry.registerConfiguration({
+			'id': '_test_default',
+			'type': 'object',
+			'properties': {
+				'config': {
+					'type': 'object',
+				}
+			}
+		});
+		configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 1, b: 2 } }]);
+		configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 2, c: 3 } }]);
+
+		assert.deepEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 1, b: 2 });
+		assert.deepEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, c: 3 });
+	});
+
+	test('configuration override defaults - merges defaults', async () => {
+		configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 1, b: 2 } }]);
+		configurationRegistry.registerDefaultConfigurations([{ '[lang]': { a: 2, c: 3 } }]);
+
+		assert.deepEqual(configurationRegistry.getConfigurationProperties()['[lang]'].default, { a: 2, b: 2, c: 3 });
+	});
+
+	test('configuration defaults - overrides defaults', async () => {
+		configurationRegistry.registerConfiguration({
+			'id': '_test_default',
+			'type': 'object',
+			'properties': {
+				'config': {
+					'type': 'object',
+				}
+			}
+		});
+		configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 1, b: 2 } }]);
+		configurationRegistry.registerDefaultConfigurations([{ 'config': { a: 2, c: 3 } }]);
+
+		assert.deepEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, c: 3 });
+	});
+});