Path: blob/main/src/vs/platform/configuration/common/configurations.ts
5257 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, PolicyValue } 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<unknown> {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}8990type ParsedType = IStringDictionary<unknown> | Array<unknown>;9192export class PolicyConfiguration extends Disposable implements IPolicyConfiguration {9394private readonly _onDidChangeConfiguration = this._register(new Emitter<ConfigurationModel>());95readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;9697private readonly configurationRegistry: IConfigurationRegistry;9899private _configurationModel: ConfigurationModel;100get configurationModel() { return this._configurationModel; }101102constructor(103private readonly defaultConfiguration: DefaultConfiguration,104@IPolicyService private readonly policyService: IPolicyService,105@ILogService private readonly logService: ILogService106) {107super();108this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);109this.configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);110}111112async initialize(): Promise<ConfigurationModel> {113this.logService.trace('PolicyConfiguration#initialize');114115this.update(await this.updatePolicyDefinitions(this.defaultConfiguration.configurationModel.keys), false);116this.update(await this.updatePolicyDefinitions(Object.keys(this.configurationRegistry.getExcludedConfigurationProperties())), false);117this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames)));118this._register(this.defaultConfiguration.onDidChangeConfiguration(async ({ properties }) => this.update(await this.updatePolicyDefinitions(properties), true)));119return this._configurationModel;120}121122private async updatePolicyDefinitions(properties: string[]): Promise<string[]> {123this.logService.trace('PolicyConfiguration#updatePolicyDefinitions', properties);124const policyDefinitions: IStringDictionary<PolicyDefinition> = {};125const keys: string[] = [];126const configurationProperties = this.configurationRegistry.getConfigurationProperties();127const excludedConfigurationProperties = this.configurationRegistry.getExcludedConfigurationProperties();128129for (const key of properties) {130const config = configurationProperties[key] ?? excludedConfigurationProperties[key];131if (!config) {132// Config is removed. So add it to the list if in case it was registered as policy before133keys.push(key);134continue;135}136if (config.policy) {137if (config.type !== 'string' && config.type !== 'number' && config.type !== 'array' && config.type !== 'object' && config.type !== 'boolean') {138this.logService.warn(`Policy ${config.policy.name} has unsupported type ${config.type}`);139continue;140}141const { value } = config.policy;142keys.push(key);143policyDefinitions[config.policy.name] = {144type: config.type === 'number' ? 'number' : config.type === 'boolean' ? 'boolean' : 'string',145value,146};147}148}149150if (!isEmptyObject(policyDefinitions)) {151await this.policyService.updatePolicyDefinitions(policyDefinitions);152}153154return keys;155}156157private onDidChangePolicies(policyNames: readonly PolicyName[]): void {158this.logService.trace('PolicyConfiguration#onDidChangePolicies', policyNames);159const policyConfigurations = this.configurationRegistry.getPolicyConfigurations();160const keys = coalesce(policyNames.map(policyName => policyConfigurations.get(policyName)));161this.update(keys, true);162}163164private update(keys: string[], trigger: boolean): void {165this.logService.trace('PolicyConfiguration#update', keys);166const configurationProperties = this.configurationRegistry.getConfigurationProperties();167const excludedConfigurationProperties = this.configurationRegistry.getExcludedConfigurationProperties();168const changed: [string, unknown][] = [];169const wasEmpty = this._configurationModel.isEmpty();170171for (const key of keys) {172const proprety = configurationProperties[key] ?? excludedConfigurationProperties[key];173const policyName = proprety?.policy?.name;174if (policyName) {175let policyValue: PolicyValue | ParsedType | undefined = this.policyService.getPolicyValue(policyName);176if (isString(policyValue) && proprety.type !== 'string') {177try {178policyValue = this.parse(policyValue);179} catch (e) {180this.logService.error(`Error parsing policy value ${policyName}:`, getErrorMessage(e));181continue;182}183}184if (wasEmpty ? policyValue !== undefined : !equals(this._configurationModel.getValue(key), policyValue)) {185changed.push([key, policyValue]);186}187} else {188if (this._configurationModel.getValue(key) !== undefined) {189changed.push([key, undefined]);190}191}192}193194if (changed.length) {195this.logService.trace('PolicyConfiguration#changed', changed);196const old = this._configurationModel;197this._configurationModel = ConfigurationModel.createEmptyModel(this.logService);198for (const key of old.keys) {199this._configurationModel.setValue(key, old.getValue(key));200}201for (const [key, policyValue] of changed) {202if (policyValue === undefined) {203this._configurationModel.removeValue(key);204} else {205this._configurationModel.setValue(key, policyValue);206}207}208if (trigger) {209this._onDidChangeConfiguration.fire(this._configurationModel);210}211}212}213214private parse(content: string): ParsedType {215let raw: ParsedType = {};216let currentProperty: string | null = null;217let currentParent: ParsedType = [];218const previousParents: Array<ParsedType> = [];219const parseErrors: json.ParseError[] = [];220221function onValue(value: unknown) {222if (Array.isArray(currentParent)) {223currentParent.push(value);224} else if (currentProperty !== null) {225if (currentParent[currentProperty] !== undefined) {226throw new Error(`Duplicate property found: ${currentProperty}`);227}228currentParent[currentProperty] = value;229}230}231232const visitor: json.JSONVisitor = {233onObjectBegin: () => {234const object = {};235onValue(object);236previousParents.push(currentParent);237currentParent = object;238currentProperty = null;239},240onObjectProperty: (name: string) => {241currentProperty = name;242},243onObjectEnd: () => {244currentParent = previousParents.pop()!;245},246onArrayBegin: () => {247const array: unknown[] = [];248onValue(array);249previousParents.push(currentParent);250currentParent = array;251currentProperty = null;252},253onArrayEnd: () => {254currentParent = previousParents.pop()!;255},256onLiteralValue: onValue,257onError: (error: json.ParseErrorCode, offset: number, length: number) => {258parseErrors.push({ error, offset, length });259}260};261262if (content) {263json.visit(content, visitor);264raw = (currentParent[0] as ParsedType | undefined) || raw;265}266267if (parseErrors.length > 0) {268throw new Error(parseErrors.map(e => getErrorMessage(e.error)).join('\n'));269}270271return raw;272}273}274275276