Path: blob/main/src/vs/platform/configuration/common/configurations.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { coalesce } from '../../../base/common/arrays.js';6import { IStringDictionary } from '../../../base/common/collections.js';7import { Emitter, Event } from '../../../base/common/event.js';8import { Disposable } from '../../../base/common/lifecycle.js';9import { deepClone, equals } from '../../../base/common/objects.js';10import { isEmptyObject, isString } from '../../../base/common/types.js';11import { ConfigurationModel } from './configurationModels.js';12import { Extensions, IConfigurationRegistry, IRegisteredConfigurationPropertySchema } from './configurationRegistry.js';13import { ILogService, NullLogService } from '../../log/common/log.js';14import { IPolicyService, PolicyDefinition } from '../../policy/common/policy.js';15import { Registry } from '../../registry/common/platform.js';16import { getErrorMessage } from '../../../base/common/errors.js';17import * as json from '../../../base/common/json.js';18import { PolicyName } from '../../../base/common/policy.js';1920export class DefaultConfiguration extends Disposable {2122private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel; properties: string[] }>());23readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;2425private _configurationModel: ConfigurationModel;26get configurationModel(): ConfigurationModel {27return this._configurationModel;28}2930constructor(private readonly logService: ILogService) {31super();32this._configurationModel = ConfigurationModel.createEmptyModel(logService);33}3435async initialize(): Promise<ConfigurationModel> {36this.resetConfigurationModel();37this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(Array.from(properties), defaultsOverrides)));38return this.configurationModel;39}4041reload(): ConfigurationModel {42this.resetConfigurationModel();43return this.configurationModel;44}4546protected onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void {47this.updateConfigurationModel(properties, Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties());48this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties });49}5051protected getConfigurationDefaultOverrides(): IStringDictionary<any> {52return {};53}5455private resetConfigurationModel(): void {56this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);57const properties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();58this.updateConfigurationModel(Object.keys(properties), properties);59}6061private updateConfigurationModel(properties: string[], configurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>): void {62const configurationDefaultsOverrides = this.getConfigurationDefaultOverrides();63for (const key of properties) {64const defaultOverrideValue = configurationDefaultsOverrides[key];65const propertySchema = configurationProperties[key];66if (defaultOverrideValue !== undefined) {67this._configurationModel.setValue(key, defaultOverrideValue);68} else if (propertySchema) {69this._configurationModel.setValue(key, deepClone(propertySchema.default));70} else {71this._configurationModel.removeValue(key);72}73}74}7576}7778export interface IPolicyConfiguration {79readonly onDidChangeConfiguration: Event<ConfigurationModel>;80readonly configurationModel: ConfigurationModel;81initialize(): Promise<ConfigurationModel>;82}8384export class NullPolicyConfiguration implements IPolicyConfiguration {85readonly onDidChangeConfiguration = Event.None;86readonly configurationModel = ConfigurationModel.createEmptyModel(new NullLogService());87async initialize() { return this.configurationModel; }88}8990export class PolicyConfiguration extends Disposable implements IPolicyConfiguration {9192private readonly _onDidChangeConfiguration = this._register(new Emitter<ConfigurationModel>());93readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;9495private readonly configurationRegistry: IConfigurationRegistry;9697private _configurationModel: ConfigurationModel;98get configurationModel() { return this._configurationModel; }99100constructor(101private readonly defaultConfiguration: DefaultConfiguration,102@IPolicyService private readonly policyService: IPolicyService,103@ILogService private readonly logService: ILogService104) {105super();106this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);107this.configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);108}109110async initialize(): Promise<ConfigurationModel> {111this.logService.trace('PolicyConfiguration#initialize');112113this.update(await this.updatePolicyDefinitions(this.defaultConfiguration.configurationModel.keys), false);114this.update(await this.updatePolicyDefinitions(Object.keys(this.configurationRegistry.getExcludedConfigurationProperties())), false);115this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames)));116this._register(this.defaultConfiguration.onDidChangeConfiguration(async ({ properties }) => this.update(await this.updatePolicyDefinitions(properties), true)));117return this._configurationModel;118}119120private async updatePolicyDefinitions(properties: string[]): Promise<string[]> {121this.logService.trace('PolicyConfiguration#updatePolicyDefinitions', properties);122const policyDefinitions: IStringDictionary<PolicyDefinition> = {};123const keys: string[] = [];124const configurationProperties = this.configurationRegistry.getConfigurationProperties();125const excludedConfigurationProperties = this.configurationRegistry.getExcludedConfigurationProperties();126127for (const key of properties) {128const config = configurationProperties[key] ?? excludedConfigurationProperties[key];129if (!config) {130// Config is removed. So add it to the list if in case it was registered as policy before131keys.push(key);132continue;133}134if (config.policy) {135if (config.type !== 'string' && config.type !== 'number' && config.type !== 'array' && config.type !== 'object' && config.type !== 'boolean') {136this.logService.warn(`Policy ${config.policy.name} has unsupported type ${config.type}`);137continue;138}139const { value } = config.policy;140keys.push(key);141policyDefinitions[config.policy.name] = {142type: config.type === 'number' ? 'number' : config.type === 'boolean' ? 'boolean' : 'string',143value,144};145}146}147148if (!isEmptyObject(policyDefinitions)) {149await this.policyService.updatePolicyDefinitions(policyDefinitions);150}151152return keys;153}154155private onDidChangePolicies(policyNames: readonly PolicyName[]): void {156this.logService.trace('PolicyConfiguration#onDidChangePolicies', policyNames);157const policyConfigurations = this.configurationRegistry.getPolicyConfigurations();158const keys = coalesce(policyNames.map(policyName => policyConfigurations.get(policyName)));159this.update(keys, true);160}161162private update(keys: string[], trigger: boolean): void {163this.logService.trace('PolicyConfiguration#update', keys);164const configurationProperties = this.configurationRegistry.getConfigurationProperties();165const excludedConfigurationProperties = this.configurationRegistry.getExcludedConfigurationProperties();166const changed: [string, any][] = [];167const wasEmpty = this._configurationModel.isEmpty();168169for (const key of keys) {170const proprety = configurationProperties[key] ?? excludedConfigurationProperties[key];171const policyName = proprety?.policy?.name;172if (policyName) {173let policyValue = this.policyService.getPolicyValue(policyName);174if (isString(policyValue) && proprety.type !== 'string') {175try {176policyValue = this.parse(policyValue);177} catch (e) {178this.logService.error(`Error parsing policy value ${policyName}:`, getErrorMessage(e));179continue;180}181}182if (wasEmpty ? policyValue !== undefined : !equals(this._configurationModel.getValue(key), policyValue)) {183changed.push([key, policyValue]);184}185} else {186if (this._configurationModel.getValue(key) !== undefined) {187changed.push([key, undefined]);188}189}190}191192if (changed.length) {193this.logService.trace('PolicyConfiguration#changed', changed);194const old = this._configurationModel;195this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);196for (const key of old.keys) {197this._configurationModel.setValue(key, old.getValue(key));198}199for (const [key, policyValue] of changed) {200if (policyValue === undefined) {201this._configurationModel.removeValue(key);202} else {203this._configurationModel.setValue(key, policyValue);204}205}206if (trigger) {207this._onDidChangeConfiguration.fire(this._configurationModel);208}209}210}211212private parse(content: string): any {213let raw: any = {};214let currentProperty: string | null = null;215let currentParent: any = [];216const previousParents: any[] = [];217const parseErrors: json.ParseError[] = [];218219function onValue(value: any) {220if (Array.isArray(currentParent)) {221(<any[]>currentParent).push(value);222} else if (currentProperty !== null) {223if (currentParent[currentProperty] !== undefined) {224throw new Error(`Duplicate property found: ${currentProperty}`);225}226currentParent[currentProperty] = value;227}228}229230const visitor: json.JSONVisitor = {231onObjectBegin: () => {232const object = {};233onValue(object);234previousParents.push(currentParent);235currentParent = object;236currentProperty = null;237},238onObjectProperty: (name: string) => {239currentProperty = name;240},241onObjectEnd: () => {242currentParent = previousParents.pop();243},244onArrayBegin: () => {245const array: any[] = [];246onValue(array);247previousParents.push(currentParent);248currentParent = array;249currentProperty = null;250},251onArrayEnd: () => {252currentParent = previousParents.pop();253},254onLiteralValue: onValue,255onError: (error: json.ParseErrorCode, offset: number, length: number) => {256parseErrors.push({ error, offset, length });257}258};259260if (content) {261json.visit(content, visitor);262raw = currentParent[0] || {};263}264265if (parseErrors.length > 0) {266throw new Error(parseErrors.map(e => getErrorMessage(e.error)).join('\n'));267}268269return raw;270}271}272273274