UNPKG

5.64 kBJavaScriptView Raw
1"use strict";
2var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
3 if (!privateMap.has(receiver)) {
4 throw new TypeError("attempted to set private field on non-instance");
5 }
6 privateMap.set(receiver, value);
7 return value;
8};
9var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
10 if (!privateMap.has(receiver)) {
11 throw new TypeError("attempted to get private field on non-instance");
12 }
13 return privateMap.get(receiver);
14};
15var __importDefault = (this && this.__importDefault) || function (mod) {
16 return (mod && mod.__esModule) ? mod : { "default": mod };
17};
18var _resolve, _reject;
19const fs_1 = __importDefault(require("fs"));
20const assert_1 = __importDefault(require("assert"));
21const callsites_1 = __importDefault(require("callsites"));
22const acorn_loose_1 = require("acorn-loose");
23const estree_walker_1 = require("estree-walker");
24const esbuild_1 = require("esbuild");
25/**
26 * Why use estree-walker over acorn-walk?
27 *
28 * acorn-walk's traverser is recursive and designed in a way where the callbacks
29 * on the way back up rather than on the way down. This makes it unoptimal for when
30 * you're implementing a "search" and you don't need to traverse the rest of the tree
31 * once you've found the target.
32 *
33 * Furthermore, estree-walker has an enter/leave API, which is optimal for scope analysis
34 * https://github.com/eslint/eslint-scope
35 *
36 * Might use this in the future to detect the exact instance of dbgr
37 *
38 */
39const getFileCode = async (filePath) => (await fs_1.default.promises.readFile(filePath)).toString();
40const isCallExpression = (node) => node.type === 'CallExpression';
41const isIdentifier = (node) => node.type === 'Identifier';
42function getCallerFilePath() {
43 const stack = callsites_1.default();
44 const currentFilePath = stack.shift().getFileName();
45 let callerFilePath;
46 while (stack.length > 0) {
47 callerFilePath = stack.shift().getFileName();
48 if (currentFilePath !== callerFilePath) {
49 break;
50 }
51 }
52 return callerFilePath;
53}
54function findNode(fileCode, conditionCallback) {
55 const ast = acorn_loose_1.parse(fileCode, {
56 ecmaVersion: 'latest',
57 sourceType: 'module',
58 });
59 let nodeString;
60 estree_walker_1.walk(ast, {
61 enter(node) {
62 if (nodeString) {
63 this.skip();
64 return;
65 }
66 const nodeMatch = conditionCallback(node);
67 if (nodeMatch) {
68 nodeString = fileCode.slice(nodeMatch.start, nodeMatch.end);
69 }
70 },
71 });
72 return nodeString;
73}
74async function getDbgrHookCode(fileCode, isTs) {
75 let dbgrHookCode = findNode(fileCode, (node) => {
76 if (isCallExpression(node)
77 && isIdentifier(node.callee)
78 && node.callee.name === 'dbgr') {
79 const [dbgrHook, evalCallback] = node.arguments;
80 assert_1.default(dbgrHook === null || dbgrHook === void 0 ? void 0 : dbgrHook.type.endsWith('FunctionExpression'), 'Dbgr hook function is missing');
81 assert_1.default(evalCallback === null || evalCallback === void 0 ? void 0 : evalCallback.type.endsWith('FunctionExpression'), 'Eval callback function is missing');
82 return dbgrHook;
83 }
84 });
85 assert_1.default(dbgrHookCode, 'Dbgr call not found');
86 // Convert function declaration to expression
87 dbgrHookCode = `(${dbgrHookCode})`;
88 if (isTs) {
89 const { code } = await esbuild_1.transform(dbgrHookCode, {
90 loader: 'ts',
91 });
92 dbgrHookCode = code;
93 }
94 return dbgrHookCode;
95}
96class Deferred {
97 constructor() {
98 this.isResolved = false;
99 _resolve.set(this, void 0);
100 _reject.set(this, void 0);
101 this.$ = new Promise((resolve, reject) => {
102 __classPrivateFieldSet(this, _resolve, resolve);
103 __classPrivateFieldSet(this, _reject, reject);
104 });
105 this.resolve = this.resolve.bind(this);
106 }
107 resolve(value) {
108 this.isResolved = true;
109 __classPrivateFieldGet(this, _resolve).call(this, value);
110 }
111}
112_resolve = new WeakMap(), _reject = new WeakMap();
113async function dbgr(dbgrHook, evalCallback) {
114 assert_1.default(typeof dbgrHook === 'function', 'Dbgr hook must be a function');
115 assert_1.default((typeof evalCallback === 'function'
116 && evalCallback.length === 1
117 && /\(?_\)?\s?=>\s?eval\(_\)/.test(evalCallback.toString())), 'Invalid eval callback');
118 const callerFilePath = getCallerFilePath();
119 const isTs = callerFilePath.endsWith('.ts');
120 let lastCode = await getFileCode(callerFilePath);
121 let lastDbgrHookCode = await getDbgrHookCode(lastCode, isTs);
122 const deferred = new Deferred();
123 await dbgrHook(deferred.resolve);
124 let watcher;
125 if (!deferred.isResolved) {
126 watcher = fs_1.default.watch(callerFilePath, async (eventName) => {
127 if (eventName !== 'change') {
128 return;
129 }
130 const newCode = await getFileCode(callerFilePath);
131 if (newCode === lastCode) {
132 return;
133 }
134 lastCode = newCode;
135 const dbgrHookCode = await getDbgrHookCode(newCode, isTs);
136 if (dbgrHookCode === lastDbgrHookCode) {
137 return;
138 }
139 lastDbgrHookCode = dbgrHookCode;
140 evalCallback(dbgrHookCode)(deferred.resolve);
141 });
142 }
143 await deferred.$;
144 if (watcher) {
145 watcher.close();
146 }
147}
148module.exports = dbgr;