From 2086d8c60a96cc48f125de6078190da9801543ba Mon Sep 17 00:00:00 2001 From: jorge guerrero Date: Mon, 23 Feb 2026 22:22:55 -0500 Subject: [PATCH 1/2] cli: allow unary-negated eval arguments Fixes: https://github.com/nodejs/node/issues/43397 Signed-off-by: jorge guerrero --- src/node_options-inl.h | 31 ++++++++++++++++++++++++++++++- test/parallel/test-cli-eval.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/node_options-inl.h b/src/node_options-inl.h index 877e8ce4ded92b..c0a4eebd512631 100644 --- a/src/node_options-inl.h +++ b/src/node_options-inl.h @@ -467,7 +467,36 @@ void OptionsParser::Parse( value = args.pop_first(); - if (!value.empty() && value[0] == '-') { + bool is_eval_expression = false; + if (name == "--eval" && !value.empty() && value[0] == '-') { + std::string eval_value_name = value; + const size_t equals_index = value.find('='); + if (equals_index != std::string::npos) { + eval_value_name = value.substr(0, equals_index); + } + + bool is_option_name = + options_.contains(eval_value_name) || + aliases_.contains(eval_value_name) || + aliases_.contains(eval_value_name + "=") || + aliases_.contains(eval_value_name + " "); + + if (!is_option_name && eval_value_name.starts_with("--no-")) { + const std::string non_negated_name = + "--" + eval_value_name.substr(5); + is_option_name = + options_.contains(non_negated_name) || + aliases_.contains(non_negated_name) || + aliases_.contains(non_negated_name + "=") || + aliases_.contains(non_negated_name + " "); + } + + is_eval_expression = !is_option_name; + } + + if (!value.empty() && + value[0] == '-' && + !is_eval_expression) { missing_argument(); break; } else { diff --git a/test/parallel/test-cli-eval.js b/test/parallel/test-cli-eval.js index 8d765f10fbcbdb..082a098607df23 100644 --- a/test/parallel/test-cli-eval.js +++ b/test/parallel/test-cli-eval.js @@ -115,6 +115,39 @@ child.exec(...common.escapePOSIXShell`"${process.execPath}" -p "\\-42"`, common. assert.strictEqual(stderr, ''); })); +// Unary-negative eval expressions should not be rejected as missing arguments. +child.exec(...common.escapePOSIXShell`"${process.execPath}" -pe -42`, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout, '-42\n'); + assert.strictEqual(stderr, ''); +})); + +// Edge case: negative zero should preserve its sign when printed. +child.exec(...common.escapePOSIXShell`"${process.execPath}" -pe -0`, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout, '-0\n'); + assert.strictEqual(stderr, ''); +})); + +// Expressions like -NaN should be treated as eval input, not options. +child.exec(...common.escapePOSIXShell`"${process.execPath}" -pe -NaN`, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout, 'NaN\n'); + assert.strictEqual(stderr, ''); +})); + +// A bare '-' should be passed to eval and fail with a syntax error. +child.exec(...common.escapePOSIXShell`"${process.execPath}" -pe -`, common.mustCall((err, stdout, stderr) => { + assert.notStrictEqual(err.code, 9); + assert.strictEqual(stdout, ''); + assert.match(stderr, /SyntaxError/); +})); + +// Nearby-path safety: option-looking values should still be rejected. +child.exec(...common.escapePOSIXShell`"${process.execPath}" -e -p`, common.mustCall((err, stdout, stderr) => { + assert.strictEqual(err.code, 9); + assert.strictEqual(stdout, ''); + assert.strictEqual(stderr.trim(), + `${process.execPath}: -e requires an argument`); +})); + // Long output should not be truncated. child.exec(...common.escapePOSIXShell`"${process.execPath}" -p "'1'.repeat(1e5)"`, common.mustSucceed((stdout, stderr) => { assert.strictEqual(stdout, `${'1'.repeat(1e5)}\n`); From ad880aeb676483d427087b130c42d5ed144f72ac Mon Sep 17 00:00:00 2001 From: jorge guerrero Date: Tue, 24 Feb 2026 16:38:49 -0500 Subject: [PATCH 2/2] doc,test: clarify --eval usage for leading '-' scripts Refs: https://github.com/nodejs/node/issues/43397 Signed-off-by: jorge guerrero --- doc/api/cli.md | 3 +++ src/node_options-inl.h | 31 +------------------------------ test/parallel/test-cli-eval.js | 21 ++++----------------- 3 files changed, 8 insertions(+), 47 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 4d8e806723866d..f8e4922b30656f 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -997,6 +997,9 @@ changes: Evaluate the following argument as JavaScript. The modules which are predefined in the REPL can also be used in `script`. +If `script` starts with `-`, pass it using `=` (for example, +`node --print --eval=-42`) so it is parsed as the value of `--eval`. + On Windows, using `cmd.exe` a single quote will not work correctly because it only recognizes double `"` for quoting. In Powershell or Git bash, both `'` and `"` are usable. diff --git a/src/node_options-inl.h b/src/node_options-inl.h index c0a4eebd512631..877e8ce4ded92b 100644 --- a/src/node_options-inl.h +++ b/src/node_options-inl.h @@ -467,36 +467,7 @@ void OptionsParser::Parse( value = args.pop_first(); - bool is_eval_expression = false; - if (name == "--eval" && !value.empty() && value[0] == '-') { - std::string eval_value_name = value; - const size_t equals_index = value.find('='); - if (equals_index != std::string::npos) { - eval_value_name = value.substr(0, equals_index); - } - - bool is_option_name = - options_.contains(eval_value_name) || - aliases_.contains(eval_value_name) || - aliases_.contains(eval_value_name + "=") || - aliases_.contains(eval_value_name + " "); - - if (!is_option_name && eval_value_name.starts_with("--no-")) { - const std::string non_negated_name = - "--" + eval_value_name.substr(5); - is_option_name = - options_.contains(non_negated_name) || - aliases_.contains(non_negated_name) || - aliases_.contains(non_negated_name + "=") || - aliases_.contains(non_negated_name + " "); - } - - is_eval_expression = !is_option_name; - } - - if (!value.empty() && - value[0] == '-' && - !is_eval_expression) { + if (!value.empty() && value[0] == '-') { missing_argument(); break; } else { diff --git a/test/parallel/test-cli-eval.js b/test/parallel/test-cli-eval.js index 082a098607df23..414344d07cf185 100644 --- a/test/parallel/test-cli-eval.js +++ b/test/parallel/test-cli-eval.js @@ -115,32 +115,19 @@ child.exec(...common.escapePOSIXShell`"${process.execPath}" -p "\\-42"`, common. assert.strictEqual(stderr, ''); })); -// Unary-negative eval expressions should not be rejected as missing arguments. -child.exec(...common.escapePOSIXShell`"${process.execPath}" -pe -42`, common.mustSucceed((stdout, stderr) => { +// Eval expressions that start with '-' can be passed with '='. +child.exec(...common.escapePOSIXShell`"${process.execPath}" --print --eval=-42`, common.mustSucceed((stdout, stderr) => { assert.strictEqual(stdout, '-42\n'); assert.strictEqual(stderr, ''); })); // Edge case: negative zero should preserve its sign when printed. -child.exec(...common.escapePOSIXShell`"${process.execPath}" -pe -0`, common.mustSucceed((stdout, stderr) => { +child.exec(...common.escapePOSIXShell`"${process.execPath}" --print --eval=-0`, common.mustSucceed((stdout, stderr) => { assert.strictEqual(stdout, '-0\n'); assert.strictEqual(stderr, ''); })); -// Expressions like -NaN should be treated as eval input, not options. -child.exec(...common.escapePOSIXShell`"${process.execPath}" -pe -NaN`, common.mustSucceed((stdout, stderr) => { - assert.strictEqual(stdout, 'NaN\n'); - assert.strictEqual(stderr, ''); -})); - -// A bare '-' should be passed to eval and fail with a syntax error. -child.exec(...common.escapePOSIXShell`"${process.execPath}" -pe -`, common.mustCall((err, stdout, stderr) => { - assert.notStrictEqual(err.code, 9); - assert.strictEqual(stdout, ''); - assert.match(stderr, /SyntaxError/); -})); - -// Nearby-path safety: option-looking values should still be rejected. +// Nearby-path safety: option-looking values should still be rejected with -e. child.exec(...common.escapePOSIXShell`"${process.execPath}" -e -p`, common.mustCall((err, stdout, stderr) => { assert.strictEqual(err.code, 9); assert.strictEqual(stdout, '');