Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/agentHost/test/common/commandLineHelpers.test.ts
13399 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 { isWindows } from '../../../../base/common/platform.js';
8
import { URI } from '../../../../base/common/uri.js';
9
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
10
import { extractCdPrefix, stripRedundantCdPrefix } from '../../common/commandLineHelpers.js';
11
12
suite('extractCdPrefix', () => {
13
14
ensureNoDisposablesAreLeakedInTestSuite();
15
16
const cases: Array<{
17
name: string;
18
commandLine: string;
19
isPowerShell: boolean;
20
expected: { directory: string; command: string } | undefined;
21
}> = [
22
// bash matches
23
{ name: 'bash: simple cd', commandLine: 'cd /tmp && ls', isPowerShell: false, expected: { directory: '/tmp', command: 'ls' } },
24
{ name: 'bash: quoted dir', commandLine: 'cd "/path with spaces" && ls -la', isPowerShell: false, expected: { directory: '/path with spaces', command: 'ls -la' } },
25
{ name: 'bash: extra spaces after &&', commandLine: 'cd /tmp && echo hi', isPowerShell: false, expected: { directory: '/tmp', command: 'echo hi' } },
26
27
// bash non-matches
28
{ name: 'bash: no cd prefix', commandLine: 'ls -la', isPowerShell: false, expected: undefined },
29
{ name: 'bash: cd with semicolon (not allowed in bash variant)', commandLine: 'cd /tmp; ls', isPowerShell: false, expected: undefined },
30
{ name: 'bash: cd alone', commandLine: 'cd /tmp', isPowerShell: false, expected: undefined },
31
{ name: 'bash: Set-Location not bash', commandLine: 'Set-Location /tmp && ls', isPowerShell: false, expected: undefined },
32
33
// powershell matches
34
{ name: 'pwsh: cd && ', commandLine: 'cd C:\\foo && dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },
35
{ name: 'pwsh: cd ;', commandLine: 'cd C:\\foo; dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },
36
{ name: 'pwsh: cd /d', commandLine: 'cd /d C:\\foo && dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },
37
{ name: 'pwsh: Set-Location', commandLine: 'Set-Location C:\\foo; dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },
38
{ name: 'pwsh: Set-Location -Path', commandLine: 'Set-Location -Path C:\\foo && dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },
39
{ name: 'pwsh: quoted dir', commandLine: 'cd "C:\\path with spaces"; dir', isPowerShell: true, expected: { directory: 'C:\\path with spaces', command: 'dir' } },
40
{ name: 'pwsh: case insensitive', commandLine: 'CD C:\\foo && dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },
41
42
// powershell non-matches
43
{ name: 'pwsh: no cd prefix', commandLine: 'dir', isPowerShell: true, expected: undefined },
44
{ name: 'pwsh: cd alone', commandLine: 'cd C:\\foo', isPowerShell: true, expected: undefined },
45
];
46
47
for (const tc of cases) {
48
test(tc.name, () => {
49
const result = extractCdPrefix(tc.commandLine, tc.isPowerShell);
50
assert.deepStrictEqual(result, tc.expected);
51
});
52
}
53
});
54
55
suite('stripRedundantCdPrefix', () => {
56
57
ensureNoDisposablesAreLeakedInTestSuite();
58
59
const wd = URI.file('/repo/project');
60
61
test('rewrites bash command when cd matches working directory', () => {
62
const params: Record<string, unknown> = { command: 'cd /repo/project && npm test' };
63
const changed = stripRedundantCdPrefix('bash', params, wd);
64
assert.strictEqual(changed, true);
65
assert.strictEqual(params.command, 'npm test');
66
});
67
68
test('rewrites bash command tolerating trailing slash', () => {
69
const params: Record<string, unknown> = { command: 'cd /repo/project/ && ls' };
70
const changed = stripRedundantCdPrefix('bash', params, wd);
71
assert.strictEqual(changed, true);
72
assert.strictEqual(params.command, 'ls');
73
});
74
75
test('does not rewrite when cd target differs', () => {
76
const params: Record<string, unknown> = { command: 'cd /tmp && ls' };
77
const changed = stripRedundantCdPrefix('bash', params, wd);
78
assert.strictEqual(changed, false);
79
assert.strictEqual(params.command, 'cd /tmp && ls');
80
});
81
82
test('does not rewrite for non-shell tools', () => {
83
const params: Record<string, unknown> = { command: 'cd /repo/project && ls' };
84
const changed = stripRedundantCdPrefix('read_file', params, wd);
85
assert.strictEqual(changed, false);
86
assert.strictEqual(params.command, 'cd /repo/project && ls');
87
});
88
89
test('handles missing working directory', () => {
90
const params: Record<string, unknown> = { command: 'cd /repo/project && ls' };
91
const changed = stripRedundantCdPrefix('bash', params, undefined);
92
assert.strictEqual(changed, false);
93
});
94
95
test('handles missing parameters', () => {
96
const changed = stripRedundantCdPrefix('bash', undefined, wd);
97
assert.strictEqual(changed, false);
98
});
99
100
test('handles non-string command', () => {
101
const params: Record<string, unknown> = { command: 42 };
102
const changed = stripRedundantCdPrefix('bash', params, wd);
103
assert.strictEqual(changed, false);
104
});
105
106
test('rewrites powershell with semicolon separator', () => {
107
const params: Record<string, unknown> = { command: 'cd /repo/project; dir' };
108
const changed = stripRedundantCdPrefix('powershell', params, wd);
109
assert.strictEqual(changed, true);
110
assert.strictEqual(params.command, 'dir');
111
});
112
113
test('matches mixed path separators (forward-slash extracted vs native fsPath wd)', () => {
114
// On Windows, the model may emit `cd C:/repo/project && …` while
115
// URI.file('C:\\repo\\project').fsPath uses backslashes. The helper
116
// must normalize separators so the prefix is still recognized.
117
// On POSIX, `C:\…` is not a meaningful path, so the cross-separator
118
// test only makes sense on Windows.
119
if (!isWindows) {
120
return;
121
}
122
const winWd = URI.file('C:\\repo\\project');
123
const params: Record<string, unknown> = { command: 'cd C:/repo/project && npm test' };
124
const changed = stripRedundantCdPrefix('bash', params, winWd);
125
assert.strictEqual(changed, true);
126
assert.strictEqual(params.command, 'npm test');
127
});
128
129
test('matches backslash extracted dir against backslash native wd on Windows', () => {
130
// Inverse direction: the model emits backslashes and the native wd is
131
// also backslashes. This is the most common Windows case and must
132
// match without relying on POSIX-shape paths.
133
if (!isWindows) {
134
return;
135
}
136
const winWd = URI.file('C:\\repo\\project');
137
const params: Record<string, unknown> = { command: 'cd C:\\repo\\project && ls' };
138
const changed = stripRedundantCdPrefix('bash', params, winWd);
139
assert.strictEqual(changed, true);
140
assert.strictEqual(params.command, 'ls');
141
});
142
});
143
144