Skip to content

Commit 3431da2

Browse files
author
Aditya Thakral
committed
Support additionalProperties
1 parent fe91c2b commit 3431da2

6 files changed

Lines changed: 108 additions & 82 deletions

File tree

extensions/emmet/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
},
5050
"emmet.includeLanguages": {
5151
"type": "object",
52-
"patternProperties": {
53-
".*": { "type": "string" }
52+
"additionalProperties": {
53+
"type": "string"
5454
},
5555
"default": {},
5656
"markdownDescription": "%emmetIncludeLanguages%"

src/vs/workbench/contrib/files/browser/files.contribution.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,9 @@ configurationRegistry.registerConfiguration({
238238
[FILES_ASSOCIATIONS_CONFIG]: {
239239
'type': 'object',
240240
'markdownDescription': nls.localize('associations', "Configure file associations to languages (e.g. `\"*.extension\": \"html\"`). These have precedence over the default associations of the languages installed."),
241-
'patternProperties': {
242-
'.*': { type: 'string' },
243-
},
241+
'additionalProperties': {
242+
'type': 'string'
243+
}
244244
},
245245
'files.encoding': {
246246
'type': 'string',

src/vs/workbench/contrib/preferences/browser/settingsTree.ts

Lines changed: 71 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -93,67 +93,81 @@ function getObjectDisplayValue(element: SettingsTreeSettingElement): IObjectData
9393
{ ...element.defaultValue, ...element.scopeValue } :
9494
element.defaultValue;
9595

96-
let items: IObjectDataItem[] = [];
97-
98-
if (element.setting.objectProperties) {
99-
const wellDefinedKeys = Object.keys(element.setting.objectProperties);
100-
const keyOptions = wellDefinedKeys.map(value => ({ value }));
101-
102-
items = items.concat(
103-
wellDefinedKeys
104-
.filter(key => !!data[key])
105-
.map(key => {
106-
const valueEnumOptions = getEnumOptionsFromSchema(element.setting.objectProperties![key]);
107-
108-
return {
109-
key: {
110-
type: 'enum',
111-
data: key,
112-
options: keyOptions,
113-
},
114-
value: {
115-
type: valueEnumOptions.length > 0 ? 'enum' : 'string',
116-
data: data[key],
117-
options: valueEnumOptions,
118-
},
119-
};
120-
})
121-
);
122-
}
96+
const items: IObjectDataItem[] = [];
12397

124-
if (element.setting.objectPatternProperties) {
125-
const patternsAndSchemas = Object
126-
.entries(element.setting.objectPatternProperties)
127-
.map(([pattern, schema]) => ({
128-
pattern: new RegExp(pattern),
129-
schema
130-
}));
98+
const { objectProperties, objectPatternProperties, objectAdditionalProperties } = element.setting;
13199

132-
const keysWithSchema = Object.keys(data)
133-
.filter(key => !!data[key] && !(key in (element.setting.objectProperties ?? {})))
134-
.map(key => {
135-
const patternAndSchema = patternsAndSchemas.find(({ pattern }) => pattern.test(key));
136-
return patternAndSchema
137-
? { key, schema: patternAndSchema.schema }
138-
: undefined;
139-
})
140-
.filter(isDefined);
100+
const patternsAndSchemas = Object
101+
.entries(objectPatternProperties ?? {})
102+
.map(([pattern, schema]) => ({
103+
pattern: new RegExp(pattern),
104+
schema
105+
}));
141106

142-
items = items.concat(keysWithSchema.map(({ key, schema }) => {
143-
const valueEnumOptions = getEnumOptionsFromSchema(schema);
107+
const allKeys = new Set<string>(Object.keys(data).concat(Object.keys(objectProperties ?? {})));
108+
const wellDefinedKeys: string[] = [];
109+
const patternKeysWithSchema = new Map<string, IJSONSchema>();
110+
const additionalKeys: string[] = [];
111+
const additionalValueEnums = getEnumOptionsFromSchema(
112+
typeof objectAdditionalProperties === 'boolean'
113+
? {}
114+
: objectAdditionalProperties ?? {}
115+
);
116+
117+
// copy the keys into appropriate buckets
118+
allKeys.forEach(key => {
119+
if (key in (objectProperties ?? {})) {
120+
wellDefinedKeys.push(key);
121+
return;
122+
}
144123

145-
return {
146-
key: { type: 'string', data: key },
147-
value: {
148-
type: valueEnumOptions.length > 0 ? 'enum' : 'string',
149-
data: data[key],
150-
options: valueEnumOptions,
151-
}
152-
};
153-
}));
154-
}
124+
const schema = patternsAndSchemas.find(({ pattern }) => pattern.test(key))?.schema;
125+
126+
if (isDefined(schema)) {
127+
patternKeysWithSchema.set(key, schema);
128+
} else {
129+
additionalKeys.push(key);
130+
}
131+
});
132+
133+
wellDefinedKeys.forEach(key => {
134+
const valueEnumOptions = getEnumOptionsFromSchema(objectProperties![key]);
135+
items.push({
136+
key: {
137+
type: 'enum',
138+
data: key,
139+
options: wellDefinedKeys.map(value => ({ value })),
140+
},
141+
value: {
142+
type: valueEnumOptions.length > 0 ? 'enum' : 'string',
143+
data: data[key],
144+
options: valueEnumOptions,
145+
},
146+
});
147+
});
155148

156-
// TODO @9at8: What should we do if properties don't match?
149+
patternKeysWithSchema.forEach((schema, key) => {
150+
const valueEnumOptions = getEnumOptionsFromSchema(schema);
151+
items.push({
152+
key: { type: 'string', data: key },
153+
value: {
154+
type: valueEnumOptions.length > 0 ? 'enum' : 'string',
155+
data: data[key],
156+
options: valueEnumOptions,
157+
},
158+
});
159+
});
160+
161+
additionalKeys.forEach(key => {
162+
items.push({
163+
key: { type: 'string', data: key },
164+
value: {
165+
type: additionalValueEnums.length > 0 ? 'enum' : 'string',
166+
data: data[key],
167+
options: additionalValueEnums,
168+
},
169+
});
170+
});
157171

158172
return items;
159173
}
@@ -984,20 +998,9 @@ export class SettingObjectRenderer extends AbstractSettingRenderer implements IT
984998
newValue[e.item.key.data] = e.item.value.data;
985999
}
9861000

987-
function sortKeys<T extends object>(obj: T) {
988-
const sortedKeys = Object.keys(obj)
989-
.sort((a, b) => a.localeCompare(b)) as Array<keyof T>;
990-
991-
const retVal: Partial<T> = {};
992-
for (const key of sortedKeys) {
993-
retVal[key] = obj[key];
994-
}
995-
return retVal;
996-
}
997-
9981001
this._onDidChangeSetting.fire({
9991002
key: template.context.setting.key,
1000-
value: Object.keys(newValue).length === 0 ? undefined : sortKeys(newValue),
1003+
value: Object.keys(newValue).length === 0 ? undefined : newValue,
10011004
type: template.context.valueType
10021005
});
10031006
}

src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/pr
1515
import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
1616
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
1717
import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
18-
import { IJSONSchemaMap } from 'vs/base/common/jsonSchema';
18+
import { IJSONSchema } from 'vs/base/common/jsonSchema';
1919

2020
export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices';
2121

@@ -468,21 +468,41 @@ export function isExcludeSetting(setting: ISetting): boolean {
468468
setting.key === 'files.watcherExclude';
469469
}
470470

471-
function isObjectRenderableSchemaMap(schemaMap: IJSONSchemaMap): boolean {
472-
return Object.values(schemaMap).every(({ type }) => type === 'string');
471+
function isObjectRenderableSchema({ type }: IJSONSchema): boolean {
472+
return type === 'string';
473473
}
474474

475-
function isObjectSetting(setting: ISetting): boolean {
476-
if (setting.type !== 'object') {
475+
function isObjectSetting({
476+
type,
477+
objectProperties,
478+
objectPatternProperties,
479+
objectAdditionalProperties
480+
}: ISetting): boolean {
481+
if (type !== 'object') {
477482
return false;
478483
}
479484

480-
if (isUndefinedOrNull(setting.objectProperties) && isUndefinedOrNull(setting.objectPatternProperties)) {
485+
// object can have any shape
486+
if (
487+
isUndefinedOrNull(objectProperties) &&
488+
isUndefinedOrNull(objectPatternProperties) &&
489+
isUndefinedOrNull(objectAdditionalProperties)
490+
) {
491+
return false;
492+
}
493+
494+
// object additional properties allow it to have any shape
495+
if (objectAdditionalProperties === true) {
481496
return false;
482497
}
483498

484-
return isObjectRenderableSchemaMap(setting.objectProperties ?? {})
485-
&& isObjectRenderableSchemaMap(setting.objectPatternProperties ?? {});
499+
return Object.values(objectProperties ?? {}).every(isObjectRenderableSchema) &&
500+
Object.values(objectPatternProperties ?? {}).every(isObjectRenderableSchema) &&
501+
(
502+
typeof objectAdditionalProperties === 'object'
503+
? isObjectRenderableSchema(objectAdditionalProperties)
504+
: true
505+
);
486506
}
487507

488508
function settingTypeEnumRenderable(_type: string | string[]) {

src/vs/workbench/services/preferences/common/preferences.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
77
import { Event } from 'vs/base/common/event';
88
import { URI } from 'vs/base/common/uri';
99
import { IRange } from 'vs/editor/common/core/range';
10-
import { IJSONSchemaMap } from 'vs/base/common/jsonSchema';
10+
import { IJSONSchemaMap, IJSONSchema } from 'vs/base/common/jsonSchema';
1111
import { ITextModel } from 'vs/editor/common/model';
1212
import { localize } from 'vs/nls';
1313
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
@@ -68,6 +68,7 @@ export interface ISetting {
6868
arrayItemType?: string;
6969
objectProperties?: IJSONSchemaMap,
7070
objectPatternProperties?: IJSONSchemaMap,
71+
objectAdditionalProperties?: boolean | IJSONSchema,
7172
enum?: string[];
7273
enumDescriptions?: string[];
7374
enumDescriptionsAreMarkdown?: boolean;

src/vs/workbench/services/preferences/common/preferencesModels.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ export class DefaultSettings extends Disposable {
622622

623623
const objectProperties = prop.type === 'object' ? prop.properties : undefined;
624624
const objectPatternProperties = prop.type === 'object' ? prop.patternProperties : undefined;
625+
const objectAdditionalProperties = prop.type === 'object' ? prop.additionalProperties : undefined;
625626

626627
result.push({
627628
key,
@@ -638,6 +639,7 @@ export class DefaultSettings extends Disposable {
638639
arrayItemType: listItemType,
639640
objectProperties,
640641
objectPatternProperties,
642+
objectAdditionalProperties,
641643
enum: prop.enum,
642644
enumDescriptions: prop.enumDescriptions || prop.markdownEnumDescriptions,
643645
enumDescriptionsAreMarkdown: !prop.enumDescriptions,

0 commit comments

Comments
 (0)