@reproapp/node-sdk 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/tracing.md +158 -0
- package/package.json +2 -13
- package/src/index.ts +2851 -0
- package/src/integrations/sendgrid.ts +184 -0
- package/tracer/cjs-hook.js +281 -0
- package/tracer/dep-hook.js +142 -0
- package/tracer/esm-loader.mjs +46 -0
- package/tracer/index.js +68 -0
- package/tracer/register.js +194 -0
- package/tracer/runtime.js +963 -0
- package/tracer/server.js +65 -0
- package/tracer/wrap-plugin.js +608 -0
- package/tsconfig.json +12 -0
package/tracer/server.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// server.js
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const mongoose = require('mongoose');
|
|
4
|
+
|
|
5
|
+
const app = express();
|
|
6
|
+
|
|
7
|
+
(async () => {
|
|
8
|
+
await mongoose.connect('mongodb://127.0.0.1:27018', { dbName: 'trace_demo' });
|
|
9
|
+
|
|
10
|
+
const userSchema = new mongoose.Schema({
|
|
11
|
+
name: String, email: String, age: Number
|
|
12
|
+
}, { timestamps: true });
|
|
13
|
+
const User = mongoose.model('User', userSchema);
|
|
14
|
+
|
|
15
|
+
// seed some data
|
|
16
|
+
app.get('/seed', async (_req, res) => {
|
|
17
|
+
await User.deleteMany({});
|
|
18
|
+
await User.insertMany([
|
|
19
|
+
{ name: 'Ada', email: 'ada@example.com', age: 27 },
|
|
20
|
+
{ name: 'Lin', email: 'lin@example.com', age: 35 },
|
|
21
|
+
{ name: 'Sam', email: 'sam@example.com', age: 17 },
|
|
22
|
+
]);
|
|
23
|
+
res.json({ ok: true, count: await User.countDocuments() });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// list adults
|
|
27
|
+
app.get('/users', async (_req, res) => {
|
|
28
|
+
const list = await User.find({ age: { $gte: 18 } }).lean().limit(5).exec();
|
|
29
|
+
res.json({ list });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// single by id
|
|
33
|
+
app.get('/users/:id', async (req, res) => {
|
|
34
|
+
const u = await User.findById(req.params.id).exec();
|
|
35
|
+
res.json({ u });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// aggregate example
|
|
39
|
+
app.get('/agg', async (_req, res) => {
|
|
40
|
+
const out = await User.aggregate([
|
|
41
|
+
{ $group: { _id: null, avgAge: { $avg: '$age' }, count: { $sum: 1 } } }
|
|
42
|
+
]);
|
|
43
|
+
res.json({ agg: out[0] });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// native node: fs + crypto + timers
|
|
47
|
+
app.get('/native', async (_req, res) => {
|
|
48
|
+
const fs = require('node:fs/promises');
|
|
49
|
+
const crypto = require('node:crypto');
|
|
50
|
+
|
|
51
|
+
console.log('starting native work');
|
|
52
|
+
|
|
53
|
+
const src = await fs.readFile(__filename, 'utf8');
|
|
54
|
+
await new Promise((resolve, reject) =>
|
|
55
|
+
crypto.pbkdf2('secret', Buffer.alloc(16, 7), 10_000, 32, 'sha256',
|
|
56
|
+
(err, key) => err ? reject(err) : resolve(key))
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
await new Promise(r => setTimeout(r, 20));
|
|
60
|
+
|
|
61
|
+
res.json({ ok: true, size: src.length });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
app.listen(3000, () => process.stdout.write('listening on http://localhost:3000\n'));
|
|
65
|
+
})();
|
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
// wrap-plugin.js
|
|
2
|
+
module.exports = function makeWrapPlugin(filenameForMeta, opts = {}) {
|
|
3
|
+
return ({ types: t }) => {
|
|
4
|
+
const {
|
|
5
|
+
mode = 'all', // 'all' | 'allowlist'
|
|
6
|
+
allowFns = [], // regexes or strings
|
|
7
|
+
wrapGettersSetters = false, // skip noisy accessors by default
|
|
8
|
+
skipAnonymous = false, // don't wrap anon fns in node_modules
|
|
9
|
+
mapOriginalPosition = null,
|
|
10
|
+
} = opts;
|
|
11
|
+
|
|
12
|
+
const allowFnRegexes = allowFns.map(p =>
|
|
13
|
+
typeof p === 'string' ? new RegExp(`^${escapeRx(p)}$`) : p
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
function escapeRx(s){ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
17
|
+
|
|
18
|
+
const markInternal = (node) => {
|
|
19
|
+
if (node && typeof node === 'object') {
|
|
20
|
+
node.__repro_internal = true;
|
|
21
|
+
}
|
|
22
|
+
return node;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function describeExpression(node, depth = 0) {
|
|
26
|
+
if (!node || depth > 5) return '';
|
|
27
|
+
if (t.isIdentifier(node)) return node.name;
|
|
28
|
+
if (t.isThisExpression(node)) return 'this';
|
|
29
|
+
if (t.isSuper(node)) return 'super';
|
|
30
|
+
if (t.isStringLiteral(node)) return node.value;
|
|
31
|
+
if (t.isNumericLiteral(node)) return String(node.value);
|
|
32
|
+
if (t.isMemberExpression(node)) {
|
|
33
|
+
const obj = describeExpression(node.object, depth + 1) || '';
|
|
34
|
+
let prop = '';
|
|
35
|
+
if (!node.computed && t.isIdentifier(node.property)) {
|
|
36
|
+
prop = node.property.name;
|
|
37
|
+
} else if (!node.computed && t.isStringLiteral(node.property)) {
|
|
38
|
+
prop = node.property.value;
|
|
39
|
+
} else if (!node.computed && t.isNumericLiteral(node.property)) {
|
|
40
|
+
prop = String(node.property.value);
|
|
41
|
+
} else {
|
|
42
|
+
const inner = describeExpression(node.property, depth + 1) || '?';
|
|
43
|
+
prop = node.computed ? `[${inner}]` : inner;
|
|
44
|
+
}
|
|
45
|
+
if (!obj) return prop || '';
|
|
46
|
+
if (prop.startsWith('[')) return `${obj}${prop}`;
|
|
47
|
+
return prop ? `${obj}.${prop}` : obj;
|
|
48
|
+
}
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const obj = kv => t.objectExpression(
|
|
53
|
+
Object.entries(kv)
|
|
54
|
+
.filter(([, v]) => v != null)
|
|
55
|
+
.map(([k, v]) => {
|
|
56
|
+
let valueNode;
|
|
57
|
+
if (typeof v === 'string') valueNode = t.stringLiteral(v);
|
|
58
|
+
else if (typeof v === 'number') valueNode = t.numericLiteral(v);
|
|
59
|
+
else if (typeof v === 'boolean') valueNode = t.booleanLiteral(v);
|
|
60
|
+
else if (v && typeof v === 'object' && v.type) valueNode = v;
|
|
61
|
+
else if (v === null) valueNode = t.nullLiteral();
|
|
62
|
+
else throw new Error(`Unsupported object literal value for key ${k}`);
|
|
63
|
+
return t.objectProperty(t.identifier(k), valueNode);
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
function nameFor(path){
|
|
68
|
+
const n = path.node;
|
|
69
|
+
if (n.id?.name) return n.id.name;
|
|
70
|
+
if ((path.isClassMethod() || path.isObjectMethod()) && n.key) {
|
|
71
|
+
if (t.isIdentifier(n.key)) return n.key.name;
|
|
72
|
+
if (t.isStringLiteral(n.key)) return n.key.value;
|
|
73
|
+
if (t.isNumericLiteral(n.key)) return String(n.key.value);
|
|
74
|
+
}
|
|
75
|
+
if (path.parentPath?.isVariableDeclarator() && t.isIdentifier(path.parentPath.node.id))
|
|
76
|
+
return path.parentPath.node.id.name;
|
|
77
|
+
if (path.parentPath?.isAssignmentExpression()) {
|
|
78
|
+
const left = path.parentPath.node.left;
|
|
79
|
+
if (t.isIdentifier(left)) return left.name;
|
|
80
|
+
if (t.isMemberExpression(left)) {
|
|
81
|
+
const p = left.property;
|
|
82
|
+
if (t.isIdentifier(p)) return p.name;
|
|
83
|
+
if (t.isStringLiteral(p)) return p.value;
|
|
84
|
+
if (t.isNumericLiteral(p)) return String(p.value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Heuristic: generator passed to __awaiter — treat it as the enclosing function’s name
|
|
88
|
+
if (path.parentPath?.isCallExpression() &&
|
|
89
|
+
t.isIdentifier(path.parentPath.node.callee, { name: '__awaiter' })) {
|
|
90
|
+
const enclosing = path.getFunctionParent();
|
|
91
|
+
if (enclosing && enclosing.node !== path.node) {
|
|
92
|
+
const enclosingName = nameFor(enclosing);
|
|
93
|
+
if (enclosingName && enclosingName !== '(anonymous)') return enclosingName;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return '(anonymous)';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function classifyFunction(path){
|
|
100
|
+
const n = path.node;
|
|
101
|
+
|
|
102
|
+
if (path.isClassMethod() || path.isClassPrivateMethod?.()) {
|
|
103
|
+
if (n.kind === 'constructor') return 'constructor';
|
|
104
|
+
if (n.kind === 'get') return 'getter';
|
|
105
|
+
if (n.kind === 'set') return 'setter';
|
|
106
|
+
if (n.static) return 'static-method';
|
|
107
|
+
return 'method';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (path.isObjectMethod && path.isObjectMethod()) {
|
|
111
|
+
if (n.kind === 'get') return 'getter';
|
|
112
|
+
if (n.kind === 'set') return 'setter';
|
|
113
|
+
return 'method';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (path.isClassPrivateProperty?.()) return 'method';
|
|
117
|
+
|
|
118
|
+
if (path.isArrowFunctionExpression()) return 'arrow';
|
|
119
|
+
|
|
120
|
+
if (path.isFunctionDeclaration()) return 'function';
|
|
121
|
+
if (path.isFunctionExpression()) {
|
|
122
|
+
if (path.parentPath?.isClassProperty?.()) return 'method';
|
|
123
|
+
return 'function';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return 'function';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function shouldWrap(path, name){
|
|
130
|
+
// skip getters/setters unless asked
|
|
131
|
+
if (!wrapGettersSetters &&
|
|
132
|
+
(path.node.kind === 'get' || path.node.kind === 'set')) return false;
|
|
133
|
+
|
|
134
|
+
if (skipAnonymous && name === '(anonymous)') return false;
|
|
135
|
+
|
|
136
|
+
if (mode === 'allowlist') {
|
|
137
|
+
return allowFnRegexes.length === 0
|
|
138
|
+
? false
|
|
139
|
+
: allowFnRegexes.some(rx => rx.test(name));
|
|
140
|
+
}
|
|
141
|
+
return true; // mode 'all'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const TS_HELPER_NAMES = new Set(['__awaiter', '__generator']);
|
|
145
|
+
|
|
146
|
+
function isAwaiterBody(path) {
|
|
147
|
+
if (!path.parentPath) return false;
|
|
148
|
+
if (!path.parentPath.isCallExpression()) return false;
|
|
149
|
+
const call = path.parentPath.node;
|
|
150
|
+
const callee = call.callee;
|
|
151
|
+
const isHelper = t.isIdentifier(callee, { name: '__awaiter' }) || t.isIdentifier(callee, { name: '__generator' });
|
|
152
|
+
if (!isHelper) return false;
|
|
153
|
+
if (path.listKey !== 'arguments') return false;
|
|
154
|
+
const argIndex = typeof path.key === 'number' ? path.key : -1;
|
|
155
|
+
return argIndex === call.arguments.length - 1;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function wrap(path){
|
|
159
|
+
const n = path.node;
|
|
160
|
+
if (n.__repro_internal) return;
|
|
161
|
+
if (n.__wrapped) return;
|
|
162
|
+
|
|
163
|
+
if (isAwaiterBody(path)) {
|
|
164
|
+
// Don't wrap the generator passed to __awaiter/__generator; we still want to instrument its callsites.
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const name = nameFor(path);
|
|
168
|
+
if (TS_HELPER_NAMES.has(name)) {
|
|
169
|
+
markInternal(n);
|
|
170
|
+
path.skip(); // don’t instrument TS async helpers or their internals
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (!shouldWrap(path, name)) return;
|
|
174
|
+
|
|
175
|
+
const loc = n.loc?.start || null;
|
|
176
|
+
const mapped = loc && typeof mapOriginalPosition === 'function'
|
|
177
|
+
? mapOriginalPosition(loc.line ?? null, loc.column ?? 0)
|
|
178
|
+
: null;
|
|
179
|
+
|
|
180
|
+
const file = mapped?.file || filenameForMeta;
|
|
181
|
+
const line = mapped?.line ?? loc?.line ?? null;
|
|
182
|
+
const fnType = classifyFunction(path);
|
|
183
|
+
|
|
184
|
+
if (t.isArrowFunctionExpression(n) && !t.isBlockStatement(n.body)) {
|
|
185
|
+
const bodyExprPath = path.get('body');
|
|
186
|
+
const origBody = t.cloneNode(bodyExprPath.node, true);
|
|
187
|
+
bodyExprPath.replaceWith(t.blockStatement([ t.returnStatement(origBody) ]));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const bodyPath = path.get('body');
|
|
191
|
+
if (!bodyPath.isBlockStatement()) return;
|
|
192
|
+
const body = bodyPath.node;
|
|
193
|
+
|
|
194
|
+
const argsId = path.scope.generateUidIdentifier('args');
|
|
195
|
+
const resultId = path.scope.generateUidIdentifier('result');
|
|
196
|
+
const errorId = path.scope.generateUidIdentifier('error');
|
|
197
|
+
const threwId = path.scope.generateUidIdentifier('threw');
|
|
198
|
+
|
|
199
|
+
const typeofArgs = t.unaryExpression('typeof', t.identifier('arguments'));
|
|
200
|
+
const arrayProto = t.memberExpression(t.identifier('Array'), t.identifier('prototype'));
|
|
201
|
+
const arraySlice = t.memberExpression(arrayProto, t.identifier('slice'));
|
|
202
|
+
const sliceCall = t.memberExpression(arraySlice, t.identifier('call'));
|
|
203
|
+
const argsArray = markInternal(t.callExpression(sliceCall, [ t.identifier('arguments') ]));
|
|
204
|
+
const canUseArguments = !path.isArrowFunctionExpression();
|
|
205
|
+
const argsInit = canUseArguments
|
|
206
|
+
? t.conditionalExpression(
|
|
207
|
+
t.binaryExpression('===', typeofArgs, t.stringLiteral('undefined')),
|
|
208
|
+
t.arrayExpression([]),
|
|
209
|
+
argsArray
|
|
210
|
+
)
|
|
211
|
+
: t.arrayExpression(
|
|
212
|
+
n.params.map(param => {
|
|
213
|
+
if (t.isIdentifier(param)) return t.cloneNode(param, true);
|
|
214
|
+
if (t.isRestElement(param) && t.isIdentifier(param.argument)) {
|
|
215
|
+
return t.cloneNode(param.argument, true);
|
|
216
|
+
}
|
|
217
|
+
if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) {
|
|
218
|
+
return t.cloneNode(param.left, true);
|
|
219
|
+
}
|
|
220
|
+
return t.identifier('undefined');
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const argsDecl = t.variableDeclaration('const', [
|
|
225
|
+
t.variableDeclarator(argsId, argsInit)
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
const localsDecl = t.variableDeclaration('let', [
|
|
229
|
+
t.variableDeclarator(resultId, t.identifier('undefined')),
|
|
230
|
+
t.variableDeclarator(errorId, t.nullLiteral()),
|
|
231
|
+
t.variableDeclarator(threwId, t.booleanLiteral(false))
|
|
232
|
+
]);
|
|
233
|
+
|
|
234
|
+
const fnIdForMark = (path.isFunctionDeclaration() || path.isFunctionExpression()) && n.id && t.isIdentifier(n.id)
|
|
235
|
+
? n.id
|
|
236
|
+
: null;
|
|
237
|
+
const markBodyTraced = fnIdForMark
|
|
238
|
+
? markInternal(t.expressionStatement(
|
|
239
|
+
t.assignmentExpression('=', t.memberExpression(t.identifier(fnIdForMark.name), t.identifier('__repro_body_traced')), t.booleanLiteral(true))
|
|
240
|
+
))
|
|
241
|
+
: null;
|
|
242
|
+
|
|
243
|
+
const enter = t.expressionStatement(
|
|
244
|
+
markInternal(t.callExpression(
|
|
245
|
+
t.memberExpression(t.identifier('__trace'), t.identifier('enter')),
|
|
246
|
+
[ t.stringLiteral(name), obj({ file, line, functionType: fnType }), obj({ args: argsId }) ]
|
|
247
|
+
))
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const exit = t.expressionStatement(
|
|
251
|
+
markInternal(t.callExpression(
|
|
252
|
+
t.memberExpression(t.identifier('__trace'), t.identifier('exit')),
|
|
253
|
+
[
|
|
254
|
+
obj({ fn: name, file, line, functionType: fnType }),
|
|
255
|
+
obj({ returnValue: resultId, error: errorId, threw: threwId, args: argsId })
|
|
256
|
+
]
|
|
257
|
+
))
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const errId = path.scope.generateUidIdentifier('err');
|
|
261
|
+
bodyPath.traverse({
|
|
262
|
+
ReturnStatement(retPath) {
|
|
263
|
+
if (retPath.node.__repro_wrapped) return;
|
|
264
|
+
if (retPath.getFunctionParent() !== path) return;
|
|
265
|
+
const arg = retPath.node.argument
|
|
266
|
+
? t.cloneNode(retPath.node.argument, true)
|
|
267
|
+
: t.identifier('undefined');
|
|
268
|
+
const seq = t.sequenceExpression([
|
|
269
|
+
t.assignmentExpression('=', resultId, arg),
|
|
270
|
+
resultId
|
|
271
|
+
]);
|
|
272
|
+
const newReturn = t.returnStatement(seq);
|
|
273
|
+
newReturn.__repro_wrapped = true;
|
|
274
|
+
retPath.replaceWith(newReturn);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const wrappedTry = t.tryStatement(
|
|
279
|
+
body,
|
|
280
|
+
t.catchClause(errId, t.blockStatement([
|
|
281
|
+
t.expressionStatement(
|
|
282
|
+
t.assignmentExpression('=', threwId, t.booleanLiteral(true))
|
|
283
|
+
),
|
|
284
|
+
t.expressionStatement(
|
|
285
|
+
t.assignmentExpression('=', errorId, errId)
|
|
286
|
+
),
|
|
287
|
+
t.throwStatement(errId)
|
|
288
|
+
])),
|
|
289
|
+
t.blockStatement([ exit ])
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const prologue = markBodyTraced ? [ markBodyTraced, argsDecl, localsDecl, enter ] : [ argsDecl, localsDecl, enter ];
|
|
293
|
+
const wrapped = t.blockStatement([ ...prologue, wrappedTry ]);
|
|
294
|
+
|
|
295
|
+
if (path.isFunction() || path.isClassMethod() || path.isObjectMethod()) {
|
|
296
|
+
bodyPath.replaceWith(wrapped);
|
|
297
|
+
}
|
|
298
|
+
n.__wrapped = true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function isSkippableParent(p) {
|
|
302
|
+
if (!p) return false;
|
|
303
|
+
if (p.isParenthesizedExpression && p.isParenthesizedExpression()) return true;
|
|
304
|
+
if (p.isTSAsExpression && p.isTSAsExpression()) return true;
|
|
305
|
+
if (p.isTSTypeAssertion && p.isTSTypeAssertion()) return true;
|
|
306
|
+
if (p.isTSNonNullExpression && p.isTSNonNullExpression()) return true;
|
|
307
|
+
if (p.isTypeCastExpression && p.isTypeCastExpression()) return true;
|
|
308
|
+
if (p.isSequenceExpression && p.isSequenceExpression()) return true;
|
|
309
|
+
if (p.isConditionalExpression && p.isConditionalExpression()) return true;
|
|
310
|
+
if (p.isLogicalExpression && p.isLogicalExpression()) return true;
|
|
311
|
+
if (p.isBinaryExpression && p.isBinaryExpression()) return true;
|
|
312
|
+
if (p.isUnaryExpression && p.isUnaryExpression()) return true;
|
|
313
|
+
if (p.isArrayExpression && p.isArrayExpression()) return true;
|
|
314
|
+
if (p.isObjectExpression && p.isObjectExpression()) return true;
|
|
315
|
+
if (p.isObjectProperty && p.isObjectProperty()) return true;
|
|
316
|
+
if (p.isObjectMethod && p.isObjectMethod()) return true;
|
|
317
|
+
if (p.isSpreadElement && p.isSpreadElement()) return true;
|
|
318
|
+
if (p.isNewExpression && p.isNewExpression()) return true;
|
|
319
|
+
if (p.isMemberExpression && p.isMemberExpression()) return true;
|
|
320
|
+
if (p.isOptionalMemberExpression && p.isOptionalMemberExpression()) return true;
|
|
321
|
+
if (p.isTaggedTemplateExpression && p.isTaggedTemplateExpression()) return true;
|
|
322
|
+
if (p.isTemplateLiteral && p.isTemplateLiteral()) return true;
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isAwaitedCall(path) {
|
|
327
|
+
let current = path;
|
|
328
|
+
const funcParent = path.getFunctionParent();
|
|
329
|
+
|
|
330
|
+
while (current && current.parentPath) {
|
|
331
|
+
const parent = current.parentPath;
|
|
332
|
+
|
|
333
|
+
if (parent.isAwaitExpression && parent.isAwaitExpression()) return true;
|
|
334
|
+
if (parent.isYieldExpression && parent.isYieldExpression()) return true;
|
|
335
|
+
|
|
336
|
+
if (parent.isReturnStatement && parent.isReturnStatement()) {
|
|
337
|
+
if (parent.node.argument === current.node &&
|
|
338
|
+
funcParent &&
|
|
339
|
+
(funcParent.node.async || funcParent.node.generator)) {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (parent.isForOfStatement && parent.isForOfStatement()) {
|
|
345
|
+
if (parent.node.await === true && parent.node.right === current.node) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!isSkippableParent(parent)) break;
|
|
351
|
+
current = parent;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ---- NEW: wrap every call-site with __repro_call(...) ----
|
|
358
|
+
function wrapCall(path, state) {
|
|
359
|
+
const { node: n } = path;
|
|
360
|
+
if (n.__repro_call_wrapped) return;
|
|
361
|
+
if (n.__repro_internal) return;
|
|
362
|
+
|
|
363
|
+
// Skip our helper, super(), import(), optional calls for now
|
|
364
|
+
if (t.isIdentifier(n.callee, { name: '__repro_call' })) return;
|
|
365
|
+
if (t.isIdentifier(n.callee, { name: '__awaiter' })) return;
|
|
366
|
+
if (t.isIdentifier(n.callee, { name: '__generator' })) return;
|
|
367
|
+
if (t.isSuper(n.callee)) return;
|
|
368
|
+
if (t.isImport(n.callee)) return;
|
|
369
|
+
|
|
370
|
+
if (t.isMemberExpression(n.callee) && t.isIdentifier(n.callee.object, { name: '__trace' })) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (t.isIdentifier(n.callee, { name: '__trace' })) return;
|
|
374
|
+
|
|
375
|
+
const loc = n.loc?.start || null;
|
|
376
|
+
const mapped = loc && typeof mapOriginalPosition === 'function'
|
|
377
|
+
? mapOriginalPosition(loc.line ?? null, loc.column ?? 0)
|
|
378
|
+
: null;
|
|
379
|
+
|
|
380
|
+
const file = mapped?.file || filenameForMeta || state.file.opts.filename || '';
|
|
381
|
+
const line = mapped?.line ?? loc?.line ?? 0;
|
|
382
|
+
|
|
383
|
+
const fileLit = t.stringLiteral(file ?? '');
|
|
384
|
+
const lineLit = t.numericLiteral(line ?? 0);
|
|
385
|
+
|
|
386
|
+
const unawaited = !isAwaitedCall(path);
|
|
387
|
+
|
|
388
|
+
// Default: no thisArg, label from identifier name if any
|
|
389
|
+
let label = '';
|
|
390
|
+
let callExpr;
|
|
391
|
+
|
|
392
|
+
if (t.isMemberExpression(n.callee)) {
|
|
393
|
+
// --- Member call: obj.method(...args)
|
|
394
|
+
// Hoist obj and fn into temps so obj is evaluated ONCE.
|
|
395
|
+
const objOrig = n.callee.object;
|
|
396
|
+
const prop = n.callee.property;
|
|
397
|
+
const computed = n.callee.computed === true;
|
|
398
|
+
|
|
399
|
+
const objId = path.scope.generateUidIdentifierBasedOnNode(objOrig, 'obj');
|
|
400
|
+
const fnId = path.scope.generateUidIdentifier('fn');
|
|
401
|
+
|
|
402
|
+
// label = property name when available
|
|
403
|
+
label = describeExpression(n.callee) || label;
|
|
404
|
+
|
|
405
|
+
const fnMember = t.memberExpression(objId, prop, computed);
|
|
406
|
+
const argsArray = t.arrayExpression(n.arguments.map(arg => t.cloneNode(arg, true)));
|
|
407
|
+
|
|
408
|
+
const reproCall = t.callExpression(
|
|
409
|
+
t.identifier('__repro_call'),
|
|
410
|
+
[
|
|
411
|
+
fnId,
|
|
412
|
+
objId,
|
|
413
|
+
argsArray,
|
|
414
|
+
fileLit,
|
|
415
|
+
lineLit,
|
|
416
|
+
t.stringLiteral(label || ''),
|
|
417
|
+
t.booleanLiteral(unawaited)
|
|
418
|
+
]
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
// Build a single expression that:
|
|
422
|
+
// const _obj = (origObj), _fn = _obj.prop, __repro_call(_fn, _obj, args, ...)
|
|
423
|
+
// We use a sequence expression so it works anywhere an expression is allowed.
|
|
424
|
+
callExpr = t.sequenceExpression([
|
|
425
|
+
t.assignmentExpression('=', objId, objOrig),
|
|
426
|
+
t.assignmentExpression('=', fnId, fnMember),
|
|
427
|
+
reproCall
|
|
428
|
+
]);
|
|
429
|
+
|
|
430
|
+
// Ensure the temps are declared in the current scope
|
|
431
|
+
path.scope.push({ id: objId });
|
|
432
|
+
path.scope.push({ id: fnId });
|
|
433
|
+
} else {
|
|
434
|
+
// --- Plain call: fn(...args)
|
|
435
|
+
// Evaluate callee ONCE into a temp as well (avoids re-evaluation when nested).
|
|
436
|
+
const fnOrig = n.callee;
|
|
437
|
+
const fnId = path.scope.generateUidIdentifier('fn');
|
|
438
|
+
const argsArray = t.arrayExpression(n.arguments.map(arg => t.cloneNode(arg, true)));
|
|
439
|
+
|
|
440
|
+
label = describeExpression(fnOrig) || label;
|
|
441
|
+
|
|
442
|
+
const reproCall = t.callExpression(
|
|
443
|
+
t.identifier('__repro_call'),
|
|
444
|
+
[
|
|
445
|
+
fnId,
|
|
446
|
+
t.nullLiteral(),
|
|
447
|
+
argsArray,
|
|
448
|
+
fileLit,
|
|
449
|
+
lineLit,
|
|
450
|
+
t.stringLiteral(label || ''),
|
|
451
|
+
t.booleanLiteral(unawaited)
|
|
452
|
+
]
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
callExpr = t.sequenceExpression([
|
|
456
|
+
t.assignmentExpression('=', fnId, fnOrig),
|
|
457
|
+
reproCall
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
path.scope.push({ id: fnId });
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
path.replaceWith(callExpr);
|
|
464
|
+
path.node.__repro_call_wrapped = true;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function wrapOptionalCall(path, state) {
|
|
468
|
+
const { node: n } = path;
|
|
469
|
+
if (n.__repro_call_wrapped) return;
|
|
470
|
+
if (n.__repro_internal) return;
|
|
471
|
+
|
|
472
|
+
if (t.isIdentifier(n.callee, { name: '__repro_call' })) return;
|
|
473
|
+
if (t.isIdentifier(n.callee, { name: '__awaiter' })) return;
|
|
474
|
+
if (t.isIdentifier(n.callee, { name: '__generator' })) return;
|
|
475
|
+
if (t.isSuper(n.callee)) return;
|
|
476
|
+
if (t.isImport(n.callee)) return;
|
|
477
|
+
|
|
478
|
+
const loc = n.loc?.start || null;
|
|
479
|
+
const mapped = loc && typeof mapOriginalPosition === 'function'
|
|
480
|
+
? mapOriginalPosition(loc.line ?? null, loc.column ?? 0)
|
|
481
|
+
: null;
|
|
482
|
+
|
|
483
|
+
const file = mapped?.file || filenameForMeta || state.file.opts.filename || '';
|
|
484
|
+
const line = mapped?.line ?? loc?.line ?? 0;
|
|
485
|
+
|
|
486
|
+
const fileLit = t.stringLiteral(file ?? '');
|
|
487
|
+
const lineLit = t.numericLiteral(line ?? 0);
|
|
488
|
+
|
|
489
|
+
const unawaited = !isAwaitedCall(path);
|
|
490
|
+
const label = describeExpression(n.callee) || '';
|
|
491
|
+
const makeArgsArray = () => t.arrayExpression(n.arguments.map(arg => t.cloneNode(arg, true)));
|
|
492
|
+
|
|
493
|
+
const isOptMember = (t.isOptionalMemberExpression && t.isOptionalMemberExpression(n.callee)) || false;
|
|
494
|
+
if (t.isMemberExpression(n.callee) || isOptMember) {
|
|
495
|
+
const callee = n.callee;
|
|
496
|
+
const objOrig = callee.object;
|
|
497
|
+
const prop = callee.property;
|
|
498
|
+
const computed = callee.computed === true;
|
|
499
|
+
|
|
500
|
+
const needsObjGuard = Boolean(callee.optional);
|
|
501
|
+
const needsCalleeGuard = n.optional === true;
|
|
502
|
+
|
|
503
|
+
const objId = path.scope.generateUidIdentifierBasedOnNode(objOrig, 'obj');
|
|
504
|
+
const fnId = path.scope.generateUidIdentifier('fn');
|
|
505
|
+
|
|
506
|
+
const member = t.memberExpression(objId, prop, computed);
|
|
507
|
+
const fnInit = needsObjGuard
|
|
508
|
+
? t.conditionalExpression(
|
|
509
|
+
t.binaryExpression('==', objId, t.nullLiteral()),
|
|
510
|
+
t.identifier('undefined'),
|
|
511
|
+
member
|
|
512
|
+
)
|
|
513
|
+
: member;
|
|
514
|
+
|
|
515
|
+
const tests = [];
|
|
516
|
+
if (needsObjGuard) tests.push(t.binaryExpression('==', objId, t.nullLiteral()));
|
|
517
|
+
if (needsCalleeGuard) tests.push(t.binaryExpression('==', fnId, t.nullLiteral()));
|
|
518
|
+
|
|
519
|
+
let guardTest = null;
|
|
520
|
+
if (tests.length === 1) guardTest = tests[0];
|
|
521
|
+
else if (tests.length === 2) guardTest = t.logicalExpression('||', tests[0], tests[1]);
|
|
522
|
+
|
|
523
|
+
const reproCall = t.callExpression(
|
|
524
|
+
t.identifier('__repro_call'),
|
|
525
|
+
[
|
|
526
|
+
fnId,
|
|
527
|
+
objId,
|
|
528
|
+
makeArgsArray(),
|
|
529
|
+
fileLit,
|
|
530
|
+
lineLit,
|
|
531
|
+
t.stringLiteral(label || ''),
|
|
532
|
+
t.booleanLiteral(unawaited)
|
|
533
|
+
]
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
const guardedCall = guardTest
|
|
537
|
+
? t.conditionalExpression(guardTest, t.identifier('undefined'), reproCall)
|
|
538
|
+
: reproCall;
|
|
539
|
+
|
|
540
|
+
const seq = t.sequenceExpression([
|
|
541
|
+
t.assignmentExpression('=', objId, objOrig),
|
|
542
|
+
t.assignmentExpression('=', fnId, fnInit),
|
|
543
|
+
guardedCall
|
|
544
|
+
]);
|
|
545
|
+
|
|
546
|
+
path.scope.push({ id: objId });
|
|
547
|
+
path.scope.push({ id: fnId });
|
|
548
|
+
path.replaceWith(seq);
|
|
549
|
+
path.node.__repro_call_wrapped = true;
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const fnOrig = n.callee;
|
|
554
|
+
const fnId = path.scope.generateUidIdentifier('fn');
|
|
555
|
+
const needsCalleeGuard = n.optional === true;
|
|
556
|
+
const guardTest = needsCalleeGuard ? t.binaryExpression('==', fnId, t.nullLiteral()) : null;
|
|
557
|
+
|
|
558
|
+
const reproCall = t.callExpression(
|
|
559
|
+
t.identifier('__repro_call'),
|
|
560
|
+
[
|
|
561
|
+
fnId,
|
|
562
|
+
t.nullLiteral(),
|
|
563
|
+
makeArgsArray(),
|
|
564
|
+
fileLit,
|
|
565
|
+
lineLit,
|
|
566
|
+
t.stringLiteral(label || ''),
|
|
567
|
+
t.booleanLiteral(unawaited)
|
|
568
|
+
]
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
const guardedCall = guardTest
|
|
572
|
+
? t.conditionalExpression(guardTest, t.identifier('undefined'), reproCall)
|
|
573
|
+
: reproCall;
|
|
574
|
+
|
|
575
|
+
const seq = t.sequenceExpression([
|
|
576
|
+
t.assignmentExpression('=', fnId, fnOrig),
|
|
577
|
+
guardedCall
|
|
578
|
+
]);
|
|
579
|
+
|
|
580
|
+
path.scope.push({ id: fnId });
|
|
581
|
+
path.replaceWith(seq);
|
|
582
|
+
path.node.__repro_call_wrapped = true;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return {
|
|
586
|
+
name: 'omnitrace-wrap-functions-and-calls',
|
|
587
|
+
visitor: {
|
|
588
|
+
// function body enter/exit
|
|
589
|
+
FunctionDeclaration: wrap,
|
|
590
|
+
FunctionExpression: wrap,
|
|
591
|
+
ArrowFunctionExpression: wrap,
|
|
592
|
+
ObjectMethod: wrap,
|
|
593
|
+
ClassMethod: wrap,
|
|
594
|
+
ClassPrivateMethod: wrap,
|
|
595
|
+
|
|
596
|
+
// call-site wrapping
|
|
597
|
+
CallExpression: {
|
|
598
|
+
exit(path, state) { wrapCall(path, state); }
|
|
599
|
+
},
|
|
600
|
+
OptionalCallExpression: {
|
|
601
|
+
exit(path, state) { wrapOptionalCall(path, state); }
|
|
602
|
+
},
|
|
603
|
+
// (If you also want to wrap OptionalCallExpression in older Babel ASTs,
|
|
604
|
+
// add the same handler here)
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
};
|
|
608
|
+
};
|