Repository: sindresorhus/execa (JavaScript)

Issue:
Escaped newlines break arguments in version 9
After upgrading from version 8 to 9, some of my multi-line formatted commands have broken.

Reproduction:

```js
import {$} from "execa";

try {
  // 404 is just a stupid way to get an error for some details.
  // Some commands are large, so I break them up by escaping newlines in templates:
  //   https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#multi-line_strings
  await $`404 abc\
  def`;
} catch (error) {
  console.log("command", error.command); // 404 abc  ef
  console.log("escaped", error.escapedCommand); // 404 "abc " ef
}
```

Expected command: `404 abc  def` or something close enough
Actual commands: `404 abc  ef` or `404 "abc " ef`

I suspect it may be related to the difference between the template strings and the raw strings passed to a tag function. Example:

```js
function poo(foo, ...bar) {
  console.log("strings", foo.slice());
  console.log("raw", foo.raw);
}

poo`404 abc def`;
poo`404 abc
  def`;
poo`404 abc\
  def`;
```

In the first two examples, the raw strings and the strings arrays match up.
```text
strings (1) ['404 abc def']
raw (1) ['404 abc def']

strings (1) ['404 abc
  def']
raw (1) ['404 abc
  def']
```

In the 3rd sample however the generated strings and the raw strings are different.
```text
strings (1) ['404 abc  def']
raw (1) ['404 abc\\
  def']
```

I suspect there may be some code that may be mixing up the raw strings with the regular strings causing some of the escaping to be offset by a character. But that is just a theory 🤷.



Discussion:
Hi @aarondandy,

If you want your template string to be multiline, no escaping backslash is needed.

```js
await execa`npm run build
	--concurrency 2
	--fail-fast`;
```

Is the same as:

```js
await execa`npm run build --concurrency 2 --fail-fast`;
```

Or:

```js
await execa('npm', ['run', 'build', '--concurrency', '2', '--fail-fast'])`;
```

On the other hand, if you do want to include a newline in an argument, this can be achieved like this:

```js
await execa`npm run build {'with\nnewline'}`;
```

Which is the same as:

```js
await execa('npm', ['with\nnewline'])`;
```

This is documented [here](https://github.com/sindresorhus/execa/blob/main/docs/execution.md#multiple-lines) and [there](https://github.com/sindresorhus/execa/blob/main/docs/escaping.md).
That's pretty cool and I should probably switch to that. I think this still exposes an issue in `splitByWhitespaces` though as the parsed arguments are off by a character and this is a feature of javascript, even if it may be odd in context.
If you all decide that execa should handle javascript escaped newlines, I think a choice needs to be made about how the following would be interpreted:

```js
$`command abc\
def`;
```

If the arguments should be `abcdef` then I think the best way to handle this case is to skip over the newline in the raw string. If the arguments should be assumed to be `abc def` then the escaped newline should be a token boundary. After reading your comment above, I think the right way to delimit a token should be with a natural line break. Somebody (like me) doing weird stuff with escaped newlines probably has the intent that they want the newline ignored by execa. Because of that, I'm thinking the correct behavior when this javascript feature is encountered is to ignore the newline and parse it as `abcdef`, forcing users (like me) to make better choices and use unescaped newlines.

Regardless of which way fits this library best, I don't think `abc ef` should be a result.
Currently, the following:

```js
execa`command abc\
def`
```

Is the same as:

```js
execa('command', ['abcdef'])
```

This seems to be the right behavior for a few reasons. First, it mimics how non-tagged template strings behave:

```js
console.log(`abc\
def`) // 'abcdef'
```

Second, as you mention, users do not need to escape newlines with backslash, so there's no need to support it with an alternative syntax there.

Third, this is the current behavior, so any change there would be breaking.

Finally, it can be useful when one argument is a long contiguous string, e.g. a long base64 string (such as a token, PEM file contents, etc.)

---

Now, when it comes to the following (with spaces in the beginning on the second line):

```js
execa`command abc\
 def`
```

This currently behaves the same as:

```js
execa('command', ['abc ', 'ef'])
```

With non-tagged templates, this would behave as:

```js
console.log(`abc\
 def`) // 'abc def'
```

In other words, it behaves just like:

```js
console.log(`abc def`)
```

Therefore, any spaces in between `abc` and `def` is clearly not part of either argument, but only meant as a delimiter.

Also, since no one could intend for `d` to be removed, this fix can be considered non breaking.

So this is a bug which we should fix. Thanks for finding it. 

---

Thanks for the PR at #1176, this should solve this. :+1: 
Released in https://github.com/sindresorhus/execa/releases/tag/v9.5.2



Patch:
diff --git a/lib/methods/template.js b/lib/methods/template.js
index 4bc462159c..6b7745ea3a 100644
--- a/lib/methods/template.js
+++ b/lib/methods/template.js
@@ -75,7 +75,11 @@ const splitByWhitespaces = (template, rawTemplate) => {
 			templateStart = templateIndex + 1;
 		} else if (rawCharacter === '\\') {
 			const nextRawCharacter = rawTemplate[rawIndex + 1];
-			if (nextRawCharacter === 'u' && rawTemplate[rawIndex + 2] === '{') {
+			if (nextRawCharacter === '\n') {
+				// Handles escaped newlines in templates
+				templateIndex -= 1;
+				rawIndex += 1;
+			} else if (nextRawCharacter === 'u' && rawTemplate[rawIndex + 2] === '{') {
 				rawIndex = rawTemplate.indexOf('}', rawIndex + 3);
 			} else {
 				rawIndex += ESCAPE_LENGTH[nextRawCharacter] ?? 1;


Test patch:
diff --git a/test/methods/template.js b/test/methods/template.js
index c1efde9ca8..c67f536759 100644
--- a/test/methods/template.js
+++ b/test/methods/template.js
@@ -22,6 +22,14 @@ test('$ can use newlines and tab indentations', testScriptStdout, () => $`echo.j
 	bar`, 'foo\nbar');
 test('$ can use newlines and space indentations', testScriptStdout, () => $`echo.js foo
   bar`, 'foo\nbar');
+test('$ can use escaped newlines and space indentations', testScriptStdout, () => $`echo.js foo\
+  bar`, 'foo\nbar');
+test('$ can use escaped newlines and inline tab indentations', testScriptStdout, () => $`echo.js foo\
+	bar`, 'foo\nbar');
+test('$ can use escaped newlines and character escaped tab indentations', testScriptStdout, () => $`echo.js foo\
+\tbar`, 'foo\tbar');
+test('$ can use escaped newlines and character escaped newlines', testScriptStdout, () => $`echo.js foo\
+\n\nbar`, 'foo\n\nbar');
 test('$ can use Windows newlines and tab indentations', testScriptStdout, () => escapedCall('echo.js foo\r\n\tbar'), 'foo\nbar');
 test('$ can use Windows newlines and space indentations', testScriptStdout, () => escapedCall('echo.js foo\r\n  bar'), 'foo\nbar');
 test('$ does not ignore comments in expressions', testScriptStdout, () => $`echo.js foo