Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/configuration/common/configurations.ts
3296 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 } 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<any> {
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
export class PolicyConfiguration extends Disposable implements IPolicyConfiguration {
92
93
private readonly _onDidChangeConfiguration = this._register(new Emitter<ConfigurationModel>());
94
readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;
95
96
private readonly configurationRegistry: IConfigurationRegistry;
97
98
private _configurationModel: ConfigurationModel;
99
get configurationModel() { return this._configurationModel; }
100
101
constructor(
102
private readonly defaultConfiguration: DefaultConfiguration,
103
@IPolicyService private readonly policyService: IPolicyService,
104
@ILogService private readonly logService: ILogService
105
) {
106
super();
107
this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);
108
this.configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
109
}
110
111
async initialize(): Promise<ConfigurationModel> {
112
this.logService.trace('PolicyConfiguration#initialize');
113
114
this.update(await this.updatePolicyDefinitions(this.defaultConfiguration.configurationModel.keys), false);
115
this.update(await this.updatePolicyDefinitions(Object.keys(this.configurationRegistry.getExcludedConfigurationProperties())), false);
116
this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames)));
117
this._register(this.defaultConfiguration.onDidChangeConfiguration(async ({ properties }) => this.update(await this.updatePolicyDefinitions(properties), true)));
118
return this._configurationModel;
119
}
120
121
private async updatePolicyDefinitions(properties: string[]): Promise<string[]> {
122
this.logService.trace('PolicyConfiguration#updatePolicyDefinitions', properties);
123
const policyDefinitions: IStringDictionary<PolicyDefinition> = {};
124
const keys: string[] = [];
125
const configurationProperties = this.configurationRegistry.getConfigurationProperties();
126
const excludedConfigurationProperties = this.configurationRegistry.getExcludedConfigurationProperties();
127
128
for (const key of properties) {
129
const config = configurationProperties[key] ?? excludedConfigurationProperties[key];
130
if (!config) {
131
// Config is removed. So add it to the list if in case it was registered as policy before
132
keys.push(key);
133
continue;
134
}
135
if (config.policy) {
136
if (config.type !== 'string' && config.type !== 'number' && config.type !== 'array' && config.type !== 'object' && config.type !== 'boolean') {
137
this.logService.warn(`Policy ${config.policy.name} has unsupported type ${config.type}`);
138
continue;
139
}
140
const { value } = config.policy;
141
keys.push(key);
142
policyDefinitions[config.policy.name] = {
143
type: config.type === 'number' ? 'number' : config.type === 'boolean' ? 'boolean' : 'string',
144
value,
145
};
146
}
147
}
148
149
if (!isEmptyObject(policyDefinitions)) {
150
await this.policyService.updatePolicyDefinitions(policyDefinitions);
151
}
152
153
return keys;
154
}
155
156
private onDidChangePolicies(policyNames: readonly PolicyName[]): void {
157
this.logService.trace('PolicyConfiguration#onDidChangePolicies', policyNames);
158
const policyConfigurations = this.configurationRegistry.getPolicyConfigurations();
159
const keys = coalesce(policyNames.map(policyName => policyConfigurations.get(policyName)));
160
this.update(keys, true);
161
}
162
163
private update(keys: string[], trigger: boolean): void {
164
this.logService.trace('PolicyConfiguration#update', keys);
165
const configurationProperties = this.configurationRegistry.getConfigurationProperties();
166
const excludedConfigurationProperties = this.configurationRegistry.getExcludedConfigurationProperties();
167
const changed: [string, any][] = [];
168
const wasEmpty = this._configurationModel.isEmpty();
169
170
for (const key of keys) {
171
const proprety = configurationProperties[key] ?? excludedConfigurationProperties[key];
172
const policyName = proprety?.policy?.name;
173
if (policyName) {
174
let policyValue = this.policyService.getPolicyValue(policyName);
175
if (isString(policyValue) && proprety.type !== 'string') {
176
try {
177
policyValue = this.parse(policyValue);
178
} catch (e) {
179
this.logService.error(`Error parsing policy value ${policyName}:`, getErrorMessage(e));
180
continue;
181
}
182
}
183
if (wasEmpty ? policyValue !== undefined : !equals(this._configurationModel.getValue(key), policyValue)) {
184
changed.push([key, policyValue]);
185
}
186
} else {
187
if (this._configurationModel.getValue(key) !== undefined) {
188
changed.push([key, undefined]);
189
}
190
}
191
}
192
193
if (changed.length) {
194
this.logService.trace('PolicyConfiguration#changed', changed);
195
const old = this._configurationModel;
196
this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);
197
for (const key of old.keys) {
198
this._configurationModel.setValue(key, old.getValue(key));
199
}
200
for (const [key, policyValue] of changed) {
201
if (policyValue === undefined) {
202
this._configurationModel.removeValue(key);
203
} else {
204
this._configurationModel.setValue(key, policyValue);
205
}
206
}
207
if (trigger) {
208
this._onDidChangeConfiguration.fire(this._configurationModel);
209
}
210
}
211
}
212
213
private parse(content: string): any {
214
let raw: any = {};
215
let currentProperty: string | null = null;
216
let currentParent: any = [];
217
const previousParents: any[] = [];
218
const parseErrors: json.ParseError[] = [];
219
220
function onValue(value: any) {
221
if (Array.isArray(currentParent)) {
222
(<any[]>currentParent).push(value);
223
} else if (currentProperty !== null) {
224
if (currentParent[currentProperty] !== undefined) {
225
throw new Error(`Duplicate property found: ${currentProperty}`);
226
}
227
currentParent[currentProperty] = value;
228
}
229
}
230
231
const visitor: json.JSONVisitor = {
232
onObjectBegin: () => {
233
const object = {};
234
onValue(object);
235
previousParents.push(currentParent);
236
currentParent = object;
237
currentProperty = null;
238
},
239
onObjectProperty: (name: string) => {
240
currentProperty = name;
241
},
242
onObjectEnd: () => {
243
currentParent = previousParents.pop();
244
},
245
onArrayBegin: () => {
246
const array: any[] = [];
247
onValue(array);
248
previousParents.push(currentParent);
249
currentParent = array;
250
currentProperty = null;
251
},
252
onArrayEnd: () => {
253
currentParent = previousParents.pop();
254
},
255
onLiteralValue: onValue,
256
onError: (error: json.ParseErrorCode, offset: number, length: number) => {
257
parseErrors.push({ error, offset, length });
258
}
259
};
260
261
if (content) {
262
json.visit(content, visitor);
263
raw = currentParent[0] || {};
264
}
265
266
if (parseErrors.length > 0) {
267
throw new Error(parseErrors.map(e => getErrorMessage(e.error)).join('\n'));
268
}
269
270
return raw;
271
}
272
}
273
274