From 95a8dad19d86c1ddc5b0ba2ec90250c498bef885 Mon Sep 17 00:00:00 2001 From: Alisue Date: Tue, 26 Nov 2024 17:58:47 +0900 Subject: [PATCH] :+1: Show stack-trace on plugin error --- denops/@denops-private/service.ts | 4 ++- .../functions/denops/request_async_test.ts | 34 +++++++++++++++---- .../runtime/functions/denops/request_test.ts | 34 ++++++++++++------- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/denops/@denops-private/service.ts b/denops/@denops-private/service.ts index 49efb243..38091be9 100644 --- a/denops/@denops-private/service.ts +++ b/denops/@denops-private/service.ts @@ -275,7 +275,9 @@ class Plugin { try { return await this.#denops.dispatcher[fn](...args); } catch (err) { - const errMsg = err instanceof Error ? err.message : String(err); + const errMsg = err instanceof Error + ? err.stack ?? err.message // Prefer 'stack' if available + : String(err); throw new Error( `Failed to call '${fn}' API in '${this.name}': ${errMsg}`, ); diff --git a/tests/denops/runtime/functions/denops/request_async_test.ts b/tests/denops/runtime/functions/denops/request_async_test.ts index 15c5919f..4ad4b314 100644 --- a/tests/denops/runtime/functions/denops/request_async_test.ts +++ b/tests/denops/runtime/functions/denops/request_async_test.ts @@ -2,6 +2,7 @@ import { assertArrayIncludes, assertEquals, assertObjectMatch, + assertStringIncludes, } from "jsr:@std/assert@^1.0.1"; import { INVALID_PLUGIN_NAMES } from "/denops-testdata/invalid_plugin_names.ts"; import { resolveTestDataPath } from "/denops-testdata/resolve.ts"; @@ -131,21 +132,34 @@ testHost({ await t.step("calls failure callback", async () => { await wait(() => host.call("eval", "len(g:__test_denops_events)")); + const result = await host.call( + "eval", + "g:__test_denops_events", + // deno-lint-ignore no-explicit-any + ) as any[]; assertObjectMatch( - await host.call("eval", "g:__test_denops_events") as unknown[], + result, { 0: [ "TestDenopsRequestAsyncFailure:Called", [ { - message: - "Failed to call 'fail' API in 'dummy': Dummy failure", name: "Error", }, ], ], }, ); + const message = result[0][1][0].message as string; + assertStringIncludes( + message, + "Failed to call 'fail' API in 'dummy': Error: Dummy failure", + ); + assertStringIncludes( + message, + "dummy_dispatcher_plugin.ts:19:13", + "Error message should include the where the original error occurred", + ); }); }); @@ -165,21 +179,29 @@ testHost({ await t.step("calls failure callback", async () => { await wait(() => host.call("eval", "len(g:__test_denops_events)")); + const result = await host.call( + "eval", + "g:__test_denops_events", + // deno-lint-ignore no-explicit-any + ) as any[]; assertObjectMatch( - await host.call("eval", "g:__test_denops_events") as unknown[], + result, { 0: [ "TestDenopsRequestAsyncFailure:Called", [ { - message: - "Failed to call 'not_exist_method' API in 'dummy': this[#denops].dispatcher[fn] is not a function", name: "Error", }, ], ], }, ); + const message = result[0][1][0].message as string; + assertStringIncludes( + message, + "Failed to call 'not_exist_method' API in 'dummy': TypeError: this[#denops].dispatcher[fn] is not a function", + ); }); }); }); diff --git a/tests/denops/runtime/functions/denops/request_test.ts b/tests/denops/runtime/functions/denops/request_test.ts index 0023949d..a5fa17ce 100644 --- a/tests/denops/runtime/functions/denops/request_test.ts +++ b/tests/denops/runtime/functions/denops/request_test.ts @@ -1,4 +1,9 @@ -import { assertEquals, assertRejects } from "jsr:@std/assert@^1.0.1"; +import { + assertEquals, + assertInstanceOf, + assertRejects, + assertStringIncludes, +} from "jsr:@std/assert@^1.0.1"; import { INVALID_PLUGIN_NAMES } from "/denops-testdata/invalid_plugin_names.ts"; import { resolveTestDataPath } from "/denops-testdata/resolve.ts"; import { testHost } from "/denops-testutil/host.ts"; @@ -68,16 +73,21 @@ testHost({ await t.step("if the dispatcher method throws an error", async (t) => { await t.step("throws an error", async () => { - await assertRejects( - () => - host.call( - "denops#request", - "dummy", - "fail", - ["foo"], - ), - Error, - "Failed to call 'fail' API in 'dummy': Dummy failure", + const result = await host.call( + "denops#request", + "dummy", + "fail", + ["foo"], + ).catch((e) => e); + assertInstanceOf(result, Error); + assertStringIncludes( + result.message, + "Failed to call 'fail' API in 'dummy': Error: Dummy failure", + ); + assertStringIncludes( + result.message, + "dummy_dispatcher_plugin.ts:19:13", + "Error message should include the where the original error occurred", ); }); }); @@ -93,7 +103,7 @@ testHost({ ["foo"], ), Error, - "Failed to call 'not_exist_method' API in 'dummy': this[#denops].dispatcher[fn] is not a function", + "Failed to call 'not_exist_method' API in 'dummy': TypeError: this[#denops].dispatcher[fn] is not a function", ); }); });