Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -16,6 +16,6 @@ export interface IConfigurationResolverService {
resolve(root: IWorkspaceFolder, value: string): string;
resolve(root: IWorkspaceFolder, value: string[]): string[];
resolve(root: IWorkspaceFolder, value: IStringDictionary<string>): IStringDictionary<string>;
resolveAny<T>(root: IWorkspaceFolder, value: T): T;
resolveInteractiveVariables(configuration: any, interactiveVariablesMap: { [key: string]: string }): TPromise<any>;
resolveAny<T>(root: IWorkspaceFolder, value: T, commandMapping?: IStringDictionary<string>): T;
executeCommandVariables(value: any, variables: IStringDictionary<string>): TPromise<IStringDictionary<string>>;
}

View File

@@ -3,10 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import uri from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import * as types from 'vs/base/common/types';
import { Schemas } from 'vs/base/common/network';
import { TPromise } from 'vs/base/common/winjs.base';
import { sequence } from 'vs/base/common/async';
import { toResource } from 'vs/workbench/common/editor';
import { IStringDictionary } from 'vs/base/common/collections';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -14,159 +16,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { toResource } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { VariableResolver } from 'vs/workbench/services/configurationResolver/node/variableResolver';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { relative } from 'path';
import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { Schemas } from 'vs/base/common/network';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
class VariableResolver {
static VARIABLE_REGEXP = /\$\{(.*?)\}/g;
private envVariables: IProcessEnvironment;
constructor(
envVariables: IProcessEnvironment,
private configurationService: IConfigurationService,
private editorService: IWorkbenchEditorService,
private environmentService: IEnvironmentService,
private workspaceContextService: IWorkspaceContextService
) {
if (isWindows) {
this.envVariables = Object.create(null);
Object.keys(envVariables).forEach(key => {
this.envVariables[key.toLowerCase()] = envVariables[key];
});
} else {
this.envVariables = envVariables;
}
}
resolve(context: IWorkspaceFolder, value: string): string {
const filePath = this.getFilePath();
return value.replace(VariableResolver.VARIABLE_REGEXP, (match: string, variable: string) => {
const parts = variable.split(':');
let sufix: string;
if (parts && parts.length > 1) {
variable = parts[0];
sufix = parts[1];
}
switch (variable) {
case 'env': {
if (sufix) {
if (isWindows) {
sufix = sufix.toLowerCase();
}
const env = this.envVariables[sufix];
if (types.isString(env)) {
return env;
}
}
}
case 'config': {
if (sufix) {
const config = this.configurationService.getValue<string>(sufix, context ? { resource: context.uri } : undefined);
if (!types.isUndefinedOrNull(config) && !types.isObject(config)) {
return config;
}
}
}
default: {
if (sufix) {
const folder = this.workspaceContextService.getWorkspace().folders.filter(f => f.name === sufix).pop();
if (folder) {
context = folder;
}
}
switch (variable) {
case 'workspaceRoot':
case 'workspaceFolder':
return context ? normalizeDriveLetter(context.uri.fsPath) : match;
case 'cwd':
return context ? normalizeDriveLetter(context.uri.fsPath) : process.cwd();
case 'workspaceRootFolderName':
case 'workspaceFolderBasename':
return context ? paths.basename(context.uri.fsPath) : match;
case 'lineNumber':
return this.getLineNumber() || match;
case 'selectedText':
return this.getSelectedText() || match;
case 'file':
return filePath || match;
case 'relativeFile':
return context ? paths.normalize(relative(context.uri.fsPath, filePath)) : filePath || match;
case 'fileDirname':
return filePath ? paths.dirname(filePath) : match;
case 'fileExtname':
return filePath ? paths.extname(filePath) : match;
case 'fileBasename':
return filePath ? paths.basename(filePath) : match;
case 'fileBasenameNoExtension': {
if (!filePath) {
return match;
}
const basename = paths.basename(filePath);
return basename.slice(0, basename.length - paths.extname(basename).length);
}
case 'execPath':
return this.environmentService.execPath;
default:
return match;
}
}
}
});
}
private getSelectedText(): string {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor) {
const editorControl = (<ICodeEditor>activeEditor.getControl());
if (editorControl) {
const editorModel = editorControl.getModel();
const editorSelection = editorControl.getSelection();
if (editorModel && editorSelection) {
return editorModel.getValueInRange(editorSelection);
}
}
}
return undefined;
}
private getFilePath(): string {
let input = this.editorService.getActiveEditorInput();
if (input instanceof DiffEditorInput) {
input = input.modifiedInput;
}
const fileResource = toResource(input, { filter: Schemas.file });
if (!fileResource) {
return undefined;
}
return paths.normalize(fileResource.fsPath, true);
}
private getLineNumber(): string {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor) {
const editorControl = (<ICodeEditor>activeEditor.getControl());
if (editorControl) {
const lineNumber = editorControl.getSelection().positionLineNumber;
return String(lineNumber);
}
}
return undefined;
}
}
export class ConfigurationResolverService implements IConfigurationResolverService {
_serviceBrand: any;
@@ -180,78 +34,112 @@ export class ConfigurationResolverService implements IConfigurationResolverServi
@ICommandService private commandService: ICommandService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService
) {
this.resolver = new VariableResolver(envVariables, configurationService, editorService, environmentService, workspaceContextService);
this.resolver = new VariableResolver({
getFolderUri: (folderName: string): uri => {
const folder = workspaceContextService.getWorkspace().folders.filter(f => f.name === folderName).pop();
return folder ? folder.uri : undefined;
},
getWorkspaceFolderCount: (): number => {
return workspaceContextService.getWorkspace().folders.length;
},
getConfigurationValue: (folderUri: uri, suffix: string) => {
return configurationService.getValue<string>(suffix, folderUri ? { resource: folderUri } : undefined);
},
getExecPath: () => {
return environmentService['execPath'];
},
getFilePath: (): string | undefined => {
let input = editorService.getActiveEditorInput();
if (input instanceof DiffEditorInput) {
input = input.modifiedInput;
}
const fileResource = toResource(input, { filter: Schemas.file });
if (!fileResource) {
return undefined;
}
return paths.normalize(fileResource.fsPath, true);
},
getSelectedText: (): string | undefined => {
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
const editorControl = (<ICodeEditor>activeEditor.getControl());
if (editorControl) {
const editorModel = editorControl.getModel();
const editorSelection = editorControl.getSelection();
if (editorModel && editorSelection) {
return editorModel.getValueInRange(editorSelection);
}
}
}
return undefined;
},
getLineNumber: (): string => {
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
const editorControl = (<ICodeEditor>activeEditor.getControl());
if (editorControl) {
const lineNumber = editorControl.getSelection().positionLineNumber;
return String(lineNumber);
}
}
return undefined;
}
}, envVariables);
}
public resolve(root: IWorkspaceFolder, value: string): string;
public resolve(root: IWorkspaceFolder, value: string[]): string[];
public resolve(root: IWorkspaceFolder, value: IStringDictionary<string>): IStringDictionary<string>;
public resolve(root: IWorkspaceFolder, value: any): any {
if (types.isString(value)) {
return this.resolver.resolve(root, value);
} else if (types.isArray(value)) {
return value.map(s => this.resolver.resolve(root, s));
} else if (types.isObject(value)) {
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
Object.keys(value).forEach(key => {
result[key] = this.resolve(root, value[key]);
});
return result;
}
return value;
return this.resolver.resolveAny(root ? root.uri : undefined, value);
}
public resolveAny(root: IWorkspaceFolder, value: any): any {
if (types.isString(value)) {
return this.resolver.resolve(root, value);
} else if (types.isArray(value)) {
return value.map(s => this.resolveAny(root, s));
} else if (types.isObject(value)) {
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
Object.keys(value).forEach(key => {
result[key] = this.resolveAny(root, value[key]);
});
return result;
}
return value;
public resolveAny(root: IWorkspaceFolder, value: any, commandValueMapping?: IStringDictionary<string>): any {
return this.resolver.resolveAny(root ? root.uri : undefined, value, commandValueMapping);
}
/**
* Resolve all interactive variables in configuration #6569
* Finds and executes all command variables (see #6569)
*/
public resolveInteractiveVariables(configuration: any, interactiveVariablesMap: { [key: string]: string }): TPromise<any> {
public executeCommandVariables(configuration: any, variableToCommandMap: IStringDictionary<string>): TPromise<IStringDictionary<string>> {
if (!configuration) {
return TPromise.as(null);
}
// We need a map from interactive variables to keys because we only want to trigger an command once per key -
// even though it might occur multiple times in configuration #7026.
const interactiveVariablesToSubstitutes: { [interactiveVariable: string]: { object: any, key: string }[] } = Object.create(null);
const findInteractiveVariables = (object: any) => {
// use an array to preserve order of first appearance
const commands: string[] = [];
const cmd_var = /\${command:(.*?)}/g;
const findCommandVariables = (object: any) => {
Object.keys(object).forEach(key => {
if (object[key] && typeof object[key] === 'object') {
findInteractiveVariables(object[key]);
} else if (typeof object[key] === 'string') {
const matches = /\${command:(.+)}/.exec(object[key]);
if (matches && matches.length === 2) {
const interactiveVariable = matches[1];
if (!interactiveVariablesToSubstitutes[interactiveVariable]) {
interactiveVariablesToSubstitutes[interactiveVariable] = [];
const value = object[key];
if (value && typeof value === 'object') {
findCommandVariables(value);
} else if (typeof value === 'string') {
let matches;
while ((matches = cmd_var.exec(value)) !== null) {
if (matches.length === 2) {
const command = matches[1];
if (commands.indexOf(command) < 0) {
commands.push(command);
}
}
interactiveVariablesToSubstitutes[interactiveVariable].push({ object, key });
}
}
});
};
findInteractiveVariables(configuration);
let substitionCanceled = false;
const factory: { (): TPromise<any> }[] = Object.keys(interactiveVariablesToSubstitutes).map(interactiveVariable => {
findCommandVariables(configuration);
let cancelled = false;
const commandValueMapping: IStringDictionary<string> = Object.create(null);
const factory: { (): TPromise<any> }[] = commands.map(interactiveVariable => {
return () => {
let commandId: string = null;
commandId = interactiveVariablesMap ? interactiveVariablesMap[interactiveVariable] : null;
let commandId = variableToCommandMap ? variableToCommandMap[interactiveVariable] : null;
if (!commandId) {
// Just launch any command if the interactive variable is not contributed by the adapter #12735
commandId = interactiveVariable;
@@ -259,18 +147,14 @@ export class ConfigurationResolverService implements IConfigurationResolverServi
return this.commandService.executeCommand<string>(commandId, configuration).then(result => {
if (result) {
interactiveVariablesToSubstitutes[interactiveVariable].forEach(substitute => {
if (substitute.object[substitute.key].indexOf(`\${command:${interactiveVariable}}`) >= 0) {
substitute.object[substitute.key] = substitute.object[substitute.key].replace(`\${command:${interactiveVariable}}`, result);
}
});
commandValueMapping[interactiveVariable] = result;
} else {
substitionCanceled = true;
cancelled = true;
}
});
};
});
return sequence(factory).then(() => substitionCanceled ? null : configuration);
return sequence(factory).then(() => cancelled ? null : commandValueMapping);
}
}

View File

@@ -0,0 +1,218 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as paths from 'vs/base/common/paths';
import * as types from 'vs/base/common/types';
import { IStringDictionary } from 'vs/base/common/collections';
import { relative } from 'path';
import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { localize } from 'vs/nls';
import uri from 'vs/base/common/uri';
export interface IVariableAccessor {
getFolderUri(folderName: string): uri | undefined;
getWorkspaceFolderCount(): number;
getConfigurationValue(folderUri: uri, section: string): string | undefined;
getExecPath(): string | undefined;
getFilePath(): string | undefined;
getSelectedText(): string | undefined;
getLineNumber(): string;
}
export class VariableResolver {
static VARIABLE_REGEXP = /\$\{(.*?)\}/g;
private envVariables: IProcessEnvironment;
constructor(
private accessor: IVariableAccessor,
envVariables: IProcessEnvironment
) {
if (isWindows) {
this.envVariables = Object.create(null);
Object.keys(envVariables).forEach(key => {
this.envVariables[key.toLowerCase()] = envVariables[key];
});
} else {
this.envVariables = envVariables;
}
}
resolveAny(folderUri: uri, value: any, commandValueMapping?: IStringDictionary<string>): any {
if (types.isString(value)) {
return this.resolve(folderUri, value, commandValueMapping);
} else if (types.isArray(value)) {
return value.map(s => this.resolveAny(folderUri, s, commandValueMapping));
} else if (types.isObject(value)) {
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
Object.keys(value).forEach(key => {
result[key] = this.resolveAny(folderUri, value[key], commandValueMapping);
});
return result;
}
return value;
}
resolve(folderUri: uri, value: string, commandValueMapping: IStringDictionary<string>): string {
const filePath = this.accessor.getFilePath();
return value.replace(VariableResolver.VARIABLE_REGEXP, (match: string, variable: string) => {
let argument: string;
const parts = variable.split(':');
if (parts && parts.length > 1) {
variable = parts[0];
argument = parts[1];
}
switch (variable) {
case 'env':
if (argument) {
if (isWindows) {
argument = argument.toLowerCase();
}
const env = this.envVariables[argument];
if (types.isString(env)) {
return env;
}
// For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436
return '';
}
throw new Error(localize('missingEnvVarName', "'{0}' can not be resolved because no environment variable name is given.", match));
case 'config':
if (argument) {
const config = this.accessor.getConfigurationValue(folderUri, argument);
if (types.isUndefinedOrNull(config)) {
throw new Error(localize('configNotFound', "'{0}' can not be resolved because setting '{1}' not found.", match, argument));
}
if (types.isObject(config)) {
throw new Error(localize('configNoString', "'{0}' can not be resolved because '{1}' is a structured value.", match, argument));
}
return config;
}
throw new Error(localize('missingConfigName', "'{0}' can not be resolved because no settings name is given.", match));
case 'command':
if (argument && commandValueMapping) {
const v = commandValueMapping[argument];
if (typeof v === 'string') {
return v;
}
throw new Error(localize('noValueForCommand', "'{0}' can not be resolved because the command has no value.", match));
}
return match;
default: {
// common error handling for all variables that require an open folder and accept a folder name argument
switch (variable) {
case 'workspaceRoot':
case 'workspaceFolder':
case 'workspaceRootFolderName':
case 'workspaceFolderBasename':
case 'relativeFile':
if (argument) {
const folder = this.accessor.getFolderUri(argument);
if (folder) {
folderUri = folder;
} else {
throw new Error(localize('canNotFindFolder', "'{0}' can not be resolved. No such folder '{1}'.", match, argument));
}
}
if (!folderUri) {
if (this.accessor.getWorkspaceFolderCount() > 1) {
throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "'{0}' can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match));
}
throw new Error(localize('canNotResolveWorkspaceFolder', "'{0}' can not be resolved. Please open a folder.", match));
}
break;
default:
break;
}
// common error handling for all variables that require an open file
switch (variable) {
case 'file':
case 'relativeFile':
case 'fileDirname':
case 'fileExtname':
case 'fileBasename':
case 'fileBasenameNoExtension':
if (!filePath) {
throw new Error(localize('canNotResolveFile', "'{0}' can not be resolved. Please open an editor.", match));
}
break;
default:
break;
}
switch (variable) {
case 'workspaceRoot':
case 'workspaceFolder':
return normalizeDriveLetter(folderUri.fsPath);
case 'cwd':
return folderUri ? normalizeDriveLetter(folderUri.fsPath) : process.cwd();
case 'workspaceRootFolderName':
case 'workspaceFolderBasename':
return paths.basename(folderUri.fsPath);
case 'lineNumber':
const lineNumber = this.accessor.getLineNumber();
if (lineNumber) {
return lineNumber;
}
throw new Error(localize('canNotResolveLineNumber', "'{0}' can not be resolved. Make sure to have a line selected in the active editor.", match));
case 'selectedText':
const selectedText = this.accessor.getSelectedText();
if (selectedText) {
return selectedText;
}
throw new Error(localize('canNotResolveSelectedText', "'{0}' can not be resolved. Make sure to have some text selected in the active editor.", match));
case 'file':
return filePath;
case 'relativeFile':
if (folderUri) {
return paths.normalize(relative(folderUri.fsPath, filePath));
}
return filePath;
case 'fileDirname':
return paths.dirname(filePath);
case 'fileExtname':
return paths.extname(filePath);
case 'fileBasename':
return paths.basename(filePath);
case 'fileBasenameNoExtension':
const basename = paths.basename(filePath);
return basename.slice(0, basename.length - paths.extname(basename).length);
case 'execPath':
const ep = this.accessor.getExecPath();
if (ep) {
return ep;
}
return match;
default:
return match;
}
}
}
});
}
}

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
import * as assert from 'assert';
import uri from 'vs/base/common/uri';
import platform = require('vs/base/common/platform');
import * as platform from 'vs/base/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { IConfigurationService, getConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
@@ -86,7 +86,7 @@ suite('Configuration Resolver Service', () => {
if (platform.isWindows) {
assert.strictEqual(configurationResolverService.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - Value for key1');
} else {
assert.strictEqual(configurationResolverService.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - ${env:Key1}');
assert.strictEqual(configurationResolverService.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - ');
}
});
@@ -196,18 +196,6 @@ suite('Configuration Resolver Service', () => {
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz');
});
test('configuration should not evaluate Javascript', () => {
let configurationService: IConfigurationService;
configurationService = new MockConfigurationService({
editor: {
abc: 'foo'
}
});
let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService());
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor[\'abc\'.substr(0)]} xyz'), 'abc ${config:editor[\'abc\'.substr(0)]} xyz');
});
test('uses original variable as fallback', () => {
let configurationService: IConfigurationService;
configurationService = new MockConfigurationService({
@@ -215,10 +203,8 @@ suite('Configuration Resolver Service', () => {
});
let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService());
assert.strictEqual(service.resolve(workspace, 'abc ${invalidVariable} xyz'), 'abc ${invalidVariable} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${env:invalidVariable} xyz'), 'abc ${env:invalidVariable} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.abc.def} xyz'), 'abc ${config:editor.abc.def} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${config:panel.abc} xyz'), 'abc ${config:panel.abc} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz');
});
test('configuration variables with invalid accessor', () => {
@@ -230,9 +216,14 @@ suite('Configuration Resolver Service', () => {
});
let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService());
assert.strictEqual(service.resolve(workspace, 'abc ${config:} xyz'), 'abc ${config:} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor..fontFamily} xyz'), 'abc ${config:editor..fontFamily} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.none.none2} xyz'), 'abc ${config:editor.none.none2} xyz');
assert.throws(() => service.resolve(workspace, 'abc ${env} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${env:} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config:} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config:editor} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config:editor..fontFamily} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config:editor.none.none2} xyz'));
});
test('interactive variable simple', () => {
@@ -249,8 +240,11 @@ suite('Configuration Resolver Service', () => {
interactiveVariables['interactiveVariable1'] = 'command1';
interactiveVariables['interactiveVariable2'] = 'command2';
configurationResolverService.resolveInteractiveVariables(configuration, interactiveVariables).then(resolved => {
assert.deepEqual(resolved, {
configurationResolverService.executeCommandVariables(configuration, interactiveVariables).then(mapping => {
const result = configurationResolverService.resolveAny(undefined, configuration, mapping);
assert.deepEqual(result, {
'name': 'Attach to Process',
'type': 'node',
'request': 'attach',
@@ -281,8 +275,11 @@ suite('Configuration Resolver Service', () => {
interactiveVariables['interactiveVariable1'] = 'command1';
interactiveVariables['interactiveVariable2'] = 'command2';
configurationResolverService.resolveInteractiveVariables(configuration, interactiveVariables).then(resolved => {
assert.deepEqual(resolved, {
configurationResolverService.executeCommandVariables(configuration, interactiveVariables).then(mapping => {
const result = configurationResolverService.resolveAny(undefined, configuration, mapping);
assert.deepEqual(result, {
'name': 'Attach to Process',
'type': 'node',
'request': 'attach',