Skip to content

Commit 0a9c8f8

Browse files
hanniavaleraCopilot
andauthored
Fix token splitting for Windows backslashes (#4912)
* Fix Windows backslash handling in token splitting to preserve trailing backslashes before whitespace * add changelog entry Co-authored-by: Copilot <[email protected]> * shorten comment --------- Co-authored-by: Hannia Valera <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 420ceb1 commit 0a9c8f8

3 files changed

Lines changed: 43 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Improvements:
1010
- Improve responsiveness to CMake path changes made by vendor extensions during configure-on-open retry. [#4908](https://github.com/microsoft/vscode-cmake-tools/pull/4908) Contributed by STMicroelectronics
1111

1212
Bug Fixes:
13+
- Fix Windows backslash handling in token splitting to preserve trailing backslashes before whitespace. This caused "Compile Active File" with MSVC + Ninja Multi-Config to merge adjacent flags (e.g., `/Fd<dir>\ /FS`) into a single malformed argument. [#4902](https://github.com/microsoft/vscode-cmake-tools/issues/4902)
1314
- Fix kit detection returning "unknown vendor" when using clang-cl compiler. [#4638](https://github.com/microsoft/vscode-cmake-tools/issues/4638)
1415
- Update testing framework to fix bugs when running tests of CMake Tools without a reliable internet connection. [#4891](https://github.com/microsoft/vscode-cmake-tools/pull/4891) [@cwalther](https://github.com/cwalther)
1516

src/shlex.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ export function* split(str: string, opt?: ShlexOptions): Iterable<string> {
5252
// Windows mode: only backslash can be escaped
5353
if (escapeChars.includes(char)) {
5454
token.push(char);
55+
} else if (!quoteChar && /[\t \r\f]/.test(char)) {
56+
// Backslash followed by whitespace outside quotes: backslash is literal,
57+
// whitespace terminates the token. See issue #4902.
58+
token.push(escapeChar);
59+
yield token.join('');
60+
token = [];
61+
escapeChar = undefined;
62+
continue;
5563
} else {
5664
token.push(escapeChar, char);
5765
}

test/unit-tests/backend/shlex.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,40 @@ suite('shlex testing (backend)', () => {
3131
}
3232
});
3333

34+
test('Windows trailing backslash before space terminates token (issue #4902)', () => {
35+
// Minimal case: backslash before space outside quotes is literal,
36+
// and the space must still act as a delimiter.
37+
expect(splitWin('foo\\ bar')).to.deep.equal(['foo\\', 'bar']);
38+
});
39+
40+
test('Windows /Fd<dir>\\ /FS compile command (issue #4902)', () => {
41+
// Reproduces the exact compile_commands.json shape that breaks
42+
// "Compile Active File" with MSVC + Ninja Multi-Config when no
43+
// COMPILE_PDB_NAME is set. CMake emits /Fd<dir>\ (trailing backslash)
44+
// followed by /FS; cl.exe must receive these as two separate args.
45+
const cmd = 'cl.exe /nologo /FdCMakeFiles\\INPUT_TESTS.dir\\RelWithDebInfo\\ /FS /c foo.cpp';
46+
expect(splitWin(cmd)).to.deep.equal([
47+
'cl.exe',
48+
'/nologo',
49+
'/FdCMakeFiles\\INPUT_TESTS.dir\\RelWithDebInfo\\',
50+
'/FS',
51+
'/c',
52+
'foo.cpp'
53+
]);
54+
});
55+
56+
test('Windows backslash runs before space (issue #4902 edge cases)', () => {
57+
// Existing Windows-mode behavior collapses pairs of backslashes (\\ -> \),
58+
// and a trailing odd backslash before whitespace is now preserved literally.
59+
expect(splitWin('foo\\ bar')).to.deep.equal(['foo\\', 'bar']); // 1 backslash
60+
expect(splitWin('foo\\\\ bar')).to.deep.equal(['foo\\', 'bar']); // 2 -> collapses to 1, space delimits
61+
expect(splitWin('foo\\\\\\ bar')).to.deep.equal(['foo\\\\', 'bar']); // 3 -> 1 (collapsed pair) + 1 literal trailing
62+
});
63+
64+
test('Windows backslash before tab also terminates token', () => {
65+
expect(splitWin('foo\\\tbar')).to.deep.equal(['foo\\', 'bar']);
66+
});
67+
3468
test('Windows mode: backslash only escapes backslash, not quotes', () => {
3569
// -DAWESOME=\"\\\"'fo o' bar\\\"\" should preserve all escapes
3670
const cmd = `-DAWESOME=\"\\\"'fo o' bar\\\"\"`;

0 commit comments

Comments
 (0)