diff --git a/api/src/util/cmd.ts b/api/src/util/cmd.ts index cd530e0..c2569dd 100644 --- a/api/src/util/cmd.ts +++ b/api/src/util/cmd.ts @@ -1,4 +1,4 @@ -import { option } from "../index.js"; +import * as option from "../option/index.js"; import { ClangID } from "../option/clang.js"; /** diff --git a/doc/api.md b/doc/api.md index ee50c38..1333ed7 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1,158 +1 @@ -```typescript -// ------------------------------------------------------------ -// Internal API, not for plugin developers -// ------------------------------------------------------------ -const ActionKind = ['skip', 'drop', 'abort', 'modify'] as const; - -/** - * @type skip - skip this command, but execute the original command - * @type drop - drop this command, do not execute the original command - * @type abort - abort the whole execution, and return an error - * @type modify - modify this command, and execute the modified command - */ -type ActionType = (typeof ActionKind)[number]; - -type Action = { - // for modify - data?: CommandData; - type: ActionType; -}; - -const EventKind = ['finish', 'output'] as const; -type EventType = (typeof EventKind)[number]; - -type ExecutionEvent = { - // for output - stdout?: string; - stderr?: string; - code: number; - type: EventType; -}; - -type CatterRuntime = { - supportActions: ActionType[]; - supportEvents: EventType[]; - // eslogger: only in mac - // env: eg. CC=catter-proxy, then proxy report this cmd - type: 'inject' | 'eslogger' | 'env'; - supportParentId: boolean; -}; - -/** - * @field scriptArgs - the arguments of this script - * @field scriptPath - the path of this script - * @field buildSystemCommand - the command to execute this script in build system, eg. ['bazel', 'build', '//:target'] - * @field isScriptSupported - defaults to true, if false, catter will instantly abort the execution and return an error. - * @field runtime - the runtime environment of this script, can be used to determine which actions and events are supported - * @field options - the options of catter, can be used to enable some features of catter, eg. log - */ -type CatterConfig = { - scriptPath: string; - scriptArgs: string[]; - buildSystemCommand: string[]; - runtime: CatterRuntime; - options: { - log: boolean; - }; - isScriptSupported: boolean; -}; - -type CatterErr = { - //... -}; - -/** - * @field parent - When supportParentId is true at runtime, this field is the ID of the parent command that generated this command; otherwise, this field is undefined. - * @field env - the environment variables of this command, in the format of ["KEY=VALUE", ...] - */ -type CommandData = { - cwd: string; - exe: string; - argv: string[]; - env: string[]; - runtime: CatterRuntime; - parent?: number; -}; - -export function service_on_start(cb: (config: CatterConfig) => CatterConfig): void; -export function service_on_finish(cb: () => void): void; -export function service_on_command(cb: (id: number, data: CommandData | CatterErr) => Action): void; -export function service_on_execution(cb: (id: number, event: ExecutionEvent) => void): void; - - -// ------------------------------------------------------------ -// Plugin API, for plugin developers -// ------------------------------------------------------------ - -import { service_on_start, service_on_finish, service_on_command, service_on_execution } from 'catter-c'; - -/** - * @method onStart - called when catter start, can modify config - * @method onFinish - called when catter finish - * @method onCommand - called when a command being captured - * @param onCommand.id - a unique identifier for this command, can be used to correlate with onExecution - * @param onCommand.data - the data of this command, if there is an error during capturing, this will be a CatterErr object - * @method onExecution - called when a command being executed, can listen on its output and finish event. - * @param onExecution.id - the unique identifier for this command, same as onCommand - * @param onExecution.event - the event of this command, if there is an error during execution, the code field will be non-zero and stdout/stderr may be undefined - */ -interface CatterService { - onStart: (config: CatterConfig) => CatterConfig; - onFinish: () => void; - onCommand: (id: number, data: CommandData | CatterErr) => Action; - onExecution: (id: number, event: ExecutionEvent) => void; -} - -export function onStart(cb: (config: CatterConfig) => CatterConfig): void{ - service_on_start(cb); -} -export function onFinish(cb: () => void): void { - service_on_finish(cb); -} -export function onCommand(cb: (id: number, data: CommandData | CatterErr) => Action): void { - service_on_command(cb); -} -export function onExecution(cb: (id: number, event: ExecutionEvent) => void): void { - service_on_execution(cb); -} - - -export function register(service: CatterService) { - onStart(service.onStart); - onFinish(service.onFinish); - onCommand(service.onCommand); - onExecution(service.onExecution); -} - -// ------------------------------------------------------------ -// Example -// ------------------------------------------------------------ -class MyCatterPlugin implements CatterService { - dataMap: Map = new Map(); - eventMap: Map = new Map(); - onStart(config: CatterConfig): CatterConfig { - // modify config - return config; - } - - onFinish(): void { - for (const id of this.dataMap.keys()) { - const data = this.dataMap.get(id); - const event = this.eventMap.get(id); - console.log(`Command ${id} data:`, data); - console.log(`Command ${id} event:`, event); - } - } - - onCommand(id: number, data: CommandData | CatterErr): Action { - this.dataMap.set(id, data); - return { type: 'skip' }; - } - - onExecution(id: number, event: ExecutionEvent): void { - this.eventMap.set(id, event); - } -} - -register(new MyCatterPlugin()); -``` +TODO diff --git a/src/catter/core/apitool.h b/src/catter/core/apitool.h index de3fa22..85fa287 100644 --- a/src/catter/core/apitool.h +++ b/src/catter/core/apitool.h @@ -80,20 +80,20 @@ static R invoke_with_log(const std::string& args_s, CallArgs&&... call_args) { try { if constexpr(std::is_void_v) { V(std::forward(call_args)...); - LOG_INFO("Invoke C API [{}]:\n -> args = {}\n -> ret = ", + LOG_INFO("Invoke C API `{}`:\n -> args = {}\n -> ret = ", capi_name(), args_s); return; } else { auto ret = V(std::forward(call_args)...); - LOG_INFO("Invoke C API [{}]:\n -> args = {}\n -> ret = {}", + LOG_INFO("Invoke C API `{}`:\n -> args = {}\n -> ret = {}", capi_name(), args_s, serialize_value(ret)); return ret; } } catch(const std::exception& e) { - LOG_INFO("Invoke C API [{}]:\n -> args = {}\n -> throw = {}", + LOG_INFO("Invoke C API `{}`:\n -> args = {}\n -> throw = {}", capi_name(), args_s, e.what()); diff --git a/src/catter/core/capi/type.h b/src/catter/core/capi/type.h index 400d8f1..789e874 100644 --- a/src/catter/core/capi/type.h +++ b/src/catter/core/capi/type.h @@ -127,7 +127,7 @@ T read_property(const qjs::Object& object, std::string_view property_name) { if constexpr(is_optional_v) { using value_type = typename is_optional::value_type; auto optional_value = object.get_optional_property(std::string(property_name)); - if(!optional_value.has_value() || optional_value->is_nothing()) { + if(!optional_value.has_value()) { return std::nullopt; } return from_property_value(*optional_value); diff --git a/src/catter/core/js.cc b/src/catter/core/js.cc index 04345dd..e51f2ad 100644 --- a/src/catter/core/js.cc +++ b/src/catter/core/js.cc @@ -33,65 +33,6 @@ struct Self { namespace { Self self{}; - -std::optional property_to_string(JSContext* ctx, JSValueConst value, const char* key) { - JSValue prop = JS_GetPropertyStr(ctx, value, key); - if(JS_IsException(prop)) { - JS_FreeValue(ctx, JS_GetException(ctx)); - return std::nullopt; - } - - std::optional result = std::nullopt; - if(!JS_IsUndefined(prop) && !JS_IsNull(prop)) { - if(const char* str = JS_ToCString(ctx, prop)) { - result = str; - JS_FreeCString(ctx, str); - } else if(JS_HasException(ctx)) { - JS_FreeValue(ctx, JS_GetException(ctx)); - } - } - - JS_FreeValue(ctx, prop); - return result; -} - -std::string stringify_value(JSContext* ctx, JSValueConst value) { - if(const char* str = JS_ToCString(ctx, value)) { - std::string result = str; - JS_FreeCString(ctx, str); - return result; - } - - if(JS_HasException(ctx)) { - JS_FreeValue(ctx, JS_GetException(ctx)); - } - return ""; -} - -std::string format_rejection_reason(const qjs::Value& value) { - auto* ctx = value.context(); - auto message = property_to_string(ctx, value.value(), "message"); - auto stack = property_to_string(ctx, value.value(), "stack"); - - if(message.has_value() || stack.has_value()) { - std::string result; - if(message.has_value()) { - result += std::format("Error Message: {}\n", *message); - } - if(stack.has_value()) { - result += std::format("Stack Trace:\n{}\n", *stack); - } - return result; - } - - return std::format("{}\n", stringify_value(ctx, value.value())); -} - -void append_rejection_trace(std::string& error_trace, const qjs::Parameters& args) { - for(const auto& arg: args) { - error_trace += format_rejection_reason(arg); - } -} } // namespace CatterConfig on_start(CatterConfig config) { @@ -164,7 +105,9 @@ void sync_eval(std::string_view input, const char* filename, int eval_flags) { auto reject = CallBack::from(js_ctx, [&](qjs::Parameters args) { state = Rejected; - append_rejection_trace(error_strace, args); + for(auto& arg: args) { + error_strace += arg.as().format() + "\n"; + } }); auto then_promise = promise_obj["then"].as().invoke(promise_obj, @@ -173,7 +116,13 @@ void sync_eval(std::string_view input, const char* filename, int eval_flags) { auto catch_fn = CallBack::from(js_ctx, [&](qjs::Parameters args) { state = Rejected; - append_rejection_trace(error_strace, args); + try { + for(auto& arg: args) { + error_strace += arg.as().format() + "\n"; + } + } catch(const std::exception& e) { + error_strace += std::format("Exception: {}\n", e.what()); + } }); then_promise["catch"].as().invoke(then_promise, qjs::Object::from(catch_fn)); diff --git a/src/catter/core/qjs.h b/src/catter/core/qjs.h index e56a750..457fb17 100644 --- a/src/catter/core/qjs.h +++ b/src/catter/core/qjs.h @@ -59,33 +59,6 @@ struct value_trans; template struct object_trans; - -inline std::string dump(JSContext* ctx) { - JSValue exception_val = JS_GetException(ctx); - - // Get the error name - JSValue name = JS_GetPropertyStr(ctx, exception_val, "name"); - const char* error_name = JS_ToCString(ctx, name); - - // Get the stack trace - JSValue stack = JS_GetPropertyStr(ctx, exception_val, "stack"); - const char* stack_str = JS_ToCString(ctx, stack); - - // Get the message - const char* msg = JS_ToCString(ctx, exception_val); - std::string result = std::format("Error Name: {}\nMessage: {}\nStack Trace:\n{}", - error_name ? error_name : "Unknown", - msg ? msg : "No message", - stack_str ? stack_str : "No stack trace"); - - JS_FreeCString(ctx, error_name); - JS_FreeCString(ctx, stack_str); - JS_FreeCString(ctx, msg); - JS_FreeValue(ctx, name); - JS_FreeValue(ctx, stack); - JS_FreeValue(ctx, exception_val); - return result; -} } // namespace detail /** @@ -95,8 +68,13 @@ inline std::string dump(JSContext* ctx) { */ class Exception : public std::exception { public: - Exception(const std::string& details) : - details(std::format("{}\n{}", details, cpptrace::generate_trace().to_string())) {} + Exception(const std::string& details) : details(details) {} + + Exception(std::string&& details) : details(std::move(details)) {} + + template + Exception(std::format_string fmt, Args&&... args) : + Exception(std::format(fmt, std::forward(args)...)) {} const char* what() const noexcept override { return details.c_str(); @@ -106,9 +84,17 @@ class Exception : public std::exception { std::string details; }; -class TypeError : public Exception { +class TypeException : public Exception { +public: + TypeException(const std::string& details) : Exception(std::format("TypeError: {}", details)) {} +}; + +class Error; + +class JSException : public Exception { public: - TypeError(const std::string& details) : Exception(std::format("TypeError: {}", details)) {} + inline JSException(const Error& error); + inline static JSException dump(JSContext* ctx); }; /** @@ -319,7 +305,7 @@ class Object : protected Value { auto ret = Value{this->context(), JS_GetPropertyStr(this->context(), this->value(), prop_name.c_str())}; if(ret.is_exception()) { - throw qjs::Exception(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } return ret; } @@ -341,15 +327,15 @@ class Object : protected Value { * @return std::optional */ std::optional get_optional_property(const std::string& prop_name) const noexcept { - auto ret = Value{this->context(), - JS_GetPropertyStr(this->context(), this->value(), prop_name.c_str())}; - if(ret.is_exception()) { - detail::dump(this->context()); - return std::nullopt; - } else if(ret.is_undefined()) { + try { + if(auto ret = get_property(prop_name); ret.is_undefined()) { + return std::nullopt; + } else { + return ret; + } + } catch(const qjs::Exception&) { return std::nullopt; } - return ret; } /** @@ -367,7 +353,7 @@ class Object : protected Value { JSValue js_val = JS_DupValue(this->context(), val); int ret = JS_SetPropertyStr(this->context(), this->value(), prop_name.c_str(), js_val); if(ret < 0) { - throw qjs::Exception(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } } else if constexpr(requires { { val.value() } -> std::convertible_to; @@ -375,7 +361,7 @@ class Object : protected Value { JSValue js_val = JS_DupValue(this->context(), val.value()); int ret = JS_SetPropertyStr(this->context(), this->value(), prop_name.c_str(), js_val); if(ret < 0) { - throw qjs::Exception(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } } else { auto js_val = Value::from>(this->context(), std::forward(val)); @@ -384,7 +370,7 @@ class Object : protected Value { prop_name.c_str(), js_val.release()); if(ret < 0) { - throw qjs::Exception(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } } } @@ -440,6 +426,51 @@ class Object : protected Value { } }; +class Error : protected Object { +public: + using Object::Object; + using Object::is_valid; + using Object::value; + using Object::context; + using Object::operator bool; + using Object::release; + + Error(JSContext* ctx, const JSValue& val) : Object(ctx, val) {} + + Error(JSContext* ctx, JSValue&& val) : Object(ctx, std::move(val)) {} + + Error(const Error&) = default; + Error(Error&& other) = default; + Error& operator= (const Error&) = default; + Error& operator= (Error&& other) = default; + ~Error() = default; + + std::string message() const { + return this->get_property("message").as(); + } + + std::string stack() const { + return this->get_property("stack").as(); + } + + std::string name() const { + return this->get_property("name").as(); + } + + std::string format() const { + return std::format("{}: {}\nStack Trace:\n{}", + this->name(), + this->message(), + this->stack()); + } +}; + +inline JSException::JSException(const Error& error) : Exception(error.format()) {} + +inline JSException JSException::dump(JSContext* ctx) { + return JSException(Error(ctx, JS_GetException(ctx))); +} + /** * @brief A typed wrapper for JavaScript functions. * This class allows calling JavaScript functions from C++ and creating C++ callbacks that can be @@ -592,7 +623,7 @@ class Function : protected Object { } if(value.is_exception()) { - throw qjs::Exception(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } if constexpr(std::is_void_v) { @@ -788,7 +819,7 @@ class Function : protected Object { for(const auto& arg: args) { if(!arg.is_valid()) { - throw TypeError("Function argument contains an invalid value"); + throw TypeException("Function argument contains an invalid value"); } argv.push_back(JS_DupValue(this->context(), arg.value())); } @@ -801,7 +832,7 @@ class Function : protected Object { } if(value.is_exception()) { - throw qjs::Exception(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } if constexpr(std::is_void_v) { @@ -902,7 +933,7 @@ class Array : protected Object { uint32_t length() const { qjs::Value len_val = this->get_property("length"); if(len_val.is_exception()) { - throw qjs::Exception(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } return len_val.as(); } @@ -911,7 +942,7 @@ class Array : protected Object { auto val = catter::qjs::Value{this->context(), JS_GetPropertyUint32(this->context(), this->value(), index)}; if(val.is_exception()) { - throw qjs::TypeError(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } return val.as(); } @@ -919,7 +950,7 @@ class Array : protected Object { std::optional get(uint32_t index) const noexcept { try { return this->operator[] (index); - } catch(const qjs::TypeError&) { + } catch(const qjs::TypeException&) { return std::nullopt; } } @@ -930,7 +961,7 @@ class Array : protected Object { uint32_t len = this->length(); auto res = JS_SetPropertyUint32(this->context(), this->value(), len, js_val.release()); if(res < 0) { - throw qjs::TypeError(detail::dump(this->context())); + throw qjs::JSException::dump(this->context()); } } @@ -970,7 +1001,7 @@ class Array : protected Object { result.push_back(arr[i]); } return result; - } catch(const qjs::TypeError&) { + } catch(const qjs::TypeException&) { return std::nullopt; } } @@ -1000,7 +1031,7 @@ struct value_trans { static bool as(const Value& val) { if(!JS_IsBool(val.value())) { - throw TypeError("Value is not a boolean"); + throw TypeException("Value is not a boolean"); } return JS_ToBool(val.context(), val.value()); } @@ -1008,7 +1039,7 @@ struct value_trans { static std::optional to(const Value& val) noexcept { try { return as(val); - } catch(const TypeError&) { + } catch(const TypeException&) { return std::nullopt; } } @@ -1029,19 +1060,19 @@ struct value_trans { static Num as(const Value& val) { if(!JS_IsNumber(val.value())) { - throw TypeError("Value is not a number"); + throw TypeException("Value is not a number"); } if constexpr(std::is_unsigned_v) { if constexpr(sizeof(Num) <= sizeof(uint32_t)) { uint32_t temp; if(JS_ToUint32(val.context(), &temp, val.value()) < 0) { - throw TypeError("Failed to convert value to uint32_t"); + throw TypeException("Failed to convert value to uint32_t"); } return static_cast(temp); } else { uint64_t temp; if(JS_ToIndex(val.context(), &temp, val.value()) < 0) { - throw TypeError("Failed to convert value to uint32_t"); + throw TypeException("Failed to convert value to uint32_t"); } return static_cast(temp); } @@ -1049,13 +1080,13 @@ struct value_trans { if constexpr(sizeof(Num) <= sizeof(int32_t)) { int32_t temp; if(JS_ToInt32(val.context(), &temp, val.value()) < 0) { - throw TypeError("Failed to convert value to uint32_t"); + throw TypeException("Failed to convert value to uint32_t"); } return static_cast(temp); } else { int64_t temp; if(JS_ToInt64(val.context(), &temp, val.value()) < 0) { - throw TypeError("Failed to convert value to int64_t"); + throw TypeException("Failed to convert value to int64_t"); } return static_cast(temp); } @@ -1068,7 +1099,7 @@ struct value_trans { static std::optional to(const Value& val) noexcept { try { return as(val); - } catch(const TypeError&) { + } catch(const TypeException&) { return std::nullopt; } } @@ -1082,12 +1113,12 @@ struct value_trans { static std::string as(const Value& val) { if(!JS_IsString(val.value())) { - throw TypeError("Value is not a string"); + throw TypeException("Value is not a string"); } size_t len; const char* str = JS_ToCStringLen(val.context(), &len, val.value()); if(str == nullptr) { - throw TypeError("Failed to convert value to string"); + throw TypeException("Failed to convert value to string"); } std::string result{str, len}; JS_FreeCString(val.context(), str); @@ -1097,7 +1128,7 @@ struct value_trans { static std::optional to(const Value& val) noexcept { try { return as(val); - } catch(const TypeError&) { + } catch(const TypeException&) { return std::nullopt; } } @@ -1116,7 +1147,7 @@ struct value_trans { static Object as(const Value& val) { if(!JS_IsObject(val.value())) { - throw TypeError("Value is not an object"); + throw TypeException("Value is not an object"); } return Object{val.context(), val.value()}; } @@ -1124,7 +1155,31 @@ struct value_trans { static std::optional to(const Value& val) noexcept { try { return as(val); - } catch(const TypeError&) { + } catch(const TypeException&) { + return std::nullopt; + } + } +}; + +template <> +struct value_trans { + static Value from(const Error& value) noexcept { + return Value{value.context(), value.value()}; + } + + static Value from(Error&& value) noexcept { + auto ctx = value.context(); + return Value{ctx, value.release()}; + } + + static Error as(const Value& val) { + return val.as().as(); + } + + static std::optional to(const Value& val) noexcept { + try { + return as(val); + } catch(const TypeException&) { return std::nullopt; } } @@ -1148,7 +1203,7 @@ struct value_trans> { static std::optional> to(const Value& val) noexcept { try { return as(val); - } catch(const TypeError&) { + } catch(const TypeException&) { return std::nullopt; } } @@ -1174,7 +1229,34 @@ struct value_trans> { static std::optional to(const Value& val) noexcept { try { return as(val); - } catch(const TypeError&) { + } catch(const TypeException&) { + return std::nullopt; + } + } +}; + +template <> +struct object_trans { + static Object from(const Error& value) noexcept { + return Object{value.context(), value.value()}; + } + + static Object from(Error&& value) noexcept { + auto ctx = value.context(); + return Object{ctx, value.release()}; + } + + static Error as(const Object& obj) { + if(!JS_IsError(obj.value())) { + throw TypeException("Object is not an error"); + } + return Error{obj.context(), obj.value()}; + } + + static std::optional to(const Object& obj) noexcept { + try { + return as(obj); + } catch(const TypeException&) { return std::nullopt; } } @@ -1195,7 +1277,7 @@ struct object_trans> { static ArrTy as(const Object& obj) { if(!JS_IsArray(obj.value())) { - throw TypeError("Object is not an array"); + throw TypeException("Object is not an array"); } return ArrTy{obj.context(), obj.value()}; } @@ -1203,7 +1285,7 @@ struct object_trans> { static std::optional to(const Object& obj) noexcept { try { return as(obj); - } catch(const TypeError&) { + } catch(const TypeException&) { return std::nullopt; } } @@ -1224,11 +1306,11 @@ struct object_trans> { static FuncType as(const Object& obj) { if(!JS_IsFunction(obj.context(), obj.value())) { - throw TypeError("Object is not a function"); + throw TypeException("Object is not a function"); } if(obj.get_property("length").as() != sizeof...(Args)) { - throw TypeError("Function has incorrect number of arguments"); + throw TypeException("Function has incorrect number of arguments"); } return FuncType{obj.context(), obj.value()}; @@ -1237,7 +1319,7 @@ struct object_trans> { static std::optional to(const Object& val) noexcept { try { return as(val); - } catch(const TypeError&) { + } catch(const TypeException&) { return std::nullopt; } } @@ -1258,7 +1340,7 @@ struct object_trans> { static FuncType as(const Object& obj) { if(!JS_IsFunction(obj.context(), obj.value())) { - throw TypeError("Object is not a function"); + throw TypeException("Object is not a function"); } return FuncType{obj.context(), obj.value()}; @@ -1267,7 +1349,7 @@ struct object_trans> { static std::optional to(const Object& val) noexcept { try { return as(val); - } catch(const TypeError&) { + } catch(const TypeException&) { return std::nullopt; } } @@ -1298,8 +1380,7 @@ class CModule { Value{this->ctx, func.value()} }); if(JS_AddModuleExport(this->ctx, m, name.c_str()) < 0) { - throw std::runtime_error( - std::format("Failed to add export '{}' to module '{}'", name, this->name)); + throw qjs::Exception("Failed to add export '{}' to module '{}'", name, this->name); } return *this; } @@ -1310,8 +1391,7 @@ class CModule { Value{this->ctx, JS_NewCFunction(this->ctx, func, name.c_str(), argc)} }); if(JS_AddModuleExport(this->ctx, m, name.c_str()) < 0) { - throw std::runtime_error( - std::format("Failed to add export '{}' to module '{}'", name, this->name)); + throw qjs::Exception("Failed to add export '{}' to module '{}'", name, this->name); } return *this; } @@ -1382,7 +1462,7 @@ class Context { return 0; }); if(m == nullptr) { - throw std::runtime_error("Failed to create new C module"); + throw qjs::Exception("Failed to create new C module"); } return this->raw->modules.emplace(name, CModule(this->js_context(), m, name)) @@ -1395,7 +1475,7 @@ class Context { if(this->has_exception()) { JS_FreeValue(this->js_context(), val); - throw qjs::Exception(detail::dump(this->js_context())); + throw qjs::JSException::dump(this->js_context()); } return Value{this->js_context(), std::move(val)}; } @@ -1477,7 +1557,7 @@ class Runtime { static Runtime create() { auto js_rt = JS_NewRuntime(); if(!js_rt) { - throw std::runtime_error("Failed to create new JS runtime"); + throw qjs::Exception("Failed to create new JS runtime"); } return Runtime(js_rt); } @@ -1490,7 +1570,7 @@ class Runtime { } else { auto js_ctx = JS_NewContext(this->js_runtime()); if(!js_ctx) { - throw std::runtime_error("Failed to create new JS context"); + throw qjs::Exception("Failed to create new JS context"); } return this->raw->ctxs.emplace(name, Context(js_ctx)).first->second; } @@ -1546,7 +1626,7 @@ std::string stringify(T&& v) { auto val = v.value(); auto json_str_val = qjs::Value{ctx, JS_JSONStringify(ctx, val, JS_UNDEFINED, JS_UNDEFINED)}; if(json_str_val.is_exception()) { - throw qjs::Exception(detail::dump(ctx)); + throw qjs::JSException::dump(ctx); } const char* json_cstr = JS_ToCString(ctx, json_str_val.value()); @@ -1555,7 +1635,7 @@ std::string stringify(T&& v) { JS_FreeCString(ctx, json_cstr); return result; } - throw qjs::Exception("Failed to stringify value"); + throw qjs::TypeException("Failed to convert value to JSON string"); }; // namespace json inline qjs::Value parse(const std::string& json_str, const Context& ctx) { @@ -1565,7 +1645,7 @@ inline qjs::Value parse(const std::string& json_str, const Context& ctx) { JS_ParseJSON(ctx.js_context(), json_str.data(), json_str.size(), "")}; if(ret.is_exception()) { - throw qjs::Exception(detail::dump(ctx.js_context())); + throw qjs::JSException::dump(ctx.js_context()); } return ret; } diff --git a/tests/unit/catter/core/js.cc b/tests/unit/catter/core/js.cc index d11b0d1..bf3605b 100644 --- a/tests/unit/catter/core/js.cc +++ b/tests/unit/catter/core/js.cc @@ -197,9 +197,9 @@ TEST_SUITE(js_tests) { } catch(const catter::qjs::Exception& ex) { caught = true; std::string message = ex.what(); - EXPECT_TRUE(message.find("Error Message: async boom") != std::string::npos); - EXPECT_TRUE(message.find("Stack Trace:") != std::string::npos); - EXPECT_TRUE(message.find("reject.js") != std::string::npos); + EXPECT_TRUE(message.contains("async boom")); + EXPECT_TRUE(message.contains("Stack Trace:")); + EXPECT_TRUE(message.contains("reject.js")); } EXPECT_TRUE(caught); diff --git a/tests/unit/catter/core/qjs.cc b/tests/unit/catter/core/qjs.cc index bf2a735..4bf38d3 100644 --- a/tests/unit/catter/core/qjs.cc +++ b/tests/unit/catter/core/qjs.cc @@ -493,6 +493,41 @@ TEST_SUITE(qjs_tests) { "Value is not a number")); }; + TEST_CASE(error_and_json_helpers_cover_metadata_stringify_and_invalid_variadic_args) { + auto f = [&]() { + auto runtime = qjs::Runtime::create(); + auto& ctx = runtime.context(); + + auto error = ctx.eval("new TypeError('boom')", "", eval_flags) + .as() + .as(); + + EXPECT_TRUE(error.name() == "TypeError"); + EXPECT_TRUE(error.message() == "boom"); + EXPECT_TRUE(error.stack().contains(":1:4")); + EXPECT_TRUE(error.format().contains("Stack Trace:")); + + auto parsed = qjs::json::parse(R"({"number":1,"text":"ok"})", ctx).as(); + auto dumped = qjs::json::stringify(parsed); + EXPECT_TRUE(dumped.contains(R"("number":1)")); + EXPECT_TRUE(dumped.contains(R"("text":"ok")")); + + auto cyclic = + ctx.eval("const x = {}; x.self = x; x;", "", eval_flags).as(); + EXPECT_TRUE( + throws_with_message([&]() { (void)qjs::json::stringify(cyclic); }, "TypeError")); + + auto count_args = qjs::Function::from( + ctx.js_context(), + [](qjs::Parameters args) { return static_cast(args.size()); }); + qjs::Parameters invalid_args{}; + invalid_args.emplace_back(); + EXPECT_TRUE( + throws_with_message([&]() { (void)count_args(invalid_args); }, "invalid value")); + }; + EXPECT_NOTHROWS(f()); + }; + TEST_CASE(object_register_reuses_class_id_per_runtime_and_separates_runtimes) { auto runtime_a = qjs::Runtime::create(); auto& ctx_a = runtime_a.context(); diff --git a/tests/unit/common/opt/clang.cc b/tests/unit/common/opt/clang.cc index 59bc12d..1bbd4d0 100644 --- a/tests/unit/common/opt/clang.cc +++ b/tests/unit/common/opt/clang.cc @@ -93,29 +93,24 @@ TEST_SUITE(clang_option_table_tests) { EXPECT_EQ(parsed.args[2].option_id.id(), opt::clang::ID_INPUT); EXPECT_EQ(parsed.args[2].get_spelling_view(), "-dash.cc"); }; - - TEST_CASE(parse_unknown_and_missing_value){ - {const auto argv = - std::to_array({"clang++", "--definitely-not-a-real-clang-flag"}); - - auto parsed = parse_command(argv); - - EXPECT_TRUE(parsed.errors.empty()); - ASSERT_EQ(parsed.args.size(), 1U); - EXPECT_EQ(parsed.args[0].option_id.id(), opt::clang::ID_UNKNOWN); - EXPECT_EQ(parsed.args[0].get_spelling_view(), "--definitely-not-a-real-clang-flag"); -} - -{ - const auto argv = std::to_array({"clang++", "-o"}); - - auto parsed = parse_command(argv); - - EXPECT_TRUE(parsed.args.empty()); - ASSERT_EQ(parsed.errors.size(), 1U); - EXPECT_TRUE(parsed.errors[0].contains("missing argument value")); -} -} -; -} -; + TEST_CASE(parse_unknown_and_missing_value) { + + { + const auto argv = + std::to_array({"clang++", "--definitely-not-a-real-clang-flag"}); + auto parsed = parse_command(argv); + EXPECT_TRUE(parsed.errors.empty()); + ASSERT_EQ(parsed.args.size(), 1U); + EXPECT_EQ(parsed.args[0].option_id.id(), opt::clang::ID_UNKNOWN); + EXPECT_EQ(parsed.args[0].get_spelling_view(), "--definitely-not-a-real-clang-flag"); + }; + + { + const auto argv = std::to_array({"clang++", "-o"}); + auto parsed = parse_command(argv); + EXPECT_TRUE(parsed.args.empty()); + ASSERT_EQ(parsed.errors.size(), 1U); + EXPECT_TRUE(parsed.errors[0].contains("missing argument value")); + }; + } +};