diff --git a/RELEASES.rst b/RELEASES.rst index fe96b7b5..5e0bf65c 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -1396,6 +1396,17 @@ Planned explicit filename is known; this makes file/line information thrown from such code more useful in practice (GH-516, GH-644) +* Add support for non-standard module.fileName (initialized to resolved + module ID) and module.name (initialized to last component of resolved + module ID) which are used for the internal module wrapper function's + .fileName and .name properties; they show up in stack traces, debugger + integration, logger instance default naming, etc, and can now be + controlled by modSearch() (GH-639) + +* Add a .name property for the require() functions created for included + modules, so that they have a readable name in stack traces like the top + level require() function (GH-639) + * Add Windows version of the debugger example TCP transport (GH-579) * Add support for application specific debugger commands (AppRequest) and diff --git a/doc/modules.rst b/doc/modules.rst index 0a29ecf6..4f67ede0 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -12,7 +12,8 @@ built into Duktape: - http://wiki.commonjs.org/wiki/Modules/1.1.1 - - Duktape supports also ``module.exports`` + - Duktape supports also ``module.exports`` and a few Duktape specific + properties (``module.fileName`` and ``module.name``) * The user must provide a *module search function* which locates a module corresponding to a resolved module ID, and can register module symbols @@ -252,6 +253,19 @@ Duktape supports ``module.exports`` since Duktape 1.3, see: * ``test-commonjs-module-exports-repl.js`` +module.fileName and module.name +=============================== + +The ``module.fileName`` and ``module.name`` properties are Duktape specific +and allow modSearch() to control the ``.fileName`` and ``.name`` properties +of the module wrapper function used to implement module loading. This is +useful because they appear in e.g. tracebacks for errors created from the +module, see: https://github.com/svaarala/duktape/pull/639. + +The initial value for ``module.fileName`` is the full resolved module ID +(e.g. ``foo/bar``) and for ``module.name`` the last component of the +resolved module ID (e.g. ``bar``). + C modules and DLLs ================== diff --git a/src/duk_bi_global.c b/src/duk_bi_global.c index 43c0f8d7..12ae61f2 100644 --- a/src/duk_bi_global.c +++ b/src/duk_bi_global.c @@ -580,7 +580,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) { DUK_DDD(DUK_DDDPRINT("eval -> lex_env=%!iO, var_env=%!iO, this_binding=%!T", (duk_heaphdr *) outer_lex_env, (duk_heaphdr *) outer_var_env, - (duk_tval *) duk_get_tval(ctx, -1))); + duk_get_tval(ctx, -1))); /* [ source template closure this ] */ @@ -846,6 +846,7 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re duk_uint8_t buf_out[DUK_BI_COMMONJS_MODULE_ID_LIMIT]; duk_uint8_t *p; duk_uint8_t *q; + duk_uint8_t *q_last; /* last component */ DUK_ASSERT(req_id != NULL); /* mod_id may be NULL */ @@ -907,10 +908,18 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re for (;;) { duk_uint_fast8_t c; - /* Here 'p' always points to the start of a term. */ + /* Here 'p' always points to the start of a term. + * + * We can also unconditionally reset q_last here: if this is + * the last (non-empty) term q_last will have the right value + * on loop exit. + */ + DUK_DDD(DUK_DDDPRINT("resolve loop top: p -> '%s', q=%p, buf_out=%p", (const char *) p, (void *) q, (void *) buf_out)); + q_last = q; + c = *p++; if (DUK_UNLIKELY(c == 0)) { DUK_DD(DUK_DDPRINT("resolve error: requested ID must end with a non-empty term")); @@ -959,6 +968,9 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re *q++ = c; c = *p++; if (DUK_UNLIKELY(c == 0)) { + /* This was the last term, and q_last was + * updated to match this term at loop top. + */ goto loop_done; } else if (DUK_UNLIKELY(c == '/')) { *q++ = '/'; @@ -980,8 +992,17 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re } } loop_done: - + /* Output #1: resolved absolute name */ + DUK_ASSERT(q >= buf_out); duk_push_lstring(ctx, (const char *) buf_out, (size_t) (q - buf_out)); + + /* Output #2: last component name */ + DUK_ASSERT(q >= q_last); + DUK_ASSERT(q_last >= buf_out); + duk_push_lstring(ctx, (const char *) q_last, (size_t) (q - q_last)); + + DUK_DD(DUK_DDPRINT("after resolving module name: buf_out=%p, q_last=%p, q=%p", + (void *) buf_out, (void *) q_last, (void *) q)); return; resolve_error: @@ -990,6 +1011,19 @@ DUK_LOCAL void duk__bi_global_resolve_module_id(duk_context *ctx, const char *re #endif /* DUK_USE_COMMONJS_MODULES */ #if defined(DUK_USE_COMMONJS_MODULES) +/* Stack indices for better readability */ +#define DUK__IDX_REQUESTED_ID 0 /* Module id requested */ +#define DUK__IDX_REQUIRE 1 /* Current require() function */ +#define DUK__IDX_REQUIRE_ID 2 /* The base ID of the current require() function, resolution base */ +#define DUK__IDX_RESOLVED_ID 3 /* Resolved, normalized absolute module ID */ +#define DUK__IDX_LASTCOMP 4 /* Last component name in resolved path */ +#define DUK__IDX_DUKTAPE 5 /* Duktape object */ +#define DUK__IDX_MODLOADED 6 /* Duktape.modLoaded[] module cache */ +#define DUK__IDX_UNDEFINED 7 /* 'undefined', artifact of lookup */ +#define DUK__IDX_FRESH_REQUIRE 8 /* New require() function for module, updated resolution base */ +#define DUK__IDX_EXPORTS 9 /* Default exports table */ +#define DUK__IDX_MODULE 10 /* Module object containing module.exports, etc */ + DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { const char *str_req_id; /* requested identifier */ const char *str_mod_id; /* require.id of current module */ @@ -1005,23 +1039,24 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * Resolve module identifier into canonical absolute form. */ - str_req_id = duk_require_string(ctx, 0); + str_req_id = duk_require_string(ctx, DUK__IDX_REQUESTED_ID); duk_push_current_function(ctx); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_ID); - str_mod_id = duk_get_string(ctx, 2); /* ignore non-strings */ + str_mod_id = duk_get_string(ctx, DUK__IDX_REQUIRE_ID); /* ignore non-strings */ DUK_DDD(DUK_DDDPRINT("resolve module id: requested=%!T, currentmodule=%!T", - (duk_tval *) duk_get_tval(ctx, 0), - (duk_tval *) duk_get_tval(ctx, 2))); + duk_get_tval(ctx, DUK__IDX_REQUESTED_ID), + duk_get_tval(ctx, DUK__IDX_REQUIRE_ID))); duk__bi_global_resolve_module_id(ctx, str_req_id, str_mod_id); str_req_id = NULL; str_mod_id = NULL; - DUK_DDD(DUK_DDDPRINT("resolved module id: requested=%!T, currentmodule=%!T, result=%!T", - (duk_tval *) duk_get_tval(ctx, 0), - (duk_tval *) duk_get_tval(ctx, 2), - (duk_tval *) duk_get_tval(ctx, 3))); + DUK_DDD(DUK_DDDPRINT("resolved module id: requested=%!T, currentmodule=%!T, result=%!T, lastcomp=%!T", + duk_get_tval(ctx, DUK__IDX_REQUESTED_ID), + duk_get_tval(ctx, DUK__IDX_REQUIRE_ID), + duk_get_tval(ctx, DUK__IDX_RESOLVED_ID), + duk_get_tval(ctx, DUK__IDX_LASTCOMP))); - /* [ requested_id require require.id resolved_id ] */ - DUK_ASSERT_TOP(ctx, 4); + /* [ requested_id require require.id resolved_id last_comp ] */ + DUK_ASSERT_TOP(ctx, DUK__IDX_LASTCOMP + 1); /* * Cached module check. @@ -1032,27 +1067,22 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * be supported to some extent. */ - /* [ requested_id require require.id resolved_id ] */ - DUK_ASSERT_TOP(ctx, 4); - duk_push_hobject_bidx(ctx, DUK_BIDX_DUKTAPE); - duk_get_prop_stridx(ctx, 4, DUK_STRIDX_MOD_LOADED); /* Duktape.modLoaded */ - (void) duk_require_hobject(ctx, 5); - - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded ] */ - DUK_ASSERT_TOP(ctx, 6); + duk_get_prop_stridx(ctx, DUK__IDX_DUKTAPE, DUK_STRIDX_MOD_LOADED); /* Duktape.modLoaded */ + (void) duk_require_hobject(ctx, DUK__IDX_MODLOADED); + DUK_ASSERT_TOP(ctx, DUK__IDX_MODLOADED + 1); - duk_dup(ctx, 3); - if (duk_get_prop(ctx, 5)) { - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded Duktape.modLoaded[id] ] */ + duk_dup(ctx, DUK__IDX_RESOLVED_ID); + if (duk_get_prop(ctx, DUK__IDX_MODLOADED)) { + /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded Duktape.modLoaded[id] ] */ DUK_DD(DUK_DDPRINT("module already loaded: %!T", - (duk_tval *) duk_get_tval(ctx, 3))); + duk_get_tval(ctx, DUK__IDX_RESOLVED_ID))); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_EXPORTS); /* return module.exports */ return 1; } + DUK_ASSERT_TOP(ctx, DUK__IDX_UNDEFINED + 1); - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined ] */ - DUK_ASSERT_TOP(ctx, 7); + /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined ] */ /* * Module not loaded (and loading not started previously). @@ -1064,8 +1094,12 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * succeeds in finding the module. */ - DUK_DD(DUK_DDPRINT("module not yet loaded: %!T", - (duk_tval *) duk_get_tval(ctx, 3))); + DUK_D(DUK_DPRINT("loading module %!T, resolution base %!T, requested ID %!T -> resolved ID %!T, last component %!T", + duk_get_tval(ctx, DUK__IDX_RESOLVED_ID), + duk_get_tval(ctx, DUK__IDX_REQUIRE_ID), + duk_get_tval(ctx, DUK__IDX_REQUESTED_ID), + duk_get_tval(ctx, DUK__IDX_RESOLVED_ID), + duk_get_tval(ctx, DUK__IDX_LASTCOMP))); /* Fresh require: require.id is left configurable (but not writable) * so that is not easy to accidentally tweak it, but it can still be @@ -1075,32 +1109,44 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * is no practical reason to touch it. */ duk_push_c_function(ctx, duk_bi_global_object_require, 1 /*nargs*/); - duk_dup(ctx, 3); - duk_xdef_prop_stridx(ctx, 7, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_C); /* a fresh require() with require.id = resolved target module id */ + duk_push_hstring_stridx(ctx, DUK_STRIDX_REQUIRE); + duk_xdef_prop_stridx(ctx, DUK__IDX_FRESH_REQUIRE, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE); + duk_dup(ctx, DUK__IDX_RESOLVED_ID); + duk_xdef_prop_stridx(ctx, DUK__IDX_FRESH_REQUIRE, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_C); /* a fresh require() with require.id = resolved target module id */ /* Module table: * - module.exports: initial exports table (may be replaced by user) * - module.id is non-writable and non-configurable, as the CommonJS - * spec suggests this if possible. + * spec suggests this if possible + * - module.fileName: fileName where module loaded from, defaults to + * resolved ID + * - module.name: name for module wrapper function, defaults to last + * component of resolved ID */ duk_push_object(ctx); /* exports */ duk_push_object(ctx); /* module */ - duk_dup(ctx, -2); - duk_xdef_prop_stridx(ctx, 9, DUK_STRIDX_EXPORTS, DUK_PROPDESC_FLAGS_WC); /* module.exports = exports */ - duk_dup(ctx, 3); /* resolved id: require(id) must return this same module */ - duk_xdef_prop_stridx(ctx, 9, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_NONE); /* module.id = resolved_id */ - duk_compact(ctx, 9); /* module table remains registered to modLoaded, minimize its size */ + duk_dup(ctx, DUK__IDX_EXPORTS); + duk_xdef_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_EXPORTS, DUK_PROPDESC_FLAGS_WC); /* module.exports = exports */ + duk_dup(ctx, DUK__IDX_RESOLVED_ID); /* resolved id: require(id) must return this same module */ + duk_dup_top(ctx); + duk_xdef_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_ID, DUK_PROPDESC_FLAGS_NONE); /* module.id = resolved_id */ + duk_xdef_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC); /* module.fileName = resolved_id */ + duk_dup(ctx, DUK__IDX_LASTCOMP); + duk_xdef_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_WC); /* module.name = last_comp */ + duk_compact(ctx, DUK__IDX_MODULE); /* module table remains registered to modLoaded, minimize its size */ + DUK_ASSERT_TOP(ctx, DUK__IDX_MODULE + 1); - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module ] */ - DUK_ASSERT_TOP(ctx, 10); + DUK_DD(DUK_DDPRINT("module table created: %!T", duk_get_tval(ctx, DUK__IDX_MODULE))); + + /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module ] */ /* Register the module table early to modLoaded[] so that we can * support circular references even in modSearch(). If an error * is thrown, we'll delete the reference. */ - duk_dup(ctx, 3); - duk_dup(ctx, 9); - duk_put_prop(ctx, 5); /* Duktape.modLoaded[resolved_id] = module */ + duk_dup(ctx, DUK__IDX_RESOLVED_ID); + duk_dup(ctx, DUK__IDX_MODULE); + duk_put_prop(ctx, DUK__IDX_MODLOADED); /* Duktape.modLoaded[resolved_id] = module */ /* * Call user provided module search function and build the wrapped @@ -1122,13 +1168,13 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { duk_push_string(ctx, "(function(require,exports,module){"); /* Duktape.modSearch(resolved_id, fresh_require, exports, module). */ - duk_get_prop_stridx(ctx, 4, DUK_STRIDX_MOD_SEARCH); /* Duktape.modSearch */ - duk_dup(ctx, 3); - duk_dup(ctx, 7); - duk_dup(ctx, 8); - duk_dup(ctx, 9); /* [ ... Duktape.modSearch resolved_id fresh_require exports module ] */ + duk_get_prop_stridx(ctx, DUK__IDX_DUKTAPE, DUK_STRIDX_MOD_SEARCH); /* Duktape.modSearch */ + duk_dup(ctx, DUK__IDX_RESOLVED_ID); + duk_dup(ctx, DUK__IDX_FRESH_REQUIRE); + duk_dup(ctx, DUK__IDX_EXPORTS); + duk_dup(ctx, DUK__IDX_MODULE); /* [ ... Duktape.modSearch resolved_id last_comp fresh_require exports module ] */ pcall_rc = duk_pcall(ctx, 4 /*nargs*/); /* -> [ ... source ] */ - DUK_ASSERT_TOP(ctx, 12); + DUK_ASSERT_TOP(ctx, DUK__IDX_MODULE + 3); if (pcall_rc != DUK_EXEC_SUCCESS) { /* Delete entry in Duktape.modLoaded[] and rethrow. */ @@ -1138,7 +1184,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { /* If user callback did not return source code, module loading * is finished (user callback initialized exports table directly). */ - if (!duk_is_string(ctx, 11)) { + if (!duk_is_string(ctx, -1)) { /* User callback did not return source code, so module loading * is finished: just update modLoaded with final module.exports * and we're done. @@ -1146,26 +1192,30 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { goto return_exports; } - /* Finish the wrapped module source. Force resolved module ID as the - * fileName so it gets set for functions defined within a module. This - * also ensures loggers created within the module get the module ID as - * their default logger name. + /* Finish the wrapped module source. Force module.fileName as the + * function .fileName so it gets set for functions defined within a + * module. This also ensures loggers created within the module get + * the module ID (or overridden filename) as their default logger name. */ duk_push_string(ctx, "})"); duk_concat(ctx, 3); - duk_dup(ctx, 3); /* resolved module ID for fileName */ + duk_get_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_FILE_NAME); /* module.fileName for fileName */ duk_eval_raw(ctx, NULL, 0, DUK_COMPILE_EVAL); - /* XXX: The module wrapper function is currently anonymous and is shown - * in stack traces. It would be nice to force it to match the module - * name (perhaps just the cleaned up last term). At the moment 'name' - * is write protected so we can't change it directly. Note that we must - * not introduce an actual name binding into the function scope (which - * is usually the case with a named function) because it would affect - * the scope seen by the module and shadow accesses to globals of the - * same name. + /* Module has now evaluated to a wrapped module function. Force its + * .name to match module.name (defaults to last component of resolved + * ID) so that it is shown in stack traces too. Note that we must not + * introduce an actual name binding into the function scope (which is + * usually the case with a named function) because it would affect the + * scope seen by the module and shadow accesses to globals of the same name. + * This is now done by compiling the function as anonymous and then forcing + * its .name without setting a "has name binding" flag. */ + duk_push_hstring_stridx(ctx, DUK_STRIDX_NAME); + duk_get_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_NAME); + duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); + /* * Call the wrapped module function. * @@ -1173,16 +1223,16 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { * even if the module throws an error. */ - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module mod_func ] */ - DUK_ASSERT_TOP(ctx, 11); + /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module mod_func ] */ + DUK_ASSERT_TOP(ctx, DUK__IDX_MODULE + 2); - duk_dup(ctx, 8); /* exports (this binding) */ - duk_dup(ctx, 7); /* fresh require (argument) */ - duk_get_prop_stridx(ctx, 9, DUK_STRIDX_EXPORTS); /* relookup exports from module.exports in case it was changed by modSearch */ - duk_dup(ctx, 9); /* module (argument) */ + duk_dup(ctx, DUK__IDX_EXPORTS); /* exports (this binding) */ + duk_dup(ctx, DUK__IDX_FRESH_REQUIRE); /* fresh require (argument) */ + duk_get_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_EXPORTS); /* relookup exports from module.exports in case it was changed by modSearch */ + duk_dup(ctx, DUK__IDX_MODULE); /* module (argument) */ + DUK_ASSERT_TOP(ctx, DUK__IDX_MODULE + 6); - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module mod_func exports fresh_require exports module ] */ - DUK_ASSERT_TOP(ctx, 15); + /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module mod_func exports fresh_require exports module ] */ pcall_rc = duk_pcall_method(ctx, 3 /*nargs*/); if (pcall_rc != DUK_EXEC_SUCCESS) { @@ -1193,21 +1243,33 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { goto delete_rethrow; } - /* [ requested_id require require.id resolved_id Duktape Duktape.modLoaded undefined fresh_require exports module result(ignored) ] */ - DUK_ASSERT_TOP(ctx, 11); + /* [ requested_id require require.id resolved_id last_comp Duktape Duktape.modLoaded undefined fresh_require exports module result(ignored) ] */ + DUK_ASSERT_TOP(ctx, DUK__IDX_MODULE + 2); /* fall through */ return_exports: - duk_get_prop_stridx(ctx, 9, DUK_STRIDX_EXPORTS); + duk_get_prop_stridx(ctx, DUK__IDX_MODULE, DUK_STRIDX_EXPORTS); return 1; /* return module.exports */ delete_rethrow: - duk_dup(ctx, 3); - duk_del_prop(ctx, 5); /* delete Duktape.modLoaded[resolved_id] */ + duk_dup(ctx, DUK__IDX_RESOLVED_ID); + duk_del_prop(ctx, DUK__IDX_MODLOADED); /* delete Duktape.modLoaded[resolved_id] */ duk_throw(ctx); /* rethrow original error */ return 0; /* not reachable */ } + +#undef DUK__IDX_REQUESTED_ID +#undef DUK__IDX_REQUIRE +#undef DUK__IDX_REQUIRE_ID +#undef DUK__IDX_RESOLVED_ID +#undef DUK__IDX_LASTCOMP +#undef DUK__IDX_DUKTAPE +#undef DUK__IDX_MODLOADED +#undef DUK__IDX_UNDEFINED +#undef DUK__IDX_FRESH_REQUIRE +#undef DUK__IDX_EXPORTS +#undef DUK__IDX_MODULE #else DUK_INTERNAL duk_ret_t duk_bi_global_object_require(duk_context *ctx) { DUK_UNREF(ctx); diff --git a/tests/ecmascript/test-commonjs-module-filename.js b/tests/ecmascript/test-commonjs-module-filename.js new file mode 100644 index 00000000..9c9ec776 --- /dev/null +++ b/tests/ecmascript/test-commonjs-module-filename.js @@ -0,0 +1,87 @@ +/* + * Duktape 1.5 added module.fileName and module.name support. + */ + +/*=== +default behavior +test/foo2 test/foo2 test/foo2 foo2 +test/foo2 +2 +foo2 +TIME INF test/foo2: test +override .name and .fileName +test/bar2 test/bar2 test/bar2 bar2 +my_source.js +2 +my_module +TIME INF my_source.js: test +.name shadowing test +test/quux2 test/quux2 test/quux2 quux2 +object +number +123 +===*/ + +var testSource = + '/* test */\n' + + 'var err = new Error("aiee");\n' + + 'print(err.fileName);\n' + + 'print(err.lineNumber);\n' + + 'print(Duktape.act(-2).function.name);\n' + + '/*print(err.stack);*/\n' + + 'var logger = new Duktape.Logger();\n' + + 'logger.info("test");\n'; + +function test() { + // Replace Duktape.Logger.prototype.raw to censor timestamps. + + Duktape.Logger.prototype.raw = function (buf) { + print(String(buf).replace(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.*?Z/, 'TIME')); + }; + + // Default behavior, module.fileName is resolved module ID. + + Duktape.modSearch = function modSearch(id, require, exports, module) { + print(id, module.id, module.fileName, module.name); + return testSource; + }; + + print('default behavior'); + var tmp = require('test/foo1/../foo2'); + + // Override module.fileName and module.name in modSearch(). + + Duktape.modSearch = function modSearch(id, require, exports, module) { + print(id, module.id, module.fileName, module.name); + module.fileName = 'my_source.js'; // match source filename for example + module.name = 'my_module'; + return testSource; + }; + + print('override .name and .fileName'); + var tmp = require('test/bar1/../bar2'); + + // Test that the forced .name property of the module wrapper function + // does not introduce a shadowing binding -- this is important for + // semantics and works because the wrapper function is initially + // compiled as an anonymous function (which ensures the function doesn't + // get a "has a name binding" flag) and .name is then forced manually. + + var global = new Function('return this')(); + global.myFileName = 123; // this should be visible + + Duktape.modSearch = function modSearch(id, require, exports, module) { + print(id, module.id, module.fileName, module.name); + module.fileName = 'myFileName'; + return 'print(typeof Math); print(typeof myFileName); print(myFileName);'; + }; + + print('.name shadowing test'); + var tmp = require('test/quux1/../quux2'); +} + +try { + test(); +} catch (e) { + print(e.stack || e); +} diff --git a/tests/ecmascript/test-commonjs-require-filename.js b/tests/ecmascript/test-commonjs-require-filename.js index d682065b..0d622264 100644 --- a/tests/ecmascript/test-commonjs-require-filename.js +++ b/tests/ecmascript/test-commonjs-require-filename.js @@ -2,8 +2,14 @@ * Filename for functions inside a module loaded using require() * * In Duktape 1.0.0 this would always be "duk_bi_global.c" which is confusing. - * For Duktape 1.1.0 this was fixed to be the fully resolved module ID. + * + * In Duktape 1.1.0 this was fixed to be the fully resolved module ID. * See GH-58 for discussion. + * + * In Duktape 1.5.0 the module wrapper function is also given a .name which + * defaults to the last component of the resolved module ID. Both the .name + * and .fileName can be overridden by modSearch via module.name and + * module.fileName. */ /*--- @@ -13,10 +19,10 @@ ---*/ /*=== -moduleFunc name: -moduleFunc fileName: foo +moduleFunc name: foo +moduleFunc fileName: my/foo testFunc name: testFunc -testFunc fileName: foo +testFunc fileName: my/foo ===*/ function modSearch() { @@ -35,7 +41,7 @@ function test() { */ Duktape.modSearch = modSearch; - var mod = require('foo'); + var mod = require('my/foo'); /* However, functions defined within the module don't have a proper * fileName in Duktape 1.0.0. diff --git a/tests/ecmascript/test-commonjs-require-subrequire-name.js b/tests/ecmascript/test-commonjs-require-subrequire-name.js new file mode 100644 index 00000000..c83aef4f --- /dev/null +++ b/tests/ecmascript/test-commonjs-require-subrequire-name.js @@ -0,0 +1,38 @@ +/* + * The fresh require() functions given to modules should have a .name so that + * they appear nicely in tracebacks. This was not the case in Duktape 1.4.x, + * fixed in Duktape 1.5.x. + */ + +/*=== +function +string +require +false false false +===*/ + +function test() { + Duktape.modSearch = function modSearch(id, require, exports, module) { + var pd; + + print(typeof require); + print(typeof require.name); + print(require.name); + + pd = Object.getOwnPropertyDescriptor(require, 'name'); + print(pd.writable, pd.enumerable, pd.configurable); + + //print(new Error('aiee').stack); + //require('../../../../foo') + + return undefined; + }; + + require('foo/bar'); +} + +try { + test(); +} catch (e) { + print(e.stack || e); +} diff --git a/website/guide/modules.html b/website/guide/modules.html index 8dd9ad0f..d7ecd012 100644 --- a/website/guide/modules.html +++ b/website/guide/modules.html @@ -3,7 +3,8 @@
Duktape has a built-in minimal module loading framework based on
CommonJS modules version 1.1.1,
-with additional support for module.exports
.
+with additional support for module.exports
and a few Duktape
+specific module
object properties.
The internals are documented in
modules.rst.
A recommended (but not mandatory) C module convention is described in