TS/Node.js: Getting the absolute path of the class instance rather than the class itself

Viewed 246

Is there a way to get the path (__dirname) of the file where an instance of a class was made without passing that into the constructor? For example,

// src/classes/A.ts
export class A {
    private instanceDirname: string;
    constructor() {
        this.instanceDirname = ??
    }
}

// src/index.ts
import { A } from "./classes/A"
const a = new A();
// a.instanceDirname === __dirname ✓

I tried callsite, it worked but I had to do some regex that I'm not happy with to get what I need, I also tried a module called caller-callsite, but that ended up returning the module path, not the path of the file where the instance was made. Is there a workaround for this?

1 Answers

I would have callers pass in the location information. Sniffing this stuff seems like a code smell to me (pardon the pun). ;-)

But you can do it, by using regular expressions on the V8 call stack from an Error instance, but it still involves doing regular expressions (which you didn't like with callsite), though it's doing them on V8's own stacks, which aren't likely to change in a breaking way (and certainly won't except when you do upgrades of Node.js, so it's easy to test). See comments:

// A regular expression to look for lines in this file (A.ts / A.js)
const rexThisFile = /\bA\.[tj]s:/i;

// Your class
export class A {
    constructor() {
        // Get a stack trace, break into lines -- this is V8, we can rely on the format
        const stackLines = (new Error().stack).split(/\r\n|\r|\n/);
        // Find the first line that doesn't reference this file
        const line = stackLines.find((line, index) => index > 0 && !rexThisFile.test(line));
        if (line) {
            // Found it, extract the directory from it
            const instanceOfDirName = line.replace(/^\s*at\s*/, "")
                         .replace(/\w+\.[tj]s[:\d]+$/, "")
                         .replace(/^file:\/\//, "");
            console.log(`instanceOfDirName = "${instanceOfDirName}"`);
        }
    }
}

Those three replaces can be combined:

const instanceOfDirName = line.replace(/(?:^\s*at\s*(?:file:\/\/)?)|(?:\w+\.[tj]s[:\d]+$)/g, "");

...but I left them separate for clarity; not going to make any performance difference to care about.

Related