Skip to content

Commit c2ce9b5

Browse files
committed
src: allow -- value form for option arguments
The CLI option parser rejected any value beginning with `-` when consuming the argument for a value-taking option such as `--eval`, including the conventional `--` end-of-options marker. As a result `node -pe -- -0` failed with `--eval requires an argument`, and there was no way to pass a leading-dash value (e.g. negative numeric literals) through `-e` / `-p` / `-pe`. Recognize a literal `--` as an end-of-options marker when reading the value for a value-taking option: pop the `--` and consume the following argv element verbatim. Bare `node -pe -0` continues to error so short-flag stacking and parsing of legitimate options after `-e` are preserved. Fixes: #43397 Signed-off-by: Maruthan G <[email protected]>
1 parent 21436f0 commit c2ce9b5

2 files changed

Lines changed: 110 additions & 7 deletions

File tree

src/node_options-inl.h

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -465,14 +465,28 @@ void OptionsParser<Options>::Parse(
465465
break;
466466
}
467467

468-
value = args.pop_first();
469-
470-
if (!value.empty() && value[0] == '-') {
471-
missing_argument();
472-
break;
468+
// Treat a literal `--` as an end-of-options marker for the value
469+
// of an option that takes an argument. This allows passing values
470+
// that themselves start with `-`, e.g. `node -e -- -0` or
471+
// `node --eval -- -42`, mirroring the convention that arguments
472+
// following `--` are positional.
473+
if (args.first() == "--") {
474+
args.pop_first();
475+
if (args.empty()) {
476+
missing_argument();
477+
break;
478+
}
479+
value = args.pop_first();
473480
} else {
474-
if (!value.empty() && value[0] == '\\' && value[1] == '-')
475-
value = value.substr(1); // Treat \- as escaping an -.
481+
value = args.pop_first();
482+
483+
if (!value.empty() && value[0] == '-') {
484+
missing_argument();
485+
break;
486+
} else {
487+
if (!value.empty() && value[0] == '\\' && value[1] == '-')
488+
value = value.substr(1); // Treat \- as escaping an -.
489+
}
476490
}
477491
}
478492
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict';
2+
3+
// Regression test for https://github.com/nodejs/node/issues/43397
4+
//
5+
// `node -pe '-0'` (and other expressions whose first character is `-`) used to
6+
// fail with "--eval requires an argument" because the option parser treated
7+
// the value as another flag. The supported way to pass such values is to
8+
// terminate the option list with `--`, e.g. `node -pe -- -0`.
9+
10+
require('../common');
11+
const assert = require('assert');
12+
const { spawnSync } = require('child_process');
13+
14+
function run(...args) {
15+
const result = spawnSync(process.execPath, args, { encoding: 'utf8' });
16+
return { stdout: result.stdout, stderr: result.stderr, status: result.status };
17+
}
18+
19+
// `--` should let the next argv entry be consumed verbatim as the value of
20+
// `--eval`, even when it starts with `-`.
21+
{
22+
const { stdout, stderr, status } = run('-pe', '--', '-0');
23+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
24+
assert.strictEqual(stderr, '');
25+
assert.strictEqual(stdout, '0\n');
26+
}
27+
28+
{
29+
const { stdout, stderr, status } = run('-pe', '--', '-1.5');
30+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
31+
assert.strictEqual(stderr, '');
32+
assert.strictEqual(stdout, '-1.5\n');
33+
}
34+
35+
{
36+
const { stdout, stderr, status } = run('-pe', '--', '-1+0');
37+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
38+
assert.strictEqual(stderr, '');
39+
assert.strictEqual(stdout, '-1\n');
40+
}
41+
42+
// `-e` (no print) should also accept a leading-dash value via `--`.
43+
{
44+
const { stdout, stderr, status } =
45+
run('-e', '--', '-42; console.log("ok")');
46+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
47+
assert.strictEqual(stderr, '');
48+
assert.strictEqual(stdout, 'ok\n');
49+
}
50+
51+
// The long-form `--eval` should behave the same way.
52+
{
53+
const { stdout, stderr, status } = run('--print', '--eval', '--', '-7');
54+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
55+
assert.strictEqual(stderr, '');
56+
assert.strictEqual(stdout, '-7\n');
57+
}
58+
59+
// `--eval=-42` already worked and must keep working.
60+
{
61+
const { stdout, stderr, status } = run('--print', '--eval=-42');
62+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
63+
assert.strictEqual(stderr, '');
64+
assert.strictEqual(stdout, '-42\n');
65+
}
66+
67+
// The pre-existing `\-` escape must keep working.
68+
{
69+
const { stdout, stderr, status } = run('-pe', '\\-0');
70+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
71+
assert.strictEqual(stderr, '');
72+
assert.strictEqual(stdout, '0\n');
73+
}
74+
75+
// Without `--`, a leading-dash value still produces the existing diagnostic;
76+
// that behavior is intentional so unrelated stacked flags keep being detected.
77+
{
78+
const { stderr, status } = run('-pe', '-0');
79+
assert.notStrictEqual(status, 0);
80+
assert.match(stderr, /requires an argument/);
81+
}
82+
83+
// Sanity: a positional value with no leading dash works without `--`.
84+
{
85+
const { stdout, stderr, status } = run('-pe', '42');
86+
assert.strictEqual(status, 0, `stderr: ${stderr}`);
87+
assert.strictEqual(stderr, '');
88+
assert.strictEqual(stdout, '42\n');
89+
}

0 commit comments

Comments
 (0)