From ffc71edd54e98607e29c2b22e365e551c953d628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sat, 30 Nov 2019 23:52:03 -0400 Subject: [PATCH] Add Env::RunScript This is a thin wrapper around napi_run_script. Refs: https://github.com/nodejs/node/pull/15216 PR-URL: https://github.com/nodejs/node-addon-api/pull/616 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gus Caplan Reviewed-By: Kevin Eady Reviewed-By: Michael Dawson --- doc/env.md | 14 ++++++++++++ napi-inl.h | 16 ++++++++++++++ napi.h | 4 ++++ test/binding.cc | 2 ++ test/binding.gyp | 1 + test/index.js | 1 + test/run_script.cc | 55 ++++++++++++++++++++++++++++++++++++++++++++++ test/run_script.js | 46 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 139 insertions(+) create mode 100644 test/run_script.cc create mode 100644 test/run_script.js diff --git a/doc/env.md b/doc/env.md index 9bde741ca..70c641850 100644 --- a/doc/env.md +++ b/doc/env.md @@ -61,3 +61,17 @@ Napi::Error Napi::Env::GetAndClearPendingException(); ``` Returns an `Napi::Error` object representing the environment's pending exception, if any. + +### RunScript + +```cpp +Napi::Value Napi::Env::RunScript(____ script); +``` +- `[in] script`: A string containing JavaScript code to execute. + +Runs JavaScript code contained in a string and returns its result. + +The `script` can be any of the following types: +- [`Napi::String`](string.md) +- `const char *` +- `const std::string &` diff --git a/napi-inl.h b/napi-inl.h index 38f75d340..7bef465fc 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -319,6 +319,22 @@ inline Error Env::GetAndClearPendingException() { return Error(_env, value); } +inline Value Env::RunScript(const char* utf8script) { + String script = String::New(_env, utf8script); + return RunScript(script); +} + +inline Value Env::RunScript(const std::string& utf8script) { + return RunScript(utf8script.c_str()); +} + +inline Value Env::RunScript(String script) { + napi_value result; + napi_status status = napi_run_script(_env, script, &result); + NAPI_THROW_IF_FAILED(_env, status, Undefined()); + return Value(_env, result); +} + //////////////////////////////////////////////////////////////////////////////// // Value class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 3b5bb15df..c44b3ba92 100644 --- a/napi.h +++ b/napi.h @@ -178,6 +178,10 @@ namespace Napi { bool IsExceptionPending() const; Error GetAndClearPendingException(); + Value RunScript(const char* utf8script); + Value RunScript(const std::string& utf8script); + Value RunScript(String script); + private: napi_env _env; }; diff --git a/test/binding.cc b/test/binding.cc index 5c3cd6b24..ad4650819 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -39,6 +39,7 @@ Object InitObject(Env env); Object InitObjectDeprecated(Env env); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED Object InitPromise(Env env); +Object InitRunScript(Env env); #if (NAPI_VERSION > 3) Object InitThreadSafeFunctionCtx(Env env); Object InitThreadSafeFunctionExistingTsfn(Env env); @@ -92,6 +93,7 @@ Object Init(Env env, Object exports) { exports.Set("object_deprecated", InitObjectDeprecated(env)); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); + exports.Set("run_script", InitRunScript(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env)); exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env)); diff --git a/test/binding.gyp b/test/binding.gyp index ced1a6802..d2a1493f9 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -36,6 +36,7 @@ 'object/object.cc', 'object/set_property.cc', 'promise.cc', + 'run_script.cc', 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', diff --git a/test/index.js b/test/index.js index e05c25a65..4fffd1d84 100644 --- a/test/index.js +++ b/test/index.js @@ -39,6 +39,7 @@ let testModules = [ 'object/object_deprecated', 'object/set_property', 'promise', + 'run_script', 'threadsafe_function/threadsafe_function_ctx', 'threadsafe_function/threadsafe_function_existing_tsfn', 'threadsafe_function/threadsafe_function_ptr', diff --git a/test/run_script.cc b/test/run_script.cc new file mode 100644 index 000000000..af47ae1f7 --- /dev/null +++ b/test/run_script.cc @@ -0,0 +1,55 @@ +#include "napi.h" + +using namespace Napi; + +namespace { + +Value RunPlainString(const CallbackInfo& info) { + Env env = info.Env(); + return env.RunScript("1 + 2 + 3"); +} + +Value RunStdString(const CallbackInfo& info) { + Env env = info.Env(); + std::string str = "1 + 2 + 3"; + return env.RunScript(str); +} + +Value RunJsString(const CallbackInfo& info) { + Env env = info.Env(); + return env.RunScript(info[0].As()); +} + +Value RunWithContext(const CallbackInfo& info) { + Env env = info.Env(); + + Array keys = info[1].As().GetPropertyNames(); + std::string code = "("; + for (unsigned int i = 0; i < keys.Length(); i++) { + if (i != 0) code += ","; + code += keys.Get(i).As().Utf8Value(); + } + code += ") => " + info[0].As().Utf8Value(); + + Value ret = env.RunScript(code); + Function fn = ret.As(); + std::vector args; + for (unsigned int i = 0; i < keys.Length(); i++) { + Value key = keys.Get(i); + args.push_back(info[1].As().Get(key)); + } + return fn.Call(args); +} + +} // end anonymous namespace + +Object InitRunScript(Env env) { + Object exports = Object::New(env); + + exports["plainString"] = Function::New(env, RunPlainString); + exports["stdString"] = Function::New(env, RunStdString); + exports["jsString"] = Function::New(env, RunJsString); + exports["withContext"] = Function::New(env, RunWithContext); + + return exports; +} diff --git a/test/run_script.js b/test/run_script.js new file mode 100644 index 000000000..271eeb50c --- /dev/null +++ b/test/run_script.js @@ -0,0 +1,46 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const testUtil = require('./testUtil'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + testUtil.runGCTests([ + 'Plain C string', + () => { + const sum = binding.run_script.plainString(); + assert.strictEqual(sum, 1 + 2 + 3); + }, + + 'std::string', + () => { + const sum = binding.run_script.stdString(); + assert.strictEqual(sum, 1 + 2 + 3); + }, + + 'JavaScript string', + () => { + const sum = binding.run_script.jsString("1 + 2 + 3"); + assert.strictEqual(sum, 1 + 2 + 3); + }, + + 'JavaScript, but not a string', + () => { + assert.throws(() => { + binding.run_script.jsString(true); + }, { + name: 'Error', + message: 'A string was expected' + }); + }, + + 'With context', + () => { + const a = 1, b = 2, c = 3; + const sum = binding.run_script.withContext("a + b + c", { a, b, c }); + assert.strictEqual(sum, a + b + c); + } + ]); +}