├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── caller.js └── fixtures ├── callee.js ├── init.js └── wrapped-error.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | *.log 4 | package-lock.json 5 | .vscode 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.11" 5 | - "0.10" 6 | 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | ## v1.1.0 (March 9, 2022) 6 | - fix: handle case where Error class is wrapped (#10) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### caller 2 | 3 | Figure out your caller (thanks to @substack). 4 | 5 | ##### Initialization Time Caller 6 | ```javascript 7 | // foo.js 8 | 9 | var bar = require('bar'); 10 | ``` 11 | 12 | ```javascript 13 | // bar.js 14 | 15 | var caller = require('caller'); 16 | console.log(caller()); // `/path/to/foo.js` 17 | ``` 18 | 19 | ##### Runtime Caller 20 | ```javascript 21 | // foo.js 22 | 23 | var bar = require('bar'); 24 | bar.doWork(); 25 | ``` 26 | 27 | ```javascript 28 | // bar.js 29 | 30 | var caller = require('caller'); 31 | 32 | exports.doWork = function () { 33 | console.log(caller()); // `/path/to/foo.js` 34 | }; 35 | ``` 36 | 37 | ### Depth 38 | 39 | Caller also accepts a `depth` argument for tracing back further (defaults to `1`). 40 | 41 | ```javascript 42 | // foo.js 43 | 44 | var bar = require('bar'); 45 | bar.doWork(); 46 | ``` 47 | 48 | ```javascript 49 | // bar.js 50 | 51 | var baz = require('baz'); 52 | 53 | exports.doWork = function () { 54 | baz.doWork(); 55 | }; 56 | ``` 57 | 58 | ```javascript 59 | // baz.js 60 | 61 | var caller = require('caller'); 62 | 63 | exports.doWork = function () { 64 | console.log(caller(2)); // `/path/to/foo.js` 65 | }; 66 | ``` 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | 4 | 5 | /** 6 | * Module wrapper of @substack's `caller.js` 7 | * @original: https://github.com/substack/node-resolve/blob/master/lib/caller.js 8 | * @blessings: https://twitter.com/eriktoth/statuses/413719312273125377 9 | * @see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi 10 | */ 11 | module.exports = function (depth) { 12 | var pst, stack, file, frame, startIdx; 13 | 14 | pst = Error.prepareStackTrace; 15 | Error.prepareStackTrace = function (_, stack) { 16 | Error.prepareStackTrace = pst; 17 | return stack; 18 | }; 19 | 20 | stack = (new Error()).stack; 21 | // Handle case where error object is wrapped by say babel. Try to find current file's index first. 22 | startIdx = 0; 23 | while(startIdx < stack.length && stack[startIdx].getFileName() !== __filename) startIdx++; 24 | assert(startIdx < stack.length, 'Unexpected: unable to find caller/index.js in the stack'); 25 | 26 | depth = !depth || isNaN(depth) ? 1 : (depth > stack.length - 2 ? stack.length - 2 : depth); 27 | stack = stack.slice(startIdx + depth + 1); 28 | 29 | do { 30 | frame = stack.shift(); 31 | file = frame && frame.getFileName(); 32 | } while (stack.length && file === 'module.js'); 33 | 34 | return file; 35 | }; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caller", 3 | "version": "1.1.0", 4 | "description": "@substack's caller.js as a module", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "scripts": { 10 | "test": "tape test/*.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/totherik/caller.git" 15 | }, 16 | "keywords": [ 17 | "caller", 18 | "file", 19 | "require" 20 | ], 21 | "author": "Erik Toth ", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "tape": "~2.3.2" 25 | }, 26 | "readmeFilename": "README.md", 27 | "gitHead": "15bef0805246629cc89fb71ded29e674009ffc45", 28 | "dependencies": {} 29 | } 30 | -------------------------------------------------------------------------------- /test/caller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | var test = require('tape'), 5 | caller = require('../'); 6 | 7 | 8 | test('caller', function (t) { 9 | 10 | t.test('determine caller', function (t) { 11 | var actual, expected; 12 | 13 | actual = caller(); 14 | expected = require.resolve('tape/lib/test'); 15 | t.equal(actual, expected); 16 | t.end(); 17 | }); 18 | 19 | t.test('determine caller at runtime', function (t) { 20 | var callee, actual, expected; 21 | 22 | callee = require('./fixtures/callee'); 23 | actual = callee(caller); 24 | expected = __filename; 25 | 26 | t.equal(actual, expected); 27 | t.end(); 28 | }); 29 | 30 | t.test('determine caller with depth', function (t) { 31 | var callee, actual, expected; 32 | 33 | callee = require('./fixtures/callee'); 34 | actual = callee(caller.bind(null, 2)); 35 | expected = require.resolve('tape/lib/test'); 36 | 37 | t.equal(actual, expected); 38 | t.end(); 39 | }); 40 | 41 | t.test('determine caller when Error is wrapped', function (t) { 42 | var restoreError, actual, expected; 43 | 44 | restoreError = require('./fixtures/wrapped-error')(); 45 | try { 46 | actual = caller(); 47 | } finally { 48 | restoreError(); 49 | } 50 | expected = require.resolve('tape/lib/test'); 51 | 52 | t.equal(actual, expected); 53 | t.end(); 54 | }); 55 | 56 | t.test('determine caller with depth cap', function (t) { 57 | var callee, actual, expected; 58 | 59 | callee = require('./fixtures/callee'); 60 | actual = callee(caller.bind(null, 99)); 61 | expected = require.resolve('tape/lib/test'); 62 | 63 | t.equal(actual, expected); 64 | t.end(); 65 | }); 66 | 67 | t.test('determine caller at initialization time', function (t) { 68 | var actual, expected; 69 | 70 | actual = require('./fixtures/init'); 71 | expected = __filename; 72 | 73 | t.equal(actual, expected); 74 | t.end(); 75 | }); 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /test/fixtures/callee.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (caller) { 4 | return caller(); 5 | }; -------------------------------------------------------------------------------- /test/fixtures/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var caller = require('../../'); 4 | 5 | console.log(caller()); 6 | module.exports = caller(); -------------------------------------------------------------------------------- /test/fixtures/wrapped-error.js: -------------------------------------------------------------------------------- 1 | function copyConstructorProperties(target, source) { 2 | const keys = Object.getOwnPropertyNames(source); 3 | for (let i = 0; i < keys.length; i++) { 4 | var key = keys[i]; 5 | if (!target.hasOwnProperty(key)) { 6 | Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); 7 | } 8 | } 9 | } 10 | // inspired from https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/wrap-error-constructor-with-cause.js 11 | function wrapErrorConstructor(ERROR_NAME, wrapper) { 12 | const clearErrorStack = (function () { 13 | const TEST = (function (arg) { return String(Error(arg).stack); })('zxcasd'); 14 | const V8_OR_CHAKRA_STACK_ENTRY = /\n\s*at [^:]*:[^\n]*/; 15 | const IS_V8_OR_CHAKRA_STACK = V8_OR_CHAKRA_STACK_ENTRY.test(TEST); 16 | return function clearErrorStackInner(stack, dropEntries) { 17 | if (IS_V8_OR_CHAKRA_STACK && typeof stack == 'string') { 18 | while (dropEntries--) stack = stack.replace(V8_OR_CHAKRA_STACK_ENTRY, ''); 19 | } return stack; 20 | }; 21 | })(); 22 | 23 | const OriginalError = globalThis[ERROR_NAME]; 24 | 25 | const OriginalErrorPrototype = OriginalError.prototype; 26 | 27 | const WrappedError = wrapper(function (a) { 28 | const message = String(a); 29 | const result = new OriginalError(); 30 | if (message !== undefined) Object.defineProperty(result, 'message', { value: message, enumerable: false, configurable: true, writable: true }); 31 | Object.defineProperty(result, 'stack', { value: clearErrorStack(result.stack, 2), enumerable: false, configurable: true, writable: true }); 32 | // if (this && OriginalErrorPrototype.isPrototypeOf(this)) { 33 | // // inheritIfRequired(result, this, WrappedError); 34 | // Object.setPrototypeOf(result, this.constructor); //?? 35 | // } 36 | return result; 37 | }); 38 | 39 | WrappedError.prototype = OriginalErrorPrototype; 40 | 41 | // Copy ownKeys from OriginalError to WrappedError 42 | copyConstructorProperties(WrappedError, OriginalError); 43 | 44 | globalThis[ERROR_NAME] = WrappedError; 45 | }; 46 | 47 | module.exports = function wrapError() { 48 | const ErrorClassName = 'Error'; 49 | const OriginalError = globalThis[ErrorClassName]; 50 | wrapErrorConstructor(ErrorClassName, function (init) { 51 | return function Error(message) { return init.apply(this, arguments); }; 52 | }); 53 | 54 | return function restore() { 55 | globalThis[ErrorClassName] = OriginalError; 56 | }; 57 | }; 58 | --------------------------------------------------------------------------------