Repository: microsoft/vscode (TypeScript)
Date: 2022-05-12 17:49:46+00:00
Task type: Bug Fix

Issue:
Some terminal launch config args are double-escaped
Run this launch config

```json
{
    "type": "node",
    "request": "launch",
    "name": "Run",
    "runtimeVersion": "16.4.0",
    "program": "${file}",
    "console": "integratedTerminal",
    "args": [">", "> hello.txt"]
},
```

This creates a file named `\> hello.txt` with a literal backslash, when it should be named `> hello.txt`


Issue discussion:

Solution patch:
diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts
--- a/src/vs/workbench/contrib/debug/node/terminals.ts
+++ b/src/vs/workbench/contrib/debug/node/terminals.ts
@@ -120,6 +120,10 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env?
 		case ShellType.cmd:
 
 			quote = (s: string) => {
+				// Note: Wrapping in cmd /C "..." complicates the escaping.
+				// cmd /C "node -e "console.log(process.argv)" """A^>0"""" # prints "A>0"
+				// cmd /C "node -e "console.log(process.argv)" "foo^> bar"" # prints foo> bar
+				// Outside of the cmd /C, it could be a simple quoting, but here, the ^ is needed too
 				s = s.replace(/\"/g, '""');
 				s = s.replace(/([><!^&|])/g, '^$1');
 				return (' "'.split('').some(char => s.includes(char)) || s.length === 0) ? `"${s}"` : s;
@@ -155,8 +159,8 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env?
 		case ShellType.bash: {
 
 			quote = (s: string) => {
-				s = s.replace(/(["'\\\$!><#()\[\]*&^|])/g, '\\$1');
-				return (' ;'.split('').some(char => s.includes(char)) || s.length === 0) ? `"${s}"` : s;
+				s = s.replace(/(["'\\\$!><#()\[\]*&^| ;])/g, '\\$1');
+				return s.length === 0 ? `""` : s;
 			};
 
 			const hardQuote = (s: string) => {


Test patch:
diff --git a/src/vs/workbench/contrib/debug/test/node/terminals.test.ts b/src/vs/workbench/contrib/debug/test/node/terminals.test.ts
new file mode 100644
--- /dev/null
+++ b/src/vs/workbench/contrib/debug/test/node/terminals.test.ts
@@ -0,0 +1,76 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as assert from 'assert';
+import { prepareCommand } from 'vs/workbench/contrib/debug/node/terminals';
+
+
+suite('Debug - prepareCommand', () => {
+	test('bash', () => {
+		assert.strictEqual(
+			prepareCommand('bash', ['{$} (']).trim(),
+			'{\\$}\\ \\(');
+		assert.strictEqual(
+			prepareCommand('bash', ['hello', 'world', '--flag=true']).trim(),
+			'hello world --flag=true');
+		assert.strictEqual(
+			prepareCommand('bash', [' space arg ']).trim(),
+			'\\ space\\ arg\\');
+	});
+
+	test('bash - do not escape > and <', () => {
+		assert.strictEqual(
+			prepareCommand('bash', ['arg1', '>', '> hello.txt', '<', '<input.in']).trim(),
+			'arg1 > \\>\\ hello.txt < \\<input.in');
+	});
+
+	test('cmd', () => {
+		assert.strictEqual(
+			prepareCommand('cmd.exe', ['^!< ']).trim(),
+			'"^^^!^< "');
+		assert.strictEqual(
+			prepareCommand('cmd.exe', ['hello', 'world', '--flag=true']).trim(),
+			'hello world --flag=true');
+		assert.strictEqual(
+			prepareCommand('cmd.exe', [' space arg ']).trim(),
+			'" space arg "');
+		assert.strictEqual(
+			prepareCommand('cmd.exe', ['"A>0"']).trim(),
+			'"""A^>0"""');
+		assert.strictEqual(
+			prepareCommand('cmd.exe', ['']).trim(),
+			'""');
+	});
+
+	test('cmd - do not escape > and <', () => {
+		assert.strictEqual(
+			prepareCommand('cmd.exe', ['arg1', '>', '> hello.txt', '<', '<input.in']).trim(),
+			'arg1 > "^> hello.txt" < ^<input.in');
+	});
+
+	test('powershell', () => {
+		assert.strictEqual(
+			prepareCommand('powershell', ['!< ']).trim(),
+			`& '!< '`);
+		assert.strictEqual(
+			prepareCommand('powershell', ['hello', 'world', '--flag=true']).trim(),
+			`& 'hello' 'world' '--flag=true'`);
+		assert.strictEqual(
+			prepareCommand('powershell', [' space arg ']).trim(),
+			`& ' space arg '`);
+		assert.strictEqual(
+			prepareCommand('powershell', ['"A>0"']).trim(),
+			`& '"A>0"'`);
+		assert.strictEqual(
+			prepareCommand('powershell', ['']).trim(),
+			`& ''`);
+	});
+
+	test('powershell - do not escape > and <', () => {
+		assert.strictEqual(
+			prepareCommand('powershell', ['arg1', '>', '> hello.txt', '<', '<input.in']).trim(),
+			`& 'arg1' > '> hello.txt' < '<input.in'`);
+	});
+});