Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions config/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,12 @@ export default [
"grouped-accessor-pairs": ["error"],
"guard-for-in": ["error"],
"id-denylist": ["error"],
"id-length": ["error"],
"id-length": [
"error",
{
exceptions: ["_"],
},
],
"id-match": ["error"],
"init-declarations": ["off"],
"logical-assignment-operators": ["error"],
Expand All @@ -647,7 +652,12 @@ export default [
"max-lines": ["off"],
"max-lines-per-function": ["off"],
"max-nested-callbacks": ["error"],
"max-params": ["error"],
"max-params": [
"error",
{
max: 4,
},
],
"max-statements": ["off"],
"new-cap": ["error"],
"no-alert": ["error"],
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,10 @@
"format": "npm run _prettier -- --write",
"fuzz": "node script/fuzz.js",
"mutation": "npm-run-all mutation:*",
"mutation:unit": "stryker run config/stryker/unit.js",
"mutation:unit": "echo TODO",
"mutation:integration": "npm run transpile && stryker run config/stryker/integration.js",
"test": "npm-run-all test:*",
"test:unit": "ava test/unit/**/*.test.js",
"test:unit": "ava --timeout 59m test/unit/**/*.test.js",
"test:integration": "npm run transpile && ava test/integration/**/*.test.js --timeout 2m",
"test:e2e": "node script/busybox-sh.js && node script/double-link-sh.js && ava test/e2e/**/*.test.js --timeout 1m",
"test:compat": "npm-run-all test:compat:*",
Expand Down
110 changes: 66 additions & 44 deletions src/internal/win/powershell.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,7 @@
* @license MPL-2.0
*/

/**
* Escape an argument for use in PowerShell.
*
* @param {string} arg The argument to escape.
* @returns {string} The escaped argument.
*/
function escapeArg(arg) {
arg = arg
.replace(/[\0\u0008\r\u001B\u009B]/gu, "")
.replace(/\n/gu, " ")
.replace(/`/gu, "``")
.replace(/(?<=^|[\s\u0085])([*1-6]?)(>)/gu, "$1`$2")
.replace(/(?<=^|[\s\u0085])([#\-:<@\]])/gu, "`$1")
.replace(/([$&'(),;{|}‘’‚‛“”„])/gu, "`$1");

if (/[\s\u0085]/u.test(arg.replace(/^[\s\u0085]+/gu, ""))) {
arg = arg
.replace(/(?<!\\)(\\*)"/gu, '$1$1`"`"')
.replace(/(?<!\\)(\\+)$/gu, "$1$1");
} else {
arg = arg.replace(/(?<!\\)(\\*)"/gu, '$1$1\\`"');
}

arg = arg.replace(/([\s\u0085])/gu, "`$1");

return arg;
}
import RegExp from "@ericcornelissen/lregexp";

/**
* Returns a function to escape arguments for use in PowerShell for the given
Expand All @@ -38,30 +12,78 @@ function escapeArg(arg) {
* @returns {function(string): string} A function to escape arguments.
*/
export function getEscapeFunction() {
return escapeArg;
const controls = new RegExp("[\0\u0008\r\u001B\u009B]", "g");
const newlines = new RegExp("\n", "g");
const backticks = new RegExp("`", "g");
const redirects = new RegExp("(^|[\\s\u0085])([*1-6]?)(>)", "g");
const specials1 = new RegExp("(^|[\\s\u0085])([#\\-:<@\\]])", "g");
const specials2 = new RegExp("([$&'(),;{|}‘’‚‛“”„])", "g");

const whitespace = new RegExp("([\\s\u0085])", "g");
const whitespacePrefix = new RegExp("^[\\s\u0085]+");

const quote = new RegExp('"', "g");
const backslashBeforeQuote = new RegExp("(^|[^\\\\])(\\\\*)\0", "g");

const backslashSuffix = new RegExp("([^\\\\])(\\\\+)$");

return (arg) => {
arg = arg
.replace(controls, "")
.replace(newlines, " ")
.replace(backticks, "``")
.replace(redirects, "$1$2`$3")
.replace(specials1, "$1`$2")
.replace(specials2, "`$1");

if (whitespace.test(arg.replace(whitespacePrefix, ""))) {
arg = arg
.replace(quote, '\0`"`"')
.replace(backslashBeforeQuote, "$1$2$2")
.replace(backslashSuffix, "$1$2$2");
} else {
arg = arg
.replace(quote, '\0\\`"')
.replace(backslashBeforeQuote, "$1$2$2");
}

arg = arg.replace(whitespace, "`$1");

return arg;
};
}

/**
* Escape an argument for use in PowerShell when the argument is being quoted.
*
* @param {string} arg The argument to escape.
* @returns {string} The escaped argument.
* @returns {function(string): string} A function to escape arguments.
*/
function escapeArgForQuoted(arg) {
arg = arg
.replace(/[\0\u0008\u001B\u009B]/gu, "")
.replace(/\r(?!\n)/gu, "")
.replace(/(['‘’‚‛])/gu, "$1$1");
function getQuoteEscapeFunction() {
const controls = new RegExp("[\0\u0008\u001B\u009B]", "g");
const crs = new RegExp("(\r\n)|\r", "g");
const quotes = new RegExp("(['‘’‚‛])", "g");

if (/[\s\u0085]/u.test(arg)) {
arg = arg
.replace(/(?<!\\)(\\*)"/gu, '$1$1""')
.replace(/(?<!\\)(\\+)$/gu, "$1$1");
} else {
arg = arg.replace(/(?<!\\)(\\*)"/gu, '$1$1\\"');
}
const whitespace = new RegExp("[\\s\u0085]");

const quote = new RegExp('"', "g");
const backslashBeforeQuote = new RegExp("(^|[^\\\\])(\\\\*)\0", "g");

const backslashSuffix = new RegExp("([^\\\\])(\\\\+)$");

return (arg) => {
arg = arg.replace(controls, "").replace(crs, "$1").replace(quotes, "$1$1");

if (whitespace.test(arg)) {
arg = arg
.replace(quote, '\0""')
.replace(backslashBeforeQuote, "$1$2$2")
.replace(backslashSuffix, "$1$2$2");
} else {
arg = arg.replace(quote, '\0\\"').replace(backslashBeforeQuote, "$1$2$2");
}

return arg;
return arg;
};
}

/**
Expand All @@ -81,7 +103,7 @@ function quoteArg(arg) {
* @returns {(function(string): string)[]} A function pair to escape & quote arguments.
*/
export function getQuoteFunction() {
return [escapeArgForQuoted, quoteArg];
return [getQuoteEscapeFunction(), quoteArg];
}

/**
Expand Down
100 changes: 100 additions & 0 deletions test/fixtures/win.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,10 @@ export const escape = {
input: '"a',
expected: '"a',
},
{
input: 'a""b',
expected: 'a""b',
},
],
"backticks ('`')": [
{
Expand Down Expand Up @@ -2984,12 +2988,24 @@ export const escape = {
input: '"a',
expected: '\\`"a',
},
{
input: 'a""b',
expected: 'a\\`"\\`"b',
},
{
input: 'a b""c',
expected: 'a` b`"`"`"`"c',
},
],
"double quotes ('\"') + backslashes ('\\')": [
{
input: 'a\\"b',
expected: 'a\\\\\\`"b',
},
{
input: 'a\\\\"b',
expected: 'a\\\\\\\\\\`"b',
},
{
input: 'a\\"b\\"c',
expected: 'a\\\\\\`"b\\\\\\`"c',
Expand All @@ -3002,6 +3018,10 @@ export const escape = {
input: '\\"a',
expected: '\\\\\\`"a',
},
{
input: '"\\"',
expected: '\\`"\\\\\\`"',
},
],
"double quotes ('\"') + whitespace": [
{
Expand Down Expand Up @@ -3074,6 +3094,10 @@ export const escape = {
input: "a\u0085@b",
expected: "a`\u0085`@b",
},
{
input: "a @b @c",
expected: "a` `@b` `@c",
},
],
"hashtags ('#')": [
{
Expand Down Expand Up @@ -3110,6 +3134,10 @@ export const escape = {
input: "a\u0085#b",
expected: "a`\u0085`#b",
},
{
input: "a #b #c",
expected: "a` `#b` `#c",
},
],
"carets ('^')": [
{
Expand Down Expand Up @@ -3218,6 +3246,10 @@ export const escape = {
input: "a\u0085-b",
expected: "a`\u0085`-b",
},
{
input: "a -b -c",
expected: "a` `-b` `-c",
},
],
"backslashes ('\\')": [
{
Expand All @@ -3236,6 +3268,10 @@ export const escape = {
input: "a\\",
expected: "a\\",
},
{
input: "\\",
expected: "\\",
},
],
"backslashes ('\\') + whitespace": [
{
Expand Down Expand Up @@ -3274,6 +3310,14 @@ export const escape = {
input: " a b\\",
expected: "` ` a` b\\\\",
},
{
input: " \\",
expected: "` \\",
},
{
input: "\\ \\",
expected: "\\` \\\\",
},
],
"colons (':')": [
{
Expand Down Expand Up @@ -3310,6 +3354,10 @@ export const escape = {
input: "a\u0085:b",
expected: "a`\u0085`:b",
},
{
input: "a :b :c",
expected: "a` `:b` `:c",
},
],
"semicolons (';')": [
{
Expand Down Expand Up @@ -3466,6 +3514,18 @@ export const escape = {
input: "a\u0085]b",
expected: "a`\u0085`]b",
},
{
input: "a [b [c",
expected: "a` [b` [c",
},
{
input: "a ]b ]c",
expected: "a` `]b` `]c",
},
{
input: "a [b ]c",
expected: "a` [b` `]c",
},
],
"curly brackets ('{', '}')": [
{
Expand Down Expand Up @@ -3568,6 +3628,14 @@ export const escape = {
input: "a\u0085>b",
expected: "a`\u0085`>b",
},
{
input: "a <b <c",
expected: "a` `<b` `<c",
},
{
input: "a >b >c",
expected: "a` `>b` `>c",
},
],
"right angle brackets ('>') + digits (/[0-9]/)": [
{
Expand Down Expand Up @@ -3650,6 +3718,10 @@ export const escape = {
input: "a 0>b",
expected: "a` 0>b",
},
{
input: "a 0>b 1>c 2>d 3>e 4>f 5>g 6>h 7>i 8>j 9>k",
expected: "a` 0>b` 1`>c` 2`>d` 3`>e` 4`>f` 5`>g` 6`>h` 7>i` 8>j` 9>k",
},
{
input: "a\t1>b",
expected: "a`\t1`>b",
Expand Down Expand Up @@ -3748,6 +3820,10 @@ export const escape = {
input: "a\u0085*>b",
expected: "a`\u0085*`>b",
},
{
input: "a *>b *>c",
expected: "a` *`>b` *`>c",
},
],
"left double quotation mark ('“')": [
{
Expand Down Expand Up @@ -5218,6 +5294,14 @@ export const quote = {
input: '"a',
expected: "'\\\"a'",
},
{
input: 'a""b',
expected: "'a\\\"\\\"b'",
},
{
input: 'a b""c',
expected: '\'a b""""c\'',
},
],
"double quotes ('\"') + backslashes ('\\')": [
{
Expand All @@ -5228,6 +5312,22 @@ export const quote = {
input: 'a\\\\"b',
expected: "'a\\\\\\\\\\\"b'",
},
{
input: 'a\\"b\\"c',
expected: "'a\\\\\\\"b\\\\\\\"c'",
},
{
input: 'a\\"',
expected: "'a\\\\\\\"'",
},
{
input: '\\"a',
expected: "'\\\\\\\"a'",
},
{
input: '"\\"',
expected: "'\\\"\\\\\\\"'",
},
],
"double quotes ('\"') + whitespace": [
{
Expand Down
Loading
Loading