Skip to content

Commit b4b34c3

Browse files
Improve overall escaping (#2651)
Backports 43d70b5 to the v2 release line. Co-authored-by: oran-s <oran@koi.ai> Assisted-by: Claude Mythos
1 parent bfbb1e6 commit b4b34c3

9 files changed

Lines changed: 274 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ Versioning].
99

1010
## Unreleased
1111

12-
- _No changes yet_
12+
- Correct escaping of `(` and `)` for CMD. ([#2651])
13+
- Correct escaping of `~` for Dash and Zsh. ([#2651])
14+
- Correct escaping of `^`, `~`, and `#` for Zsh with `EXTENDED_GLOB`. ([#2651])
15+
- Optimize flag protection implementation. ([#2651])
1316

1417
## 2.1.13 (2026-06-13)
1518

@@ -421,6 +424,7 @@ Versioning].
421424
[#2490]: https://github.com/ericcornelissen/shescape/pull/2490
422425
[#2520]: https://github.com/ericcornelissen/shescape/pull/2520
423426
[#2534]: https://github.com/ericcornelissen/shescape/pull/2534
427+
[#2651]: https://github.com/ericcornelissen/shescape/pull/2651
424428
[552e8ea]: https://github.com/ericcornelissen/shescape/commit/552e8eab56861720b1d4e5474fb65741643358f9
425429
[keep a changelog]: https://keepachangelog.com/en/1.0.0/
426430
[semantic versioning]: https://semver.org/spec/v2.0.0.html

config/eslint.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ export default [
716716
"no-magic-numbers": [
717717
"error",
718718
{
719-
ignore: [0, 1, 0xa0],
719+
ignore: [0, 1, 2, 0xa0],
720720
},
721721
],
722722
"no-misleading-character-class": ["error"],

src/internal/compose.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,17 @@ export function compose({ escapeFn, flagFn, quoteFn }) {
2727
}
2828

2929
return (arg) => {
30-
let [preFlag, , ...rest] = flagFn(arg);
31-
while (rest.length > 0 && escapeFn(preFlag) === "") {
32-
arg = rest.join("");
33-
[preFlag, , ...rest] = rest;
30+
const fragments = flagFn(arg);
31+
32+
let idx = 0;
33+
for (; idx < fragments.length - 2; idx += 2) {
34+
const escapedFragment = escapeFn(fragments[idx]);
35+
if (escapedFragment !== "") {
36+
break;
37+
}
3438
}
3539

40+
arg = fragments.slice(idx).join("");
3641
return escape(arg);
3742
};
3843
}

src/internal/unix/dash.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function getEscapeFunction() {
1616
const newlines = new RegExp(/\n/g);
1717
const backslashes = new RegExp(/\\/g);
1818
const comments = new RegExp(/(^|\s)#/g);
19-
const home = new RegExp(/(^|\s)~/g);
19+
const home = new RegExp(/(^|[\s:=])~/g);
2020
const specials = new RegExp(/(["$&'()*;<>?[\]`|])/g);
2121
const whitespace = new RegExp(/([\t ])/g);
2222
return (arg) =>

src/internal/unix/zsh.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@ export function getEscapeFunction() {
1515
const controls = new RegExp(/[\0\u0008\r\u001B\u009B]/g);
1616
const newlines = new RegExp(/\n/g);
1717
const backslashes = new RegExp(/\\/g);
18-
const comments = new RegExp(/(^|\s)#/g);
19-
const expansions = new RegExp(/(^|\s)([=~])/g);
20-
const specials = new RegExp(/(["$&'()*;<>?[\]`{|}])/g);
18+
const expansions = new RegExp(/(^|[\s:=])=/g);
19+
const specials = new RegExp(/(["#$&'()*;<>?[\]^`{|}~])/g);
2120
const whitespace = new RegExp(/([\t ])/g);
2221
return (arg) =>
2322
arg
2423
.replace(controls, "")
2524
.replace(newlines, " ")
2625
.replace(backslashes, "\\\\")
27-
.replace(comments, "$1\\#")
28-
.replace(expansions, "$1\\$2")
26+
.replace(expansions, "$1\\=")
2927
.replace(specials, "\\$1")
3028
.replace(whitespace, "\\$1");
3129
}

src/internal/win/cmd.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import RegExp from "../regexp.cjs";
1414
export function getEscapeFunction() {
1515
const controls = new RegExp(/[\0\u0008\r\u001B\u009B]/g);
1616
const newlines = new RegExp(/\n/g);
17-
const specials = new RegExp(/([%&<>^|])/g);
17+
const specials = new RegExp(/([%&()<>^|])/g);
1818
const quotes = new RegExp(/"/g);
1919
const backslashes = new RegExp(/(^|[^\\])(\\*)\0/g);
2020
return (arg) =>

0 commit comments

Comments
 (0)