diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c9f5485d0b878bb7d3a79a3a85318932294c5c51..672e82239fe45215495c3a417c9db28ba004b18c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -302,6 +302,11 @@ "from": "object.omit@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz" }, + "objectpath": { + "version": "1.2.1", + "from": "objectpath@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/objectpath/-/objectpath-1.2.1.tgz" + }, "oniguruma": { "version": "6.1.1", "from": "oniguruma@>=6.0.1 <7.0.0", diff --git a/package.json b/package.json index e66281ba13437b690236a6aa9139b7b5963f6d5f..6dfdee210ee7eabf0b5e334f5cc7bfe915431036 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "applicationinsights": "0.15.6", "chokidar": "bpasero/chokidar#vscode", "emmet": "1.3.1", + "fast-plist": "0.1.2", "gc-signals": "^0.0.1", "getmac": "1.0.7", "graceful-fs": "4.1.2", @@ -26,8 +27,8 @@ "iconv-lite": "0.4.13", "minimist": "1.2.0", "native-keymap": "0.2.0", + "objectpath": "^1.2.1", "pty.js": "https://github.com/Tyriar/pty.js/tarball/fffbf86eb9e8051b5b2be4ba9c7b07faa018ce8d", - "fast-plist": "0.1.2", "semver": "4.3.6", "vscode-debugprotocol": "1.13.0", "vscode-textmate": "2.2.0", diff --git a/src/typings/objectpath.d.ts b/src/typings/objectpath.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..128b9bafe53a65fc118c9b9dbb5909d1ae3bdd51 --- /dev/null +++ b/src/typings/objectpath.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module "objectpath" { + export function parse(path: string): string[]; +} diff --git a/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts index 51cb1b5e26550f0646a9633be10922e5f532e162..c236c89233e7784f50c7529168d104a7a2703be3 100644 --- a/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/node/configurationResolverService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as ObjectPath from 'objectpath'; import nls = require('vs/nls'); import * as paths from 'vs/base/common/paths'; import * as types from 'vs/base/common/types'; @@ -130,10 +131,26 @@ export class ConfigurationResolverService implements IConfigurationResolverServi } private resolveConfigVariable(value: string, originalValue: string): string { - let regexp = /\$\{config\.(.*?)\}/g; - return value.replace(regexp, (match: string, name: string) => { + let regexp = /\$\{config(\.|(?=\[))(.+?)\}/g; + return value.replace(regexp, (match: string, lead: string, name: string) => { let config = this.configurationService.getConfiguration(); - let newValue = new Function('_', 'try {return _.' + name + ';} catch (ex) { return "";}')(config); + let newValue: any; + try { + const keys: string[] = ObjectPath.parse(name); + if (!keys || keys.length <= 0) { + return ''; + } + while (keys.length > 1) { + const key = keys.shift(); + if (!config || !config.hasOwnProperty(key)) { + return ''; + } + config = config[key]; + } + newValue = config ? config[keys[0]] : ''; + } catch (e) { + return ''; + } if (types.isString(newValue)) { // Prevent infinite recursion and also support nested references (or tokens) return newValue === originalValue ? '' : this.resolveString(newValue); diff --git a/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts index c6a9d539975ac8cc121e46c6b138a9fbee16c5d8..09de8c8f3e8deca1f96ffbd950a63c3ce8b23e11 100644 --- a/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/node/configurationResolverService.test.ts @@ -212,6 +212,46 @@ suite('Configuration Resolver Service', () => { assert.strictEqual(service.resolve('abc ${config.editor.fontFamily} ${config.editor.lineNumbers} ${config.editor.insertSpaces} ${config.json.schemas[0].fileMatch[1]} xyz'), 'abc foo 123 false {{/myOtherfile}} xyz'); }); + test('configuration variables using bracket accessor', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo' + } + }); + + let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService); + assert.strictEqual(service.resolve("abc ${config.editor['fontFamily']} xyz"), 'abc foo xyz'); + assert.strictEqual(service.resolve('abc ${config["editor"].fontFamily} xyz'), 'abc foo xyz'); + assert.strictEqual(service.resolve('abc ${config["editor"]["fontFamily"]} xyz'), 'abc foo xyz'); + }); + + test('configuration variables with invalid accessor', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo' + } + }); + + let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService); + assert.strictEqual(service.resolve("abc ${config.} xyz"), 'abc ${config.} xyz'); + assert.strictEqual(service.resolve("abc ${config.editor..fontFamily} xyz"), 'abc xyz'); + assert.strictEqual(service.resolve("abc ${config.editor.none.none2} xyz"), 'abc xyz'); + }); + + test('configuration should not evaluate Javascript', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + a: 'foo' + } + }); + + let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService); + assert.strictEqual(service.resolve("abc ${config.editor['abc'.substr(0)]} xyz"), 'abc undefined xyz'); + }); + test('interactive variable simple', () => { const configuration = { 'name': 'Attach to Process',