Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts
3297 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { stub } from 'sinon';
8
import { Emitter, Event } from '../../../../../base/common/event.js';
9
import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js';
10
import { Schemas } from '../../../../../base/common/network.js';
11
import { IPath, normalize } from '../../../../../base/common/path.js';
12
import * as platform from '../../../../../base/common/platform.js';
13
import { isLinux, isMacintosh, isWindows } from '../../../../../base/common/platform.js';
14
import { isObject } from '../../../../../base/common/types.js';
15
import { URI } from '../../../../../base/common/uri.js';
16
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
17
import { Selection } from '../../../../../editor/common/core/selection.js';
18
import { EditorType } from '../../../../../editor/common/editorCommon.js';
19
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
20
import { IConfigurationOverrides, IConfigurationService, IConfigurationValue } from '../../../../../platform/configuration/common/configuration.js';
21
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
22
import { IExtensionDescription } from '../../../../../platform/extensions/common/extensions.js';
23
import { IFormatterChangeEvent, ILabelService, ResourceLabelFormatter, Verbosity } from '../../../../../platform/label/common/label.js';
24
import { IWorkspace, IWorkspaceFolder, IWorkspaceIdentifier, Workspace } from '../../../../../platform/workspace/common/workspace.js';
25
import { testWorkspace } from '../../../../../platform/workspace/test/common/testWorkspace.js';
26
import { TestEditorService, TestQuickInputService } from '../../../../test/browser/workbenchTestServices.js';
27
import { TestContextService, TestExtensionService, TestStorageService } from '../../../../test/common/workbenchTestServices.js';
28
import { IExtensionService } from '../../../extensions/common/extensions.js';
29
import { IPathService } from '../../../path/common/pathService.js';
30
import { BaseConfigurationResolverService } from '../../browser/baseConfigurationResolverService.js';
31
import { IConfigurationResolverService } from '../../common/configurationResolver.js';
32
import { ConfigurationResolverExpression } from '../../common/configurationResolverExpression.js';
33
34
const mockLineNumber = 10;
35
class TestEditorServiceWithActiveEditor extends TestEditorService {
36
override get activeTextEditorControl(): any {
37
return {
38
getEditorType() {
39
return EditorType.ICodeEditor;
40
},
41
getSelection() {
42
return new Selection(mockLineNumber, 1, mockLineNumber, 10);
43
}
44
};
45
}
46
override get activeEditor(): any {
47
return {
48
get resource(): any {
49
return URI.parse('file:///VSCode/workspaceLocation/file');
50
}
51
};
52
}
53
}
54
55
class TestConfigurationResolverService extends BaseConfigurationResolverService {
56
57
}
58
59
const nullContext = {
60
getAppRoot: () => undefined,
61
getExecPath: () => undefined
62
};
63
64
suite('Configuration Resolver Service', () => {
65
let configurationResolverService: IConfigurationResolverService | null;
66
const envVariables: { [key: string]: string } = { key1: 'Value for key1', key2: 'Value for key2' };
67
// let environmentService: MockWorkbenchEnvironmentService;
68
let mockCommandService: MockCommandService;
69
let editorService: TestEditorServiceWithActiveEditor;
70
let containingWorkspace: Workspace;
71
let workspace: IWorkspaceFolder;
72
let quickInputService: TestQuickInputService;
73
let labelService: MockLabelService;
74
let pathService: MockPathService;
75
let extensionService: IExtensionService;
76
77
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
78
79
setup(() => {
80
mockCommandService = new MockCommandService();
81
editorService = disposables.add(new TestEditorServiceWithActiveEditor());
82
quickInputService = new TestQuickInputService();
83
// environmentService = new MockWorkbenchEnvironmentService(envVariables);
84
labelService = new MockLabelService();
85
pathService = new MockPathService();
86
extensionService = new TestExtensionService();
87
containingWorkspace = testWorkspace(URI.parse('file:///VSCode/workspaceLocation'));
88
workspace = containingWorkspace.folders[0];
89
configurationResolverService = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(containingWorkspace), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
90
});
91
92
teardown(() => {
93
configurationResolverService = null;
94
});
95
96
test('substitute one', async () => {
97
if (platform.isWindows) {
98
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder} xyz'), 'abc \\VSCode\\workspaceLocation xyz');
99
} else {
100
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder} xyz'), 'abc /VSCode/workspaceLocation xyz');
101
}
102
});
103
104
test('does not preserve platform config even when not matched', async () => {
105
const obj = {
106
program: 'osx.sh',
107
windows: {
108
program: 'windows.exe'
109
},
110
linux: {
111
program: 'linux.sh'
112
}
113
};
114
const config: any = await configurationResolverService!.resolveAsync(workspace, obj);
115
116
const expected = isWindows ? 'windows.exe' : isMacintosh ? 'osx.sh' : isLinux ? 'linux.sh' : undefined;
117
118
assert.strictEqual(config.windows, undefined);
119
assert.strictEqual(config.osx, undefined);
120
assert.strictEqual(config.linux, undefined);
121
assert.strictEqual(config.program, expected);
122
});
123
124
test('apples platform specific config', async () => {
125
const expected = isWindows ? 'windows.exe' : isMacintosh ? 'osx.sh' : isLinux ? 'linux.sh' : undefined;
126
const obj = {
127
windows: {
128
program: 'windows.exe'
129
},
130
osx: {
131
program: 'osx.sh'
132
},
133
linux: {
134
program: 'linux.sh'
135
}
136
};
137
const originalObj = JSON.stringify(obj);
138
const config: any = await configurationResolverService!.resolveAsync(workspace, obj);
139
140
assert.strictEqual(config.program, expected);
141
assert.strictEqual(config.windows, undefined);
142
assert.strictEqual(config.osx, undefined);
143
assert.strictEqual(config.linux, undefined);
144
assert.strictEqual(JSON.stringify(obj), originalObj); // did not mutate original
145
});
146
147
test('workspace folder with argument', async () => {
148
if (platform.isWindows) {
149
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc \\VSCode\\workspaceLocation xyz');
150
} else {
151
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc /VSCode/workspaceLocation xyz');
152
}
153
});
154
155
test('workspace folder with invalid argument', async () => {
156
await assert.rejects(async () => await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder:invalidLocation} xyz'));
157
});
158
159
test('workspace folder with undefined workspace folder', async () => {
160
await assert.rejects(async () => await configurationResolverService!.resolveAsync(undefined, 'abc ${workspaceFolder} xyz'));
161
});
162
163
test('workspace folder with argument and undefined workspace folder', async () => {
164
if (platform.isWindows) {
165
assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc \\VSCode\\workspaceLocation xyz');
166
} else {
167
assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc /VSCode/workspaceLocation xyz');
168
}
169
});
170
171
test('workspace folder with invalid argument and undefined workspace folder', () => {
172
assert.rejects(async () => await configurationResolverService!.resolveAsync(undefined, 'abc ${workspaceFolder:invalidLocation} xyz'));
173
});
174
175
test('workspace root folder name', async () => {
176
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceRootFolderName} xyz'), 'abc workspaceLocation xyz');
177
});
178
179
test('current selected line number', async () => {
180
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${lineNumber} xyz'), `abc ${mockLineNumber} xyz`);
181
});
182
183
test('relative file', async () => {
184
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${relativeFile} xyz'), 'abc file xyz');
185
});
186
187
test('relative file with argument', async () => {
188
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${relativeFile:workspaceLocation} xyz'), 'abc file xyz');
189
});
190
191
test('relative file with invalid argument', () => {
192
assert.rejects(async () => await configurationResolverService!.resolveAsync(workspace, 'abc ${relativeFile:invalidLocation} xyz'));
193
});
194
195
test('relative file with undefined workspace folder', async () => {
196
if (platform.isWindows) {
197
assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${relativeFile} xyz'), 'abc \\VSCode\\workspaceLocation\\file xyz');
198
} else {
199
assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${relativeFile} xyz'), 'abc /VSCode/workspaceLocation/file xyz');
200
}
201
});
202
203
test('relative file with argument and undefined workspace folder', async () => {
204
assert.strictEqual(await configurationResolverService!.resolveAsync(undefined, 'abc ${relativeFile:workspaceLocation} xyz'), 'abc file xyz');
205
});
206
207
test('relative file with invalid argument and undefined workspace folder', () => {
208
assert.rejects(async () => await configurationResolverService!.resolveAsync(undefined, 'abc ${relativeFile:invalidLocation} xyz'));
209
});
210
211
test('substitute many', async () => {
212
if (platform.isWindows) {
213
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${workspaceFolder} - ${workspaceFolder}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation');
214
} else {
215
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${workspaceFolder} - ${workspaceFolder}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation');
216
}
217
});
218
219
test('substitute one env variable', async () => {
220
if (platform.isWindows) {
221
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc \\VSCode\\workspaceLocation Value for key1 xyz');
222
} else {
223
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc /VSCode/workspaceLocation Value for key1 xyz');
224
}
225
});
226
227
test('substitute many env variable', async () => {
228
if (platform.isWindows) {
229
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2');
230
} else {
231
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2');
232
}
233
});
234
235
test('disallows nested keys (#77289)', async () => {
236
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${env:key1} ${env:key1${env:key2}}'), 'Value for key1 ');
237
});
238
239
test('supports extensionDir', async () => {
240
const getExtension = stub(extensionService, 'getExtension');
241
getExtension.withArgs('publisher.extId').returns(Promise.resolve({ extensionLocation: URI.file('/some/path') } as IExtensionDescription));
242
243
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${extensionInstallFolder:publisher.extId}'), URI.file('/some/path').fsPath);
244
});
245
246
// test('substitute keys and values in object', () => {
247
// const myObject = {
248
// '${workspaceRootFolderName}': '${lineNumber}',
249
// 'hey ${env:key1} ': '${workspaceRootFolderName}'
250
// };
251
// assert.deepStrictEqual(configurationResolverService!.resolveAsync(workspace, myObject), {
252
// 'workspaceLocation': `${editorService.mockLineNumber}`,
253
// 'hey Value for key1 ': 'workspaceLocation'
254
// });
255
// });
256
257
258
test('substitute one env variable using platform case sensitivity', async () => {
259
if (platform.isWindows) {
260
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - Value for key1');
261
} else {
262
assert.strictEqual(await configurationResolverService!.resolveAsync(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - ');
263
}
264
});
265
266
test('substitute one configuration variable', async () => {
267
const configurationService: IConfigurationService = new TestConfigurationService({
268
editor: {
269
fontFamily: 'foo'
270
},
271
terminal: {
272
integrated: {
273
fontFamily: 'bar'
274
}
275
}
276
});
277
278
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
279
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz');
280
});
281
282
test('inlines an array (#245718)', async () => {
283
const configurationService: IConfigurationService = new TestConfigurationService({
284
editor: {
285
fontFamily: ['foo', 'bar']
286
},
287
});
288
289
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
290
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo,bar xyz');
291
});
292
293
test('substitute configuration variable with undefined workspace folder', async () => {
294
const configurationService: IConfigurationService = new TestConfigurationService({
295
editor: {
296
fontFamily: 'foo'
297
}
298
});
299
300
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
301
assert.strictEqual(await service.resolveAsync(undefined, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz');
302
});
303
304
test('substitute many configuration variables', async () => {
305
const configurationService = new TestConfigurationService({
306
editor: {
307
fontFamily: 'foo'
308
},
309
terminal: {
310
integrated: {
311
fontFamily: 'bar'
312
}
313
}
314
});
315
316
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
317
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz');
318
});
319
320
test('substitute one env variable and a configuration variable', async () => {
321
const configurationService = new TestConfigurationService({
322
editor: {
323
fontFamily: 'foo'
324
},
325
terminal: {
326
integrated: {
327
fontFamily: 'bar'
328
}
329
}
330
});
331
332
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
333
if (platform.isWindows) {
334
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz');
335
} else {
336
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo /VSCode/workspaceLocation Value for key1 xyz');
337
}
338
});
339
340
test('recursively resolve variables', async () => {
341
const configurationService = new TestConfigurationService({
342
key1: 'key1=${config:key2}',
343
key2: 'key2=${config:key3}',
344
key3: 'we did it!',
345
});
346
347
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
348
assert.strictEqual(await service.resolveAsync(workspace, '${config:key1}'), 'key1=key2=we did it!');
349
});
350
351
test('substitute many env variable and a configuration variable', async () => {
352
const configurationService = new TestConfigurationService({
353
editor: {
354
fontFamily: 'foo'
355
},
356
terminal: {
357
integrated: {
358
fontFamily: 'bar'
359
}
360
}
361
});
362
363
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
364
if (platform.isWindows) {
365
assert.strictEqual(await service.resolveAsync(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2');
366
} else {
367
assert.strictEqual(await service.resolveAsync(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar /VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2');
368
}
369
});
370
371
test('mixed types of configuration variables', async () => {
372
const configurationService = new TestConfigurationService({
373
editor: {
374
fontFamily: 'foo',
375
lineNumbers: 123,
376
insertSpaces: false
377
},
378
terminal: {
379
integrated: {
380
fontFamily: 'bar'
381
}
382
},
383
json: {
384
schemas: [
385
{
386
fileMatch: [
387
'/myfile',
388
'/myOtherfile'
389
],
390
url: 'schemaURL'
391
}
392
]
393
}
394
});
395
396
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
397
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz');
398
});
399
400
test('uses original variable as fallback', async () => {
401
const configurationService = new TestConfigurationService({
402
editor: {}
403
});
404
405
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
406
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz');
407
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz');
408
});
409
410
test('configuration variables with invalid accessor', () => {
411
const configurationService = new TestConfigurationService({
412
editor: {
413
fontFamily: 'foo'
414
}
415
});
416
417
const service = new TestConfigurationResolverService(nullContext, Promise.resolve(envVariables), disposables.add(new TestEditorServiceWithActiveEditor()), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService, extensionService, disposables.add(new TestStorageService()));
418
419
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${env} xyz'));
420
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${env:} xyz'));
421
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config} xyz'));
422
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config:} xyz'));
423
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config:editor} xyz'));
424
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config:editor..fontFamily} xyz'));
425
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${config:editor.none.none2} xyz'));
426
});
427
428
test('a single command variable', () => {
429
430
const configuration = {
431
'name': 'Attach to Process',
432
'type': 'node',
433
'request': 'attach',
434
'processId': '${command:command1}',
435
'port': 5858,
436
'sourceMaps': false,
437
'outDir': null
438
};
439
440
return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration).then(result => {
441
assert.deepStrictEqual({ ...result }, {
442
'name': 'Attach to Process',
443
'type': 'node',
444
'request': 'attach',
445
'processId': 'command1-result',
446
'port': 5858,
447
'sourceMaps': false,
448
'outDir': null
449
});
450
451
assert.strictEqual(1, mockCommandService.callCount);
452
});
453
});
454
455
test('an old style command variable', () => {
456
const configuration = {
457
'name': 'Attach to Process',
458
'type': 'node',
459
'request': 'attach',
460
'processId': '${command:commandVariable1}',
461
'port': 5858,
462
'sourceMaps': false,
463
'outDir': null
464
};
465
const commandVariables = Object.create(null);
466
commandVariables['commandVariable1'] = 'command1';
467
468
return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => {
469
assert.deepStrictEqual({ ...result }, {
470
'name': 'Attach to Process',
471
'type': 'node',
472
'request': 'attach',
473
'processId': 'command1-result',
474
'port': 5858,
475
'sourceMaps': false,
476
'outDir': null
477
});
478
479
assert.strictEqual(1, mockCommandService.callCount);
480
});
481
});
482
483
test('multiple new and old-style command variables', () => {
484
485
const configuration = {
486
'name': 'Attach to Process',
487
'type': 'node',
488
'request': 'attach',
489
'processId': '${command:commandVariable1}',
490
'pid': '${command:command2}',
491
'sourceMaps': false,
492
'outDir': 'src/${command:command2}',
493
'env': {
494
'processId': '__${command:command2}__',
495
}
496
};
497
const commandVariables = Object.create(null);
498
commandVariables['commandVariable1'] = 'command1';
499
500
return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => {
501
const expected = {
502
'name': 'Attach to Process',
503
'type': 'node',
504
'request': 'attach',
505
'processId': 'command1-result',
506
'pid': 'command2-result',
507
'sourceMaps': false,
508
'outDir': 'src/command2-result',
509
'env': {
510
'processId': '__command2-result__',
511
}
512
};
513
514
assert.deepStrictEqual(Object.keys(result), Object.keys(expected));
515
Object.keys(result).forEach(property => {
516
const expectedProperty = (<any>expected)[property];
517
if (isObject(result[property])) {
518
assert.deepStrictEqual({ ...result[property] }, expectedProperty);
519
} else {
520
assert.deepStrictEqual(result[property], expectedProperty);
521
}
522
});
523
assert.strictEqual(2, mockCommandService.callCount);
524
});
525
});
526
527
test('a command variable that relies on resolved env vars', () => {
528
529
const configuration = {
530
'name': 'Attach to Process',
531
'type': 'node',
532
'request': 'attach',
533
'processId': '${command:commandVariable1}',
534
'value': '${env:key1}'
535
};
536
const commandVariables = Object.create(null);
537
commandVariables['commandVariable1'] = 'command1';
538
539
return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => {
540
541
assert.deepStrictEqual({ ...result }, {
542
'name': 'Attach to Process',
543
'type': 'node',
544
'request': 'attach',
545
'processId': 'Value for key1',
546
'value': 'Value for key1'
547
});
548
549
assert.strictEqual(1, mockCommandService.callCount);
550
});
551
});
552
553
test('a single prompt input variable', () => {
554
555
const configuration = {
556
'name': 'Attach to Process',
557
'type': 'node',
558
'request': 'attach',
559
'processId': '${input:input1}',
560
'port': 5858,
561
'sourceMaps': false,
562
'outDir': null
563
};
564
565
return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => {
566
567
assert.deepStrictEqual({ ...result }, {
568
'name': 'Attach to Process',
569
'type': 'node',
570
'request': 'attach',
571
'processId': 'resolvedEnterinput1',
572
'port': 5858,
573
'sourceMaps': false,
574
'outDir': null
575
});
576
577
assert.strictEqual(0, mockCommandService.callCount);
578
});
579
});
580
581
test('a single pick input variable', () => {
582
583
const configuration = {
584
'name': 'Attach to Process',
585
'type': 'node',
586
'request': 'attach',
587
'processId': '${input:input2}',
588
'port': 5858,
589
'sourceMaps': false,
590
'outDir': null
591
};
592
593
return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => {
594
595
assert.deepStrictEqual({ ...result }, {
596
'name': 'Attach to Process',
597
'type': 'node',
598
'request': 'attach',
599
'processId': 'selectedPick',
600
'port': 5858,
601
'sourceMaps': false,
602
'outDir': null
603
});
604
605
assert.strictEqual(0, mockCommandService.callCount);
606
});
607
});
608
609
test('a single command input variable', () => {
610
611
const configuration = {
612
'name': 'Attach to Process',
613
'type': 'node',
614
'request': 'attach',
615
'processId': '${input:input4}',
616
'port': 5858,
617
'sourceMaps': false,
618
'outDir': null
619
};
620
621
return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => {
622
623
assert.deepStrictEqual({ ...result }, {
624
'name': 'Attach to Process',
625
'type': 'node',
626
'request': 'attach',
627
'processId': 'arg for command',
628
'port': 5858,
629
'sourceMaps': false,
630
'outDir': null
631
});
632
633
assert.strictEqual(1, mockCommandService.callCount);
634
});
635
});
636
637
test('several input variables and command', () => {
638
639
const configuration = {
640
'name': '${input:input3}',
641
'type': '${command:command1}',
642
'request': '${input:input1}',
643
'processId': '${input:input2}',
644
'command': '${input:input4}',
645
'port': 5858,
646
'sourceMaps': false,
647
'outDir': null
648
};
649
650
return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => {
651
652
assert.deepStrictEqual({ ...result }, {
653
'name': 'resolvedEnterinput3',
654
'type': 'command1-result',
655
'request': 'resolvedEnterinput1',
656
'processId': 'selectedPick',
657
'command': 'arg for command',
658
'port': 5858,
659
'sourceMaps': false,
660
'outDir': null
661
});
662
663
assert.strictEqual(2, mockCommandService.callCount);
664
});
665
});
666
667
test('input variable with undefined workspace folder', () => {
668
669
const configuration = {
670
'name': 'Attach to Process',
671
'type': 'node',
672
'request': 'attach',
673
'processId': '${input:input1}',
674
'port': 5858,
675
'sourceMaps': false,
676
'outDir': null
677
};
678
679
return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, 'tasks').then(result => {
680
681
assert.deepStrictEqual({ ...result }, {
682
'name': 'Attach to Process',
683
'type': 'node',
684
'request': 'attach',
685
'processId': 'resolvedEnterinput1',
686
'port': 5858,
687
'sourceMaps': false,
688
'outDir': null
689
});
690
691
assert.strictEqual(0, mockCommandService.callCount);
692
});
693
});
694
695
test('contributed variable', () => {
696
const buildTask = 'npm: compile';
697
const variable = 'defaultBuildTask';
698
const configuration = {
699
'name': '${' + variable + '}',
700
};
701
configurationResolverService!.contributeVariable(variable, async () => { return buildTask; });
702
return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration).then(result => {
703
assert.deepStrictEqual({ ...result }, {
704
'name': `${buildTask}`
705
});
706
});
707
});
708
709
test('resolveWithEnvironment', async () => {
710
const env = {
711
'VAR_1': 'VAL_1',
712
'VAR_2': 'VAL_2'
713
};
714
const configuration = 'echo ${env:VAR_1}${env:VAR_2}';
715
const resolvedResult = await configurationResolverService!.resolveWithEnvironment({ ...env }, undefined, configuration);
716
assert.deepStrictEqual(resolvedResult, 'echo VAL_1VAL_2');
717
});
718
719
test('substitution in object key', async () => {
720
721
const configuration = {
722
'name': 'Test',
723
'mappings': {
724
'pos1': 'value1',
725
'${workspaceFolder}/test1': '${workspaceFolder}/test2',
726
'pos3': 'value3'
727
}
728
};
729
730
return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => {
731
732
if (platform.isWindows) {
733
assert.deepStrictEqual({ ...result }, {
734
'name': 'Test',
735
'mappings': {
736
'pos1': 'value1',
737
'\\VSCode\\workspaceLocation/test1': '\\VSCode\\workspaceLocation/test2',
738
'pos3': 'value3'
739
}
740
});
741
} else {
742
assert.deepStrictEqual({ ...result }, {
743
'name': 'Test',
744
'mappings': {
745
'pos1': 'value1',
746
'/VSCode/workspaceLocation/test1': '/VSCode/workspaceLocation/test2',
747
'pos3': 'value3'
748
}
749
});
750
}
751
752
assert.strictEqual(0, mockCommandService.callCount);
753
});
754
});
755
});
756
757
758
class MockCommandService implements ICommandService {
759
760
public _serviceBrand: undefined;
761
public callCount = 0;
762
763
onWillExecuteCommand = () => Disposable.None;
764
onDidExecuteCommand = () => Disposable.None;
765
public executeCommand(commandId: string, ...args: any[]): Promise<any> {
766
this.callCount++;
767
768
let result = `${commandId}-result`;
769
if (args.length >= 1) {
770
if (args[0] && args[0].value) {
771
result = args[0].value;
772
}
773
}
774
775
return Promise.resolve(result);
776
}
777
}
778
779
class MockLabelService implements ILabelService {
780
_serviceBrand: undefined;
781
getUriLabel(resource: URI, options?: { relative?: boolean | undefined; noPrefix?: boolean | undefined }): string {
782
return normalize(resource.fsPath);
783
}
784
getUriBasenameLabel(resource: URI): string {
785
throw new Error('Method not implemented.');
786
}
787
getWorkspaceLabel(workspace: URI | IWorkspaceIdentifier | IWorkspace, options?: { verbose: Verbosity }): string {
788
throw new Error('Method not implemented.');
789
}
790
getHostLabel(scheme: string, authority?: string): string {
791
throw new Error('Method not implemented.');
792
}
793
public getHostTooltip(): string | undefined {
794
throw new Error('Method not implemented.');
795
}
796
getSeparator(scheme: string, authority?: string): '/' | '\\' {
797
throw new Error('Method not implemented.');
798
}
799
registerFormatter(formatter: ResourceLabelFormatter): IDisposable {
800
throw new Error('Method not implemented.');
801
}
802
registerCachedFormatter(formatter: ResourceLabelFormatter): IDisposable {
803
throw new Error('Method not implemented.');
804
}
805
onDidChangeFormatters: Event<IFormatterChangeEvent> = new Emitter<IFormatterChangeEvent>().event;
806
}
807
808
class MockPathService implements IPathService {
809
_serviceBrand: undefined;
810
get path(): Promise<IPath> {
811
throw new Error('Property not implemented');
812
}
813
defaultUriScheme: string = Schemas.file;
814
fileURI(path: string): Promise<URI> {
815
throw new Error('Method not implemented.');
816
}
817
userHome(options?: { preferLocal: boolean }): Promise<URI>;
818
userHome(options: { preferLocal: true }): URI;
819
userHome(options?: { preferLocal: boolean }): Promise<URI> | URI {
820
const uri = URI.file('c:\\users\\username');
821
return options?.preferLocal ? uri : Promise.resolve(uri);
822
}
823
hasValidBasename(resource: URI, basename?: string): Promise<boolean>;
824
hasValidBasename(resource: URI, os: platform.OperatingSystem, basename?: string): boolean;
825
hasValidBasename(resource: URI, arg2?: string | platform.OperatingSystem, name?: string): boolean | Promise<boolean> {
826
throw new Error('Method not implemented.');
827
}
828
resolvedUserHome: URI | undefined;
829
}
830
831
class MockInputsConfigurationService extends TestConfigurationService {
832
public override getValue(arg1?: any, arg2?: any): any {
833
let configuration;
834
if (arg1 === 'tasks') {
835
configuration = {
836
inputs: [
837
{
838
id: 'input1',
839
type: 'promptString',
840
description: 'Enterinput1',
841
default: 'default input1'
842
},
843
{
844
id: 'input2',
845
type: 'pickString',
846
description: 'Enterinput1',
847
default: 'option2',
848
options: ['option1', 'option2', 'option3']
849
},
850
{
851
id: 'input3',
852
type: 'promptString',
853
description: 'Enterinput3',
854
default: 'default input3',
855
provide: true,
856
password: true
857
},
858
{
859
id: 'input4',
860
type: 'command',
861
command: 'command1',
862
args: {
863
value: 'arg for command'
864
}
865
}
866
]
867
};
868
}
869
return configuration;
870
}
871
872
public override inspect<T>(key: string, overrides?: IConfigurationOverrides): IConfigurationValue<T> {
873
return {
874
value: undefined,
875
defaultValue: undefined,
876
userValue: undefined,
877
overrideIdentifiers: []
878
};
879
}
880
}
881
882
suite('ConfigurationResolverExpression', () => {
883
ensureNoDisposablesAreLeakedInTestSuite();
884
885
test('parse empty object', () => {
886
const expr = ConfigurationResolverExpression.parse({});
887
assert.strictEqual(Array.from(expr.unresolved()).length, 0);
888
assert.deepStrictEqual(expr.toObject(), {});
889
});
890
891
test('parse simple string', () => {
892
const expr = ConfigurationResolverExpression.parse({ value: '${env:HOME}' });
893
const unresolved = Array.from(expr.unresolved());
894
assert.strictEqual(unresolved.length, 1);
895
assert.strictEqual(unresolved[0].name, 'env');
896
assert.strictEqual(unresolved[0].arg, 'HOME');
897
});
898
899
test('parse string with argument and colon', () => {
900
const expr = ConfigurationResolverExpression.parse({ value: '${config:path:to:value}' });
901
const unresolved = Array.from(expr.unresolved());
902
assert.strictEqual(unresolved.length, 1);
903
assert.strictEqual(unresolved[0].name, 'config');
904
assert.strictEqual(unresolved[0].arg, 'path:to:value');
905
});
906
907
test('parse object with nested variables', () => {
908
const expr = ConfigurationResolverExpression.parse({
909
name: '${env:USERNAME}',
910
path: '${env:HOME}/folder',
911
settings: {
912
value: '${config:path}'
913
},
914
array: ['${env:TERM}', { key: '${env:KEY}' }]
915
});
916
917
const unresolved = Array.from(expr.unresolved());
918
assert.strictEqual(unresolved.length, 5);
919
assert.deepStrictEqual(unresolved.map(r => r.name).sort(), ['config', 'env', 'env', 'env', 'env']);
920
});
921
922
test('resolve and get result', () => {
923
const expr = ConfigurationResolverExpression.parse({
924
name: '${env:USERNAME}',
925
path: '${env:HOME}/folder'
926
});
927
928
expr.resolve({ inner: 'env:USERNAME', id: '${env:USERNAME}', name: 'env', arg: 'USERNAME' }, 'testuser');
929
expr.resolve({ inner: 'env:HOME', id: '${env:HOME}', name: 'env', arg: 'HOME' }, '/home/testuser');
930
931
assert.deepStrictEqual(expr.toObject(), {
932
name: 'testuser',
933
path: '/home/testuser/folder'
934
});
935
});
936
937
test('keeps unresolved variables', () => {
938
const expr = ConfigurationResolverExpression.parse({
939
name: '${env:USERNAME}'
940
});
941
942
assert.deepStrictEqual(expr.toObject(), {
943
name: '${env:USERNAME}'
944
});
945
});
946
947
test('deduplicates identical variables', () => {
948
const expr = ConfigurationResolverExpression.parse({
949
first: '${env:HOME}',
950
second: '${env:HOME}'
951
});
952
953
const unresolved = Array.from(expr.unresolved());
954
assert.strictEqual(unresolved.length, 1);
955
assert.strictEqual(unresolved[0].name, 'env');
956
assert.strictEqual(unresolved[0].arg, 'HOME');
957
958
expr.resolve(unresolved[0], '/home/user');
959
assert.deepStrictEqual(expr.toObject(), {
960
first: '/home/user',
961
second: '/home/user'
962
});
963
});
964
965
test('handles root string value', () => {
966
const expr = ConfigurationResolverExpression.parse('abc ${env:HOME} xyz');
967
const unresolved = Array.from(expr.unresolved());
968
assert.strictEqual(unresolved.length, 1);
969
assert.strictEqual(unresolved[0].name, 'env');
970
assert.strictEqual(unresolved[0].arg, 'HOME');
971
972
expr.resolve(unresolved[0], '/home/user');
973
assert.strictEqual(expr.toObject(), 'abc /home/user xyz');
974
});
975
976
test('handles root string value with multiple variables', () => {
977
const expr = ConfigurationResolverExpression.parse('${env:HOME}/folder${env:SHELL}');
978
const unresolved = Array.from(expr.unresolved());
979
assert.strictEqual(unresolved.length, 2);
980
981
expr.resolve({ id: '${env:HOME}', inner: 'env:HOME', name: 'env', arg: 'HOME' }, '/home/user');
982
expr.resolve({ id: '${env:SHELL}', inner: 'env:SHELL', name: 'env', arg: 'SHELL' }, '/bin/bash');
983
assert.strictEqual(expr.toObject(), '/home/user/folder/bin/bash');
984
});
985
986
test('handles root string with escaped variables', () => {
987
const expr = ConfigurationResolverExpression.parse('abc ${env:HOME${env:USER}} xyz');
988
const unresolved = Array.from(expr.unresolved());
989
assert.strictEqual(unresolved.length, 1);
990
assert.strictEqual(unresolved[0].name, 'env');
991
assert.strictEqual(unresolved[0].arg, 'HOME${env:USER}');
992
});
993
994
test('resolves nested values', () => {
995
const expr = ConfigurationResolverExpression.parse({
996
name: '${env:REDIRECTED}',
997
'key that is ${env:REDIRECTED}': 'cool!',
998
});
999
1000
for (const r of expr.unresolved()) {
1001
if (r.arg === 'REDIRECTED') {
1002
expr.resolve(r, 'username: ${env:USERNAME}');
1003
} else if (r.arg === 'USERNAME') {
1004
expr.resolve(r, 'testuser');
1005
}
1006
}
1007
1008
assert.deepStrictEqual(expr.toObject(), {
1009
name: 'username: testuser',
1010
'key that is username: testuser': 'cool!'
1011
});
1012
});
1013
1014
test('resolves nested values 2 (#245798)', () => {
1015
const expr = ConfigurationResolverExpression.parse({
1016
env: {
1017
SITE: "${input:site}",
1018
TLD: "${input:tld}",
1019
HOST: "${input:host}",
1020
},
1021
});
1022
1023
for (const r of expr.unresolved()) {
1024
if (r.arg === 'site') {
1025
expr.resolve(r, 'example');
1026
} else if (r.arg === 'tld') {
1027
expr.resolve(r, 'com');
1028
} else if (r.arg === 'host') {
1029
expr.resolve(r, 'local.${input:site}.${input:tld}');
1030
}
1031
}
1032
1033
assert.deepStrictEqual(expr.toObject(), {
1034
env: {
1035
SITE: 'example',
1036
TLD: 'com',
1037
HOST: 'local.example.com'
1038
}
1039
});
1040
});
1041
1042
test('out-of-order key resolution (#248550)', () => {
1043
const expr = ConfigurationResolverExpression.parse({
1044
'${input:key}': "${input:value}",
1045
});
1046
1047
for (const r of expr.unresolved()) {
1048
if (r.arg === 'key') {
1049
expr.resolve(r, 'the-key');
1050
}
1051
}
1052
for (const r of expr.unresolved()) {
1053
if (r.arg === 'value') {
1054
expr.resolve(r, 'the-value');
1055
}
1056
}
1057
1058
assert.deepStrictEqual(expr.toObject(), {
1059
'the-key': 'the-value'
1060
});
1061
});
1062
});
1063
1064