Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/configuration/common/configurations.ts
5257 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { coalesce } from '../../../base/common/arrays.js';
7
import { IStringDictionary } from '../../../base/common/collections.js';
8
import { Emitter, Event } from '../../../base/common/event.js';
9
import { Disposable } from '../../../base/common/lifecycle.js';
10
import { deepClone, equals } from '../../../base/common/objects.js';
11
import { isEmptyObject, isString } from '../../../base/common/types.js';
12
import { ConfigurationModel } from './configurationModels.js';
13
import { Extensions, IConfigurationRegistry, IRegisteredConfigurationPropertySchema } from './configurationRegistry.js';
14
import { ILogService, NullLogService } from '../../log/common/log.js';
15
import { IPolicyService, PolicyDefinition, PolicyValue } from '../../policy/common/policy.js';
16
import { Registry } from '../../registry/common/platform.js';
17
import { getErrorMessage } from '../../../base/common/errors.js';
18
import * as json from '../../../base/common/json.js';
19
import { PolicyName } from '../../../base/common/policy.js';
20
21
export class DefaultConfiguration extends Disposable {
22
23
private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel; properties: string[] }>());
24
readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;
25
26
private _configurationModel: ConfigurationModel;
27
get configurationModel(): ConfigurationModel {
28
return this._configurationModel;
29
}
30
31
constructor(private readonly logService: ILogService) {
32
super();
33
this._configurationModel = ConfigurationModel.createEmptyModel(logService);
34
}
35
36
async initialize(): Promise<ConfigurationModel> {
37
this.resetConfigurationModel();
38
this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(Array.from(properties), defaultsOverrides)));
39
return this.configurationModel;
40
}
41
42
reload(): ConfigurationModel {
43
this.resetConfigurationModel();
44
return this.configurationModel;
45
}
46
47
protected onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void {
48
this.updateConfigurationModel(properties, Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties());
49
this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties });
50
}
51
52
protected getConfigurationDefaultOverrides(): IStringDictionary<unknown> {
53
return {};
54
}
55
56
private resetConfigurationModel(): void {
57
this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);
58
const properties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
59
this.updateConfigurationModel(Object.keys(properties), properties);
60
}
61
62
private updateConfigurationModel(properties: string[], configurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>): void {
63
const configurationDefaultsOverrides = this.getConfigurationDefaultOverrides();
64
for (const key of properties) {
65
const defaultOverrideValue = configurationDefaultsOverrides[key];
66
const propertySchema = configurationProperties[key];
67
if (defaultOverrideValue !== undefined) {
68
this._configurationModel.setValue(key, defaultOverrideValue);
69
} else if (propertySchema) {
70
this._configurationModel.setValue(key, deepClone(propertySchema.default));
71
} else {
72
this._configurationModel.removeValue(key);
73
}
74
}
75
}
76
77
}
78
79
export interface IPolicyConfiguration {
80
readonly onDidChangeConfiguration: Event<ConfigurationModel>;
81
readonly configurationModel: ConfigurationModel;
82
initialize(): Promise<ConfigurationModel>;
83
}
84
85
export class NullPolicyConfiguration implements IPolicyConfiguration {
86
readonly onDidChangeConfiguration = Event.None;
87
readonly configurationModel = ConfigurationModel.createEmptyModel(new NullLogService());
88
async initialize() { return this.configurationModel; }
89
}
90
91
type ParsedType = IStringDictionary<unknown> | Array<unknown>;
92
93
export class PolicyConfiguration extends Disposable implements IPolicyConfiguration {
94
95
private readonly _onDidChangeConfiguration = this._register(new Emitter<ConfigurationModel>());
96
readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;
97
98
private readonly configurationRegistry: IConfigurationRegistry;
99
100
private _configurationModel: ConfigurationModel;
101
get configurationModel() { return this._configurationModel; }
102
103
constructor(
104
private readonly defaultConfiguration: DefaultConfiguration,
105
@IPolicyService private readonly policyService: IPolicyService,
106
@ILogService private readonly logService: ILogService
107
) {
108
super();
109
this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);
110
this.configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
111
}
112
113
async initialize(): Promise<ConfigurationModel> {
114
this.logService.trace('PolicyConfiguration#initialize');
115
116
this.update(await this.updatePolicyDefinitions(this.defaultConfiguration.configurationModel.keys), false);
117
this.update(await this.updatePolicyDefinitions(Object.keys(this.configurationRegistry.getExcludedConfigurationProperties())), false);
118
this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames)));
119
this._register(this.defaultConfiguration.onDidChangeConfiguration(async ({ properties }) => this.update(await this.updatePolicyDefinitions(properties), true)));
120
return this._configurationModel;
121
}
122
123
private async updatePolicyDefinitions(properties: string[]): Promise<string[]> {
124
this.logService.trace('PolicyConfiguration#updatePolicyDefinitions', properties);
125
const policyDefinitions: IStringDictionary<PolicyDefinition> = {};
126
const keys: string[] = [];
127
const configurationProperties = this.configurationRegistry.getConfigurationProperties();
128
const excludedConfigurationProperties = this.configurationRegistry.getExcludedConfigurationProperties();
129
130
for (const key of properties) {
131
const config = configurationProperties[key] ?? excludedConfigurationProperties[key];
132
if (!config) {
133
// Config is removed. So add it to the list if in case it was registered as policy before
134
keys.push(key);
135
continue;
136
}
137
if (config.policy) {
138
if (config.type !== 'string' && config.type !== 'number' && config.type !== 'array' && config.type !== 'object' && config.type !== 'boolean') {
139
this.logService.warn(`Policy ${config.policy.name} has unsupported type ${config.type}`);
140
continue;
141
}
142
const { value } = config.policy;
143
keys.push(key);
144
policyDefinitions[config.policy.name] = {
145
type: config.type === 'number' ? 'number' : config.type === 'boolean' ? 'boolean' : 'string',
146
value,
147
};
148
}
149
}
150
151
if (!isEmptyObject(policyDefinitions)) {
152
await this.policyService.updatePolicyDefinitions(policyDefinitions);
153
}
154
155
return keys;
156
}
157
158
private onDidChangePolicies(policyNames: readonly PolicyName[]): void {
159
this.logService.trace('PolicyConfiguration#onDidChangePolicies', policyNames);
160
const policyConfigurations = this.configurationRegistry.getPolicyConfigurations();
161
const keys = coalesce(policyNames.map(policyName => policyConfigurations.get(policyName)));
162
this.update(keys, true);
163
}
164
165
private update(keys: string[], trigger: boolean): void {
166
this.logService.trace('PolicyConfiguration#update', keys);
167
const configurationProperties = this.configurationRegistry.getConfigurationProperties();
168
const excludedConfigurationProperties = this.configurationRegistry.getExcludedConfigurationProperties();
169
const changed: [string, unknown][] = [];
170
const wasEmpty = this._configurationModel.isEmpty();
171
172
for (const key of keys) {
173
const proprety = configurationProperties[key] ?? excludedConfigurationProperties[key];
174
const policyName = proprety?.policy?.name;
175
if (policyName) {
176
let policyValue: PolicyValue | ParsedType | undefined = this.policyService.getPolicyValue(policyName);
177
if (isString(policyValue) && proprety.type !== 'string') {
178
try {
179
policyValue = this.parse(policyValue);
180
} catch (e) {
181
this.logService.error(`Error parsing policy value ${policyName}:`, getErrorMessage(e));
182
continue;
183
}
184
}
185
if (wasEmpty ? policyValue !== undefined : !equals(this._configurationModel.getValue(key), policyValue)) {
186
changed.push([key, policyValue]);
187
}
188
} else {
189
if (this._configurationModel.getValue(key) !== undefined) {
190
changed.push([key, undefined]);
191
}
192
}
193
}
194
195
if (changed.length) {
196
this.logService.trace('PolicyConfiguration#changed', changed);
197
const old = this._configurationModel;
198
this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);
199
for (const key of old.keys) {
200
this._configurationModel.setValue(key, old.getValue(key));
201
}
202
for (const [key, policyValue] of changed) {
203
if (policyValue === undefined) {
204
this._configurationModel.removeValue(key);
205
} else {
206
this._configurationModel.setValue(key, policyValue);
207
}
208
}
209
if (trigger) {
210
this._onDidChangeConfiguration.fire(this._configurationModel);
211
}
212
}
213
}
214
215
private parse(content: string): ParsedType {
216
let raw: ParsedType = {};
217
let currentProperty: string | null = null;
218
let currentParent: ParsedType = [];
219
const previousParents: Array<ParsedType> = [];
220
const parseErrors: json.ParseError[] = [];
221
222
function onValue(value: unknown) {
223
if (Array.isArray(currentParent)) {
224
currentParent.push(value);
225
} else if (currentProperty !== null) {
226
if (currentParent[currentProperty] !== undefined) {
227
throw new Error(`Duplicate property found: ${currentProperty}`);
228
}
229
currentParent[currentProperty] = value;
230
}
231
}
232
233
const visitor: json.JSONVisitor = {
234
onObjectBegin: () => {
235
const object = {};
236
onValue(object);
237
previousParents.push(currentParent);
238
currentParent = object;
239
currentProperty = null;
240
},
241
onObjectProperty: (name: string) => {
242
currentProperty = name;
243
},
244
onObjectEnd: () => {
245
currentParent = previousParents.pop()!;
246
},
247
onArrayBegin: () => {
248
const array: unknown[] = [];
249
onValue(array);
250
previousParents.push(currentParent);
251
currentParent = array;
252
currentProperty = null;
253
},
254
onArrayEnd: () => {
255
currentParent = previousParents.pop()!;
256
},
257
onLiteralValue: onValue,
258
onError: (error: json.ParseErrorCode, offset: number, length: number) => {
259
parseErrors.push({ error, offset, length });
260
}
261
};
262
263
if (content) {
264
json.visit(content, visitor);
265
raw = (currentParent[0] as ParsedType | undefined) || raw;
266
}
267
268
if (parseErrors.length > 0) {
269
throw new Error(parseErrors.map(e => getErrorMessage(e.error)).join('\n'));
270
}
271
272
return raw;
273
}
274
}
275
276