Skip to content

Commit ddd44e0

Browse files
committed
Fixes microsoft#45576: Allow users to control quoting in tasks
1 parent f5bf952 commit ddd44e0

11 files changed

Lines changed: 589 additions & 96 deletions

File tree

src/vs/vscode.d.ts

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4332,6 +4332,38 @@ declare module 'vscode' {
43324332
options?: ProcessExecutionOptions;
43334333
}
43344334

4335+
/**
4336+
* The shell quoting options.
4337+
*/
4338+
export interface ShellQuotingOptions {
4339+
4340+
/**
4341+
* The character used to do character escaping. If a string is provided only spaces
4342+
* are escaped. If a `{ escapeChar, charsToEscape }` literal is provide all characters
4343+
* in `charsToEscape` are escaped using the `escapeChar`.
4344+
*/
4345+
escape?: string | {
4346+
/**
4347+
* The escape character.
4348+
*/
4349+
escapeChar: string;
4350+
/**
4351+
* The characters to escape.
4352+
*/
4353+
charsToEscape: string;
4354+
};
4355+
4356+
/**
4357+
* The character used for strong quoting. The string's length must be 1.
4358+
*/
4359+
strong?: string;
4360+
4361+
/**
4362+
* The character used for weak quoting. The string's length must be 1.
4363+
*/
4364+
weak?: string;
4365+
}
4366+
43354367
/**
43364368
* Options for a shell execution
43374369
*/
@@ -4346,6 +4378,11 @@ declare module 'vscode' {
43464378
*/
43474379
shellArgs?: string[];
43484380

4381+
/**
4382+
* The shell quotes supported by this shell.
4383+
*/
4384+
shellQuoting?: ShellQuotingOptions;
4385+
43494386
/**
43504387
* The current working directory of the executed shell.
43514388
* If omitted the tools current workspace root is used.
@@ -4360,21 +4397,88 @@ declare module 'vscode' {
43604397
env?: { [key: string]: string };
43614398
}
43624399

4400+
/**
4401+
* Defines how an argument should be quoted if it contains
4402+
* spaces or unsuppoerted characters.
4403+
*/
4404+
export enum ShellQuoting {
4405+
4406+
/**
4407+
* Character escaping should be used. This for example
4408+
* uses \ on bash and ` on PowerShell.
4409+
*/
4410+
Escape = 1,
4411+
4412+
/**
4413+
* Strong string quoting should be used. This for example
4414+
* uses " for Windows cmd and ' for bash and PowerShell.
4415+
* Strong quoting treats arguments as literal strings.
4416+
* Under PowerShell echo 'The value is $(2 * 3)' will
4417+
* print `The value is $(2 * 3)`
4418+
*/
4419+
Strong = 2,
4420+
4421+
/**
4422+
* Weak string quoting should be used. This for example
4423+
* uses " for Windows cmd, bash and PowerShell. Weak quoting
4424+
* still performs some kind of evaluation inside the quoted
4425+
* string. Under PowerShell echo "The value is $(2 * 3)"
4426+
* will print `The value is 6`
4427+
*/
4428+
Weak = 3
4429+
}
4430+
4431+
/**
4432+
* A string that will be quoted depending on the used shell.
4433+
*/
4434+
export interface ShellQuotedString {
4435+
/**
4436+
* The actual string value.
4437+
*/
4438+
value: string;
4439+
4440+
/**
4441+
* The quoting style to use.
4442+
*/
4443+
quoting: ShellQuoting;
4444+
}
43634445

43644446
export class ShellExecution {
43654447
/**
4366-
* Creates a process execution.
4448+
* Creates a shell execution with a full command line.
43674449
*
43684450
* @param commandLine The command line to execute.
43694451
* @param options Optional options for the started the shell.
43704452
*/
43714453
constructor(commandLine: string, options?: ShellExecutionOptions);
43724454

43734455
/**
4374-
* The shell command line
4456+
* Creates a shell execution with a command and arguments. For the real execution VS Code will
4457+
* construct a command line from the command and the arguments. This is subject to interpretation
4458+
* especially when it comes to quoting. If full control over the command line is needed please
4459+
* use the constructor that creates a `ShellExecution` with the full command line.
4460+
*
4461+
* @param command The command to execute.
4462+
* @param args The command arguments.
4463+
* @param options Optional options for the started the shell.
4464+
*/
4465+
constructor(command: string | ShellQuotedString, args: (string | ShellQuotedString)[], options?: ShellExecutionOptions);
4466+
4467+
/**
4468+
* The shell command line. Is `undefined` if created with a command and arguments.
43754469
*/
43764470
commandLine: string;
43774471

4472+
/**
4473+
* The shell command. Is `undefined` if created with a full command line.
4474+
*/
4475+
command: string | ShellQuotedString;
4476+
4477+
/**
4478+
* The shell args. Is `undefined` if created with a full command line.
4479+
*/
4480+
args: (string | ShellQuotedString)[];
4481+
43784482
/**
43794483
* The shell options used when the command line is executed in a shell.
43804484
* Defaults to undefined.

src/vs/workbench/api/node/extHost.api.impl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,7 @@ export function createApiFactory(
638638
TaskGroup: extHostTypes.TaskGroup,
639639
ProcessExecution: extHostTypes.ProcessExecution,
640640
ShellExecution: extHostTypes.ShellExecution,
641+
ShellQuoting: extHostTypes.ShellQuoting,
641642
TaskScope: extHostTypes.TaskScope,
642643
Task: extHostTypes.Task,
643644
ConfigurationTarget: extHostTypes.ConfigurationTarget,

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

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -278,20 +278,43 @@ namespace CommandOptions {
278278
}
279279
}
280280

281+
namespace ShellQuoteOptions {
282+
export function from(value: vscode.ShellQuotingOptions): TaskSystem.ShellQuotingOptions {
283+
if (value === void 0 || value === null) {
284+
return undefined;
285+
}
286+
return {
287+
escape: value.escape,
288+
strong: value.strong,
289+
weak: value.strong
290+
};
291+
}
292+
}
293+
281294
namespace ShellConfiguration {
282-
export function from(value: { executable?: string, shellArgs?: string[] }): TaskSystem.ShellConfiguration {
295+
export function from(value: { executable?: string, shellArgs?: string[], quotes?: vscode.ShellQuotingOptions }): TaskSystem.ShellConfiguration {
283296
if (value === void 0 || value === null || !value.executable) {
284297
return undefined;
285298
}
286299

287300
let result: TaskSystem.ShellConfiguration = {
288301
executable: value.executable,
289-
args: Strings.from(value.shellArgs)
302+
args: Strings.from(value.shellArgs),
303+
quoting: ShellQuoteOptions.from(value.quotes)
290304
};
291305
return result;
292306
}
293307
}
294308

309+
namespace ShellString {
310+
export function from(value: (string | vscode.ShellQuotedString)[]): TaskSystem.CommandString[] {
311+
if (value === void 0 || value === null) {
312+
return undefined;
313+
}
314+
return value.slice(0);
315+
}
316+
}
317+
295318
namespace Tasks {
296319

297320
export function from(tasks: vscode.Task[], rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): TaskSystem.ContributedTask[] {
@@ -396,18 +419,34 @@ namespace Tasks {
396419
}
397420

398421
function getShellCommand(value: vscode.ShellExecution): TaskSystem.CommandConfiguration {
399-
if (typeof value.commandLine !== 'string') {
400-
return undefined;
401-
}
402-
let result: TaskSystem.CommandConfiguration = {
403-
name: value.commandLine,
404-
runtime: TaskSystem.RuntimeType.Shell,
405-
presentation: undefined
406-
};
407-
if (value.options) {
408-
result.options = CommandOptions.from(value.options);
422+
if (value.args) {
423+
if (typeof value.command !== 'string' && typeof value.command.value !== 'string') {
424+
return undefined;
425+
}
426+
let result: TaskSystem.CommandConfiguration = {
427+
name: value.command,
428+
args: ShellString.from(value.args),
429+
runtime: TaskSystem.RuntimeType.Shell,
430+
presentation: undefined
431+
};
432+
if (value.options) {
433+
result.options = CommandOptions.from(value.options);
434+
}
435+
return result;
436+
} else {
437+
if (typeof value.commandLine !== 'string') {
438+
return undefined;
439+
}
440+
let result: TaskSystem.CommandConfiguration = {
441+
name: value.commandLine,
442+
runtime: TaskSystem.RuntimeType.Shell,
443+
presentation: undefined
444+
};
445+
if (value.options) {
446+
result.options = CommandOptions.from(value.options);
447+
}
448+
return result;
409449
}
410-
return result;
411450
}
412451
}
413452

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

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,14 +1311,30 @@ export class ProcessExecution implements vscode.ProcessExecution {
13111311
export class ShellExecution implements vscode.ShellExecution {
13121312

13131313
private _commandLine: string;
1314+
private _command: string | vscode.ShellQuotedString;
1315+
private _args: (string | vscode.ShellQuotedString)[];
13141316
private _options: vscode.ShellExecutionOptions;
13151317

1316-
constructor(commandLine: string, options?: vscode.ShellExecutionOptions) {
1317-
if (typeof commandLine !== 'string') {
1318-
throw illegalArgument('commandLine');
1318+
constructor(commandLine: string, options?: vscode.ShellExecutionOptions);
1319+
constructor(command: string | vscode.ShellQuotedString, args: (string | vscode.ShellQuotedString)[], options?: vscode.ShellExecutionOptions);
1320+
constructor(arg0: string | vscode.ShellQuotedString, arg1?: vscode.ShellExecutionOptions | (string | vscode.ShellQuotedString)[], arg2?: vscode.ShellExecutionOptions) {
1321+
if (Array.isArray(arg1)) {
1322+
if (!arg0) {
1323+
throw illegalArgument('command can\'t be undefined or null');
1324+
}
1325+
if (typeof arg0 !== 'string' && typeof arg0.value !== 'string') {
1326+
throw illegalArgument('command');
1327+
}
1328+
this._command = arg0;
1329+
this._args = arg1 as (string | vscode.ShellQuotedString)[];
1330+
this._options = arg2;
1331+
} else {
1332+
if (typeof arg0 !== 'string') {
1333+
throw illegalArgument('commandLine');
1334+
}
1335+
this._commandLine = arg0;
1336+
this._options = arg1;
13191337
}
1320-
this._commandLine = commandLine;
1321-
this._options = options;
13221338
}
13231339

13241340
get commandLine(): string {
@@ -1332,6 +1348,25 @@ export class ShellExecution implements vscode.ShellExecution {
13321348
this._commandLine = value;
13331349
}
13341350

1351+
get command(): string | vscode.ShellQuotedString {
1352+
return this._command;
1353+
}
1354+
1355+
set command(value: string | vscode.ShellQuotedString) {
1356+
if (typeof value !== 'string' && typeof value.value !== 'string') {
1357+
throw illegalArgument('command');
1358+
}
1359+
this._command = value;
1360+
}
1361+
1362+
get args(): (string | vscode.ShellQuotedString)[] {
1363+
return this._args;
1364+
}
1365+
1366+
set args(value: (string | vscode.ShellQuotedString)[]) {
1367+
this._args = value || [];
1368+
}
1369+
13351370
get options(): vscode.ShellExecutionOptions {
13361371
return this._options;
13371372
}
@@ -1341,6 +1376,12 @@ export class ShellExecution implements vscode.ShellExecution {
13411376
}
13421377
}
13431378

1379+
export enum ShellQuoting {
1380+
Escape = 1,
1381+
Strong = 2,
1382+
Weak = 3
1383+
}
1384+
13441385
export enum TaskScope {
13451386
Global = 1,
13461387
Workspace = 2

0 commit comments

Comments
 (0)