Skip to content

Commit 98ef660

Browse files
committed
1 parent 85397a8 commit 98ef660

3 files changed

Lines changed: 100 additions & 17 deletions

File tree

src/vs/platform/configuration/common/configurationModels.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/conf
1414
import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree, toOverrides } from 'vs/platform/configuration/common/configuration';
1515
import { Workspace } from 'vs/platform/workspace/common/workspace';
1616

17+
declare const Proxy: any; // TODO@TypeScript
18+
1719
export class ConfigurationModel implements IConfigurationModel {
1820

1921
private isFrozen: boolean = false;
@@ -291,7 +293,8 @@ export class Configuration {
291293

292294
getValue(section: string, overrides: IConfigurationOverrides, workspace: Workspace): any {
293295
const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace);
294-
return consolidateConfigurationModel.getValue(section);
296+
const result = consolidateConfigurationModel.getValue(section);
297+
return this.toReadonlyValue(result);
295298
}
296299

297300
updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void {
@@ -334,7 +337,7 @@ export class Configuration {
334337
workspace: workspace ? overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._workspaceConfiguration.freeze().getValue(key) : void 0, //Check on workspace exists or not because _workspaceConfiguration is never null
335338
workspaceFolder: folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : void 0,
336339
memory: overrides.overrideIdentifier ? memoryConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.freeze().getValue(key),
337-
value: consolidateConfigurationModel.getValue(key)
340+
value: this.toReadonlyValue(consolidateConfigurationModel.getValue(key))
338341
};
339342
}
340343

@@ -421,7 +424,7 @@ export class Configuration {
421424

422425
private getWorkspaceConsolidatedConfiguration(): ConfigurationModel {
423426
if (!this._workspaceConsolidatedConfiguration) {
424-
this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration).merge(this._workspaceConfiguration).merge(this._memoryConfiguration).freeze();
427+
this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration, this._workspaceConfiguration, this._memoryConfiguration);
425428
}
426429
return this._workspaceConsolidatedConfiguration;
427430
}
@@ -432,7 +435,7 @@ export class Configuration {
432435
const workspaceConsolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();
433436
const folderConfiguration = this._folderConfigurations.get(folder);
434437
if (folderConfiguration) {
435-
folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration).freeze();
438+
folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration);
436439
this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration);
437440
} else {
438441
folderConsolidatedConfiguration = workspaceConsolidateConfiguration;
@@ -451,6 +454,22 @@ export class Configuration {
451454
return null;
452455
}
453456

457+
private toReadonlyValue(result: any): any {
458+
const readonlyProxy = (target) => {
459+
return types.isObject(target) ?
460+
new Proxy(target, {
461+
get: (target: any, property: string) => readonlyProxy(target[property]),
462+
set: (target: any, property: string, value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${property}' of object`); },
463+
deleteProperty: (target: any, property: string) => { throw new Error(`TypeError: Cannot delete read only property '${property}' of object`); },
464+
defineProperty: (target: any, property: string) => { throw new Error(`TypeError: Cannot define property '${property}' for a readonly object`); },
465+
setPrototypeOf: (target: any) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); },
466+
isExtensible: () => false,
467+
preventExtensions: () => true
468+
}) : target;
469+
};
470+
return readonlyProxy(result);
471+
}
472+
454473
toData(): IConfigurationData {
455474
return {
456475
defaults: {

src/vs/workbench/api/node/extHostConfiguration.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import { Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/
1616
import { WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
1717
import { StrictResourceMap } from 'vs/base/common/map';
1818
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
19+
import { isObject } from 'vs/base/common/types';
20+
21+
declare var Proxy: any; // TODO@TypeScript
1922

2023
function lookUp(tree: any, key: string) {
2124
if (key) {
@@ -61,9 +64,9 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape {
6164
}
6265

6366
getConfiguration(section?: string, resource?: URI, extensionId?: string): vscode.WorkspaceConfiguration {
64-
const config = deepClone(section
67+
const config = section
6568
? lookUp(this._configuration.getValue(null, { resource }, this._extHostWorkspace.workspace), section)
66-
: this._configuration.getValue(null, { resource }, this._extHostWorkspace.workspace));
69+
: this._configuration.getValue(null, { resource }, this._extHostWorkspace.workspace);
6770

6871
if (section) {
6972
this._validateConfigurationAccess(section, resource, extensionId);
@@ -93,6 +96,32 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape {
9396
let result = lookUp(config, key);
9497
if (typeof result === 'undefined') {
9598
result = defaultValue;
99+
} else {
100+
let clonedConfig = void 0;
101+
const cloneOnWriteProxy = (target: any, accessor: string): any => {
102+
let clonedTarget = void 0;
103+
return isObject(target) ?
104+
new Proxy(target, {
105+
get: (target: any, property: string) => {
106+
if (clonedConfig) {
107+
clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
108+
return clonedTarget[property];
109+
}
110+
const result = target[property];
111+
if (typeof property === 'string' && property.toLowerCase() !== 'tojson') {
112+
return cloneOnWriteProxy(result, `${accessor}.${property}`);
113+
}
114+
return result;
115+
},
116+
set: (target: any, property: string, value: any) => {
117+
clonedConfig = clonedConfig ? clonedConfig : deepClone(config);
118+
clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor);
119+
clonedTarget[property] = value;
120+
return true;
121+
}
122+
}) : target;
123+
};
124+
result = cloneOnWriteProxy(result, key);
96125
}
97126
return result;
98127
},

src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,30 +107,65 @@ suite('ExtHostConfiguration', function () {
107107

108108
let testObject = all.getConfiguration();
109109
let actual = testObject.get('farboo');
110+
actual['nested']['config1'] = 41;
111+
assert.equal(41, actual['nested']['config1']);
110112
actual['farboo1'] = 'newValue';
111113
assert.equal('newValue', actual['farboo1']);
112114

113115
testObject = all.getConfiguration();
114-
testObject['farboo']['farboo1'] = 'newValue';
115-
assert.equal('newValue', testObject['farboo']['farboo1']);
116+
actual = testObject.get('farboo');
117+
assert.equal(actual['nested']['config1'], 42);
118+
assert.equal(actual['farboo1'], undefined);
116119

117120
testObject = all.getConfiguration();
118-
testObject['farboo']['farboo1'] = 'newValue';
119-
assert.equal('newValue', testObject.get('farboo')['farboo1']);
121+
actual = testObject.get('farboo');
122+
assert.equal(actual['config0'], true);
123+
actual['config0'] = false;
124+
assert.equal(actual['config0'], false);
125+
126+
testObject = all.getConfiguration();
127+
actual = testObject.get('farboo');
128+
assert.equal(actual['config0'], true);
120129

121130
testObject = all.getConfiguration();
122131
actual = testObject.inspect('farboo');
123132
actual['value'] = 'effectiveValue';
124133
assert.equal('effectiveValue', actual['value']);
134+
});
125135

126-
testObject = all.getConfiguration();
127-
actual = testObject.get('farboo');
128-
assert.equal(undefined, actual['farboo1']);
136+
test('cannot modify returned configuration', function () {
129137

130-
testObject = all.getConfiguration();
131-
testObject['farboo']['farboo1'] = 'newValue';
132-
testObject = all.getConfiguration();
133-
assert.equal(undefined, testObject['farboo']['farboo1']);
138+
const all = createExtHostConfiguration({
139+
'farboo': {
140+
'config0': true,
141+
'nested': {
142+
'config1': 42,
143+
'config2': 'Das Pferd frisst kein Reis.'
144+
},
145+
'config4': ''
146+
}
147+
});
148+
149+
let testObject = all.getConfiguration();
150+
151+
try {
152+
testObject['get'] = null;
153+
assert.fail('This should be readonly');
154+
} catch (e) {
155+
}
156+
157+
try {
158+
testObject['farboo']['config0'] = false;
159+
assert.fail('This should be readonly');
160+
} catch (e) {
161+
console.log(e);
162+
}
163+
164+
try {
165+
testObject['farboo']['farboo1'] = 'hello';
166+
assert.fail('This should be readonly');
167+
} catch (e) {
168+
}
134169
});
135170

136171
test('inspect in no workspace context', function () {

0 commit comments

Comments
 (0)