diff --git a/scripts/lib/shell-split.js b/scripts/lib/shell-split.js index 39171b149..0a00758cf 100644 --- a/scripts/lib/shell-split.js +++ b/scripts/lib/shell-split.js @@ -1,7 +1,8 @@ 'use strict'; // A here-document opener at `index`, but not a here-string (<<<): `<<` or `<<-`, then the -// delimiter, a shell word whose quotes are removed (`<<'EOF'`, `<]/.test(ch)) { @@ -25,7 +28,7 @@ function heredocAt(command, index) { delimiter += ch; } } - return delimiter && !quote ? { text: command.slice(index, i), delimiter, stripTabs } : null; + return (delimiter || quoted) && !quote ? { text: command.slice(index, i), delimiter, stripTabs } : null; } // The index after the `))` that closes the arithmetic `((` at `index`, or -1. @@ -64,27 +67,19 @@ function endOfHeredocBodies(command, start, heredocs) { return Math.min(end, command.length); } -// At the newline at `index` the command ends, after the bodies of the here-documents opened on -// its line. The bodies are data, so they go with the command that opened the first of them, even -// when an operator after it (`cat < 0 && heredocs[0].segment < segments.length) { - segments[heredocs[0].segment] += bodies.trimEnd(); - bodies = ''; - } - const rest = (current + bodies).trim(); - if (rest) segments.push(rest); - return end; + if (current.trim()) segments.push(current.trim()); + return endOfHeredocBodies(command, index + 1, heredocs); } /** * Split a shell command into segments by operators (&&, ||, ;, &) and by * newlines, while respecting quoting (single/double) and escaped characters. * Redirection operators (&>, >&, 2>&1) are NOT treated as separators, and the - * body of a here-document stays in the segment of the command that reads it. + * body of a here-document, which is data, is left out of the segments. */ function splitShellSegments(command) { const segments = []; @@ -134,7 +129,7 @@ function splitShellSegments(command) { const heredoc = ch === '<' && next === '<' ? heredocAt(command, i) : null; if (heredoc) { - heredocs.push({ ...heredoc, segment: segments.length }); + heredocs.push(heredoc); current += heredoc.text; i += heredoc.text.length - 1; continue; diff --git a/tests/hooks/pre-bash-dev-server-block.test.js b/tests/hooks/pre-bash-dev-server-block.test.js index 739da2ad0..4db73f983 100644 --- a/tests/hooks/pre-bash-dev-server-block.test.js +++ b/tests/hooks/pre-bash-dev-server-block.test.js @@ -234,6 +234,11 @@ function runTests() { assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`); }) ? passed++ : failed++); + (test('allows a here-document body fed to a package manager command', () => { + const result = runScript('npm --version < { diff --git a/tests/lib/shell-split.test.js b/tests/lib/shell-split.test.js index bd4772305..6c99a00ba 100644 --- a/tests/lib/shell-split.test.js +++ b/tests/lib/shell-split.test.js @@ -120,16 +120,10 @@ test('an escaped or quoted newline does not split', () => { assert.deepStrictEqual(splitShellSegments('npm run \\\ndev'), ['npm run \\\ndev']); assert.deepStrictEqual(splitShellSegments('echo "a\nb"'), ['echo "a\nb"']); }); -test('a here-document body stays with its command', () => { - assert.deepStrictEqual( - splitShellSegments('cat < notes.md\na && b\nnpm run dev\nEOF\nls'), - ['cat < notes.md\na && b\nnpm run dev\nEOF', 'ls'] - ); - assert.deepStrictEqual( - splitShellSegments("cat <<-'END'\n\tnpm run dev\n\tEND\nls"), - ["cat <<-'END'\n\tnpm run dev\n\tEND", 'ls'] - ); - assert.deepStrictEqual(splitShellSegments('cat < { + assert.deepStrictEqual(splitShellSegments('cat < notes.md\na && b\nnpm run dev\nEOF\nls'), ['cat < notes.md', 'ls']); + assert.deepStrictEqual(splitShellSegments("cat <<-'END'\n\tnpm run dev\n\tEND\nls"), ["cat <<-'END'", 'ls']); + assert.deepStrictEqual(splitShellSegments('cat < { assert.deepStrictEqual(splitShellSegments('cat <<< x && y'), ['cat <<< x', 'y']); @@ -143,28 +137,18 @@ test('a << in a comment is not a here-document, and a comment is not split', () assert.deepStrictEqual(splitShellSegments('echo a#b ${#x} && c'), ['echo a#b ${#x}', 'c']); }); test('a here-document delimiter is a shell word, read without its quotes', () => { - assert.deepStrictEqual( - splitShellSegments('cat < { - assert.deepStrictEqual( - splitShellSegments('cat < { + assert.deepStrictEqual(splitShellSegments('cat < { - assert.deepStrictEqual( - splitShellSegments('cat <