@teamscale/lib-instrument 1.0.0-beta.6 → 1.0.4

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/lib/index.cjs ADDED
@@ -0,0 +1,613 @@
1
+ let _babel_core = require("@babel/core");
2
+ let source_map = require("source-map");
3
+
4
+ //#region src/origins.ts
5
+ /**
6
+ * Generator for identifiers that are unique across files to instrument.
7
+ * Relevant in case no Ecmascript modules are used.
8
+ *
9
+ * We assume that the files to be executed in a browser can
10
+ * stem from different runs of the instrumenter. We have to decrease
11
+ * the probability of colliding identifiers.
12
+ */
13
+ const fileIdSeqGenerator = (() => {
14
+ const instrumenterRunId = process.pid;
15
+ let fileIdSeq = 0;
16
+ return { next: () => {
17
+ fileIdSeq++;
18
+ let num;
19
+ if (fileIdSeq < 1e4) num = instrumenterRunId * 1e4 + fileIdSeq;
20
+ else if (fileIdSeq < 1e5) num = instrumenterRunId * 1e5 + fileIdSeq;
21
+ else throw new Error(`Not more that 100k files supported to be instrumented in one run.`);
22
+ return num.toString(36);
23
+ } };
24
+ })();
25
+ /**
26
+ * Mapping source locations to their origins, before the last transpilation,
27
+ * based on the source map.
28
+ */
29
+ var SourceOrigins = class {
30
+ sourceMap;
31
+ /**
32
+ * The mapping of file ids to the file names in the transpiler origin,
33
+ * that is, the file names found in the source map.
34
+ */
35
+ originToIdMap;
36
+ constructor(sourceMap) {
37
+ this.originToIdMap = /* @__PURE__ */ new Map();
38
+ this.sourceMap = sourceMap;
39
+ }
40
+ /**
41
+ * Register source origin file and retrieve a unique identifier for it; furthermore, map
42
+ * the given location to the location in the origin.
43
+ */
44
+ ensureKnownOrigin(loc) {
45
+ let startPos = void 0;
46
+ let endPos = void 0;
47
+ let filename = loc.filename ?? "";
48
+ if (this.sourceMap) {
49
+ startPos = this.sourceMap.originalPositionFor({
50
+ line: loc.start.line,
51
+ column: loc.start.column
52
+ });
53
+ endPos = this.sourceMap.originalPositionFor({
54
+ line: loc.end.line,
55
+ column: loc.end.column
56
+ });
57
+ filename = startPos.source ?? loc.filename;
58
+ }
59
+ if (!startPos || !endPos) {
60
+ startPos = {
61
+ line: loc.start.line,
62
+ column: loc.start.column,
63
+ source: null,
64
+ name: null
65
+ };
66
+ endPos = {
67
+ line: loc.end.line,
68
+ column: loc.end.column,
69
+ source: null,
70
+ name: null
71
+ };
72
+ }
73
+ let id = this.originToIdMap.get(filename);
74
+ if (!id) {
75
+ id = `_$o${fileIdSeqGenerator.next()}`;
76
+ this.originToIdMap.set(filename, id);
77
+ }
78
+ return [id, {
79
+ start: {
80
+ line: startPos.line,
81
+ column: startPos.column,
82
+ index: -1
83
+ },
84
+ end: {
85
+ line: endPos.line,
86
+ column: endPos.column,
87
+ index: -1
88
+ },
89
+ filename,
90
+ identifierName: startPos.name
91
+ }];
92
+ }
93
+ };
94
+
95
+ //#endregion
96
+ //#region src/visitor.ts
97
+ const COMMENT_RE = /^\s*istanbul\s+ignore\s+(if|else|next)(?=\W|$)/;
98
+ const COMMENT_FILE_RE = /^\s*istanbul\s+ignore\s+(file)(?=\W|$)/;
99
+ /**
100
+ * `VisitState` holds the state of the visitor, provides helper functions
101
+ * and is the `this` for the individual coverage visitors.
102
+ */
103
+ var VisitState = class {
104
+ types;
105
+ attrs;
106
+ nextIgnore;
107
+ ignoreClassMethods;
108
+ reportLogic;
109
+ /** Callback to determine if the given source location should be instrumented. */
110
+ shouldInstrumentCallback;
111
+ /** Object that manages the mapping to the original code, using source maps. */
112
+ origins;
113
+ constructor(types, inputSourceMapConsumer, ignoreClassMethods = [], reportLogic = false, shouldInstrumentCallback) {
114
+ this.attrs = {};
115
+ this.nextIgnore = null;
116
+ this.ignoreClassMethods = ignoreClassMethods;
117
+ this.types = types;
118
+ this.reportLogic = reportLogic;
119
+ this.shouldInstrumentCallback = shouldInstrumentCallback;
120
+ this.origins = new SourceOrigins(inputSourceMapConsumer);
121
+ }
122
+ /**
123
+ * Use the configured callback, if available, to check if the given source
124
+ * location should be instrumented.
125
+ */
126
+ shouldInstrument(path, loc) {
127
+ if (this.shouldInstrumentCallback) return this.shouldInstrumentCallback(path, loc);
128
+ return true;
129
+ }
130
+ /** Should we ignore the node? Yes, if specifically ignoring or if the node is generated. */
131
+ shouldIgnore(path) {
132
+ return this.nextIgnore !== null || !path.node.loc;
133
+ }
134
+ /** Extract the ignore comment hint (next|if|else) or null. */
135
+ hintFor(node) {
136
+ let hint = null;
137
+ if (node.leadingComments) node.leadingComments.forEach((c) => {
138
+ const v = (c.value || "").trim();
139
+ const groups = COMMENT_RE.exec(v);
140
+ if (groups) hint = groups[1];
141
+ });
142
+ return hint;
143
+ }
144
+ /**
145
+ * For these expressions the statement counter needs to be hoisted, so
146
+ * function name inference can be preserved.
147
+ */
148
+ counterNeedsHoisting(path) {
149
+ return path.isFunctionExpression() || path.isArrowFunctionExpression() || path.isClassExpression();
150
+ }
151
+ /** All the generic stuff that needs to be done on enter for every node. */
152
+ onEnter(path) {
153
+ const n = path.node;
154
+ if (this.nextIgnore !== null) return;
155
+ if (this.hintFor(n) === "next") {
156
+ this.nextIgnore = n;
157
+ return;
158
+ }
159
+ if (this.getAttr(path.node, "skip-all") !== null) this.nextIgnore = n;
160
+ if (path.isFunctionExpression() && this.ignoreClassMethods.some((name) => name === path.node.id?.name)) {
161
+ this.nextIgnore = n;
162
+ return;
163
+ }
164
+ if (path.isClassMethod() && this.ignoreClassMethods.some((name) => name === path.node.key.name)) {
165
+ this.nextIgnore = n;
166
+ return;
167
+ }
168
+ }
169
+ /**
170
+ * All the generic stuff on exit of a node, including resetting ignores and custom node attrs.
171
+ */
172
+ onExit(path) {
173
+ if (path.node === this.nextIgnore) this.nextIgnore = null;
174
+ delete path.node.__cov__;
175
+ }
176
+ /** Set a node attribute for the supplied node. */
177
+ setAttr(node, name, value) {
178
+ node.__cov__ = node.__cov__ || {};
179
+ node.__cov__[name] = value;
180
+ }
181
+ /** Retrieve a node attribute for the supplied node or null. */
182
+ getAttr(node, name) {
183
+ const c = node.__cov__;
184
+ if (!c) return null;
185
+ return c[name];
186
+ }
187
+ insertCounter(path, increment) {
188
+ const T = this.types;
189
+ if (path.isBlockStatement()) path.node.body.unshift(T.expressionStatement(increment));
190
+ else if (path.isStatement()) path.insertBefore(T.expressionStatement(increment));
191
+ else if (this.counterNeedsHoisting(path) && T.isVariableDeclarator(path.parent)) {
192
+ const grandParentPath = path.parentPath?.parentPath;
193
+ if (grandParentPath && T.isExportNamedDeclaration(grandParentPath.parent)) grandParentPath.parentPath?.insertBefore(T.expressionStatement(increment));
194
+ else if (grandParentPath && (T.isProgram(grandParentPath.parent) || T.isBlockStatement(grandParentPath.parent))) grandParentPath.insertBefore(T.expressionStatement(increment));
195
+ else path.replaceWith(T.sequenceExpression([increment, path.node]));
196
+ } else if (path.isExpression()) path.replaceWith(T.sequenceExpression([increment, path.node]));
197
+ else console.error("Unable to insert counter for node type:", path.node.type);
198
+ }
199
+ insertFunctionCounter(path) {
200
+ const T = this.types;
201
+ if (!path.node?.loc) return;
202
+ const n = path.node;
203
+ let declarationLocation;
204
+ switch (n.type) {
205
+ case "FunctionDeclaration":
206
+ case "FunctionExpression":
207
+ if (n.id) declarationLocation = n.id.loc ?? void 0;
208
+ break;
209
+ }
210
+ const body = path.get("body");
211
+ const loc = path.node.loc ?? declarationLocation;
212
+ const [originFileId, originPos] = this.origins.ensureKnownOrigin(loc);
213
+ if (body.isBlockStatement() && this.shouldInstrument(path, originPos)) {
214
+ originPos.end = originPos.start;
215
+ const increment = newLineCoverageExpression(originFileId, originPos);
216
+ body.node.body.unshift(T.expressionStatement(increment));
217
+ }
218
+ }
219
+ insertStatementCounter(path) {
220
+ const loc = path.node?.loc;
221
+ if (!loc) return;
222
+ const [originFileId, originPos] = this.origins.ensureKnownOrigin(loc);
223
+ if (!this.shouldInstrument(path, originPos)) return;
224
+ const increment = newLineCoverageExpression(originFileId, originPos);
225
+ this.insertCounter(path, increment);
226
+ }
227
+ insertBranchCounter(path, loc) {
228
+ loc = loc ?? path.node.loc;
229
+ if (!loc) return;
230
+ const [originFileId, originPos] = this.origins.ensureKnownOrigin(loc);
231
+ if (this.shouldInstrument(path, originPos)) {
232
+ const increment = newLineCoverageExpression(originFileId, originPos);
233
+ this.insertCounter(path, increment);
234
+ }
235
+ }
236
+ findLeaves(node, accumulator, parent, property) {
237
+ if (!node) return;
238
+ if (node.type === "LogicalExpression") {
239
+ if (this.hintFor(node) !== "next") {
240
+ this.findLeaves(node.left, accumulator, node, "left");
241
+ this.findLeaves(node.right, accumulator, node, "right");
242
+ }
243
+ } else accumulator.push({
244
+ node,
245
+ parent,
246
+ property
247
+ });
248
+ }
249
+ };
250
+ /**
251
+ * Create a line coverage reporting statement node.
252
+ */
253
+ function newLineCoverageExpression(originFileId, range) {
254
+ const argumentList = [{
255
+ type: "Identifier",
256
+ name: originFileId
257
+ }, {
258
+ type: "NumericLiteral",
259
+ value: range.start.line
260
+ }];
261
+ if (range.start.line !== range.end.line) argumentList.push({
262
+ type: "NumericLiteral",
263
+ value: range.end.line
264
+ });
265
+ return {
266
+ type: "CallExpression",
267
+ callee: {
268
+ type: "Identifier",
269
+ name: "_$l"
270
+ },
271
+ arguments: argumentList
272
+ };
273
+ }
274
+ /**
275
+ * Creates a new string constant AST node.
276
+ */
277
+ function newStringConstDeclarationNode(name, value) {
278
+ return {
279
+ type: "VariableDeclaration",
280
+ kind: "const",
281
+ declarations: [{
282
+ type: "VariableDeclarator",
283
+ id: {
284
+ type: "Identifier",
285
+ name
286
+ },
287
+ init: {
288
+ type: "StringLiteral",
289
+ value
290
+ }
291
+ }]
292
+ };
293
+ }
294
+ /**
295
+ * Generic function that takes a set of visitor methods and
296
+ * returns a visitor object with `enter` and `exit` properties,
297
+ * such that:
298
+ *
299
+ * - standard entry processing is done
300
+ * - the supplied visitors are called only when ignore is not in effect;
301
+ * it relieves them from worrying about ignore states and generated nodes.
302
+ * - standard exit processing is done
303
+ */
304
+ function entries(...enter) {
305
+ const wrappedEntry = function(path, node) {
306
+ this.onEnter(path);
307
+ if (this.shouldIgnore(path)) return;
308
+ enter.forEach((e) => {
309
+ e.call(this, path, node);
310
+ });
311
+ };
312
+ const exit = function(path) {
313
+ this.onExit(path);
314
+ };
315
+ return {
316
+ enter: wrappedEntry,
317
+ exit
318
+ };
319
+ }
320
+ function coverStatement(path) {
321
+ this.insertStatementCounter(path);
322
+ }
323
+ function coverAssignmentPattern(path) {
324
+ this.insertBranchCounter(path.get("right"), void 0);
325
+ }
326
+ function coverFunction(path) {
327
+ this.insertFunctionCounter(path);
328
+ }
329
+ function coverVariableDeclarator(path) {
330
+ this.insertStatementCounter(path.get("init"));
331
+ }
332
+ function coverClassPropDeclarator(path) {
333
+ this.insertStatementCounter(path.get("value"));
334
+ }
335
+ function coverSequenceExpression(path) {
336
+ const T = this.types;
337
+ if (!path.isSequenceExpression()) return;
338
+ const newExpressions = [];
339
+ for (const expression of path.node.expressions) {
340
+ const [originFileId, originPos] = this.origins.ensureKnownOrigin(expression.loc);
341
+ if (this.shouldInstrument(path, originPos)) {
342
+ const increment = newLineCoverageExpression(originFileId, originPos);
343
+ newExpressions.push(increment);
344
+ }
345
+ newExpressions.push(expression);
346
+ }
347
+ path.replaceWith(T.sequenceExpression(newExpressions));
348
+ }
349
+ function makeBlock(path) {
350
+ const T = this.types;
351
+ if (!path.node) path.replaceWith(T.blockStatement([]));
352
+ if (path.isSequenceExpression()) coverSequenceExpression.call(this, path);
353
+ else if (!path.isBlockStatement()) {
354
+ path.replaceWith(T.blockStatement([path.node]));
355
+ const block = path.node;
356
+ path.node.loc = block.body[0].loc;
357
+ block.body[0].leadingComments = path.node.leadingComments;
358
+ path.node.leadingComments = void 0;
359
+ }
360
+ }
361
+ function blockProp(prop) {
362
+ return function(path) {
363
+ makeBlock.call(this, path.get(prop));
364
+ };
365
+ }
366
+ function makeParenthesizedExpressionForNonIdentifier(path) {
367
+ const T = this.types;
368
+ if (path.node && !path.isIdentifier()) path.replaceWith(T.parenthesizedExpression(path.node));
369
+ }
370
+ function parenthesizedExpressionProp(prop) {
371
+ return function(path) {
372
+ makeParenthesizedExpressionForNonIdentifier.call(this, path.get(prop));
373
+ };
374
+ }
375
+ function convertArrowExpression(path) {
376
+ const node = path.node;
377
+ const T = this.types;
378
+ if (!T.isBlockStatement(node.body)) {
379
+ const bloc = node.body.loc;
380
+ if (node.expression) node.expression = false;
381
+ node.body = T.blockStatement([T.returnStatement(node.body)]);
382
+ node.body.loc = bloc;
383
+ node.body.body[0].loc = bloc;
384
+ }
385
+ }
386
+ function coverIfBranches(path) {
387
+ const n = path.node;
388
+ const hint = this.hintFor(n);
389
+ const ignoreIf = hint === "if";
390
+ const ignoreElse = hint === "else";
391
+ if (ignoreIf) this.setAttr(n.consequent, "skip-all", true);
392
+ else this.insertBranchCounter(path.get("consequent"), n.loc);
393
+ if (ignoreElse) this.setAttr(n.alternate, "skip-all", true);
394
+ else this.insertBranchCounter(path.get("alternate"), void 0);
395
+ }
396
+ function createSwitchBranch() {}
397
+ function coverSwitchCase(path) {
398
+ const T = this.types;
399
+ const loc = path.node.loc;
400
+ if (!loc) return;
401
+ const [originFileId, originPos] = this.origins.ensureKnownOrigin(loc);
402
+ if (this.shouldInstrument(path, originPos)) {
403
+ const increment = newLineCoverageExpression(originFileId, originPos);
404
+ path.node.consequent.unshift(T.expressionStatement(increment));
405
+ }
406
+ }
407
+ function coverTernary(path) {
408
+ const n = path.node;
409
+ const cHint = this.hintFor(n.consequent);
410
+ const aHint = this.hintFor(n.alternate);
411
+ if (cHint !== "next") this.insertBranchCounter(path.get("consequent"), void 0);
412
+ if (aHint !== "next") this.insertBranchCounter(path.get("alternate"), void 0);
413
+ }
414
+ function coverLogicalExpression(path) {
415
+ const T = this.types;
416
+ if (path.parentPath.node.type === "LogicalExpression") return;
417
+ const leaves = [];
418
+ this.findLeaves(path.node, leaves, void 0, void 0);
419
+ for (const leaf of leaves) {
420
+ if (this.hintFor(leaf.node) === "next") continue;
421
+ const loc = path.node.loc;
422
+ if (!loc) continue;
423
+ const [originFileId, originPos] = this.origins.ensureKnownOrigin(loc);
424
+ if (!this.shouldInstrument(path, originPos)) continue;
425
+ const increment = newLineCoverageExpression(originFileId, originPos);
426
+ if (!increment) continue;
427
+ leaf.parent[leaf.property] = T.sequenceExpression([increment, leaf.node]);
428
+ }
429
+ }
430
+ const codeVisitor = {
431
+ ArrowFunctionExpression: entries(convertArrowExpression, coverFunction),
432
+ AssignmentPattern: entries(coverAssignmentPattern),
433
+ BlockStatement: entries(),
434
+ ExportDefaultDeclaration: entries(),
435
+ ExportNamedDeclaration: entries(),
436
+ ClassMethod: entries(coverFunction),
437
+ ClassDeclaration: entries(parenthesizedExpressionProp("superClass")),
438
+ ClassProperty: entries(coverClassPropDeclarator),
439
+ ClassPrivateProperty: entries(coverClassPropDeclarator),
440
+ ObjectMethod: entries(coverFunction),
441
+ ExpressionStatement: entries(coverStatement),
442
+ BreakStatement: entries(coverStatement),
443
+ ContinueStatement: entries(coverStatement),
444
+ DebuggerStatement: entries(coverStatement),
445
+ ReturnStatement: entries(coverStatement),
446
+ ThrowStatement: entries(coverStatement),
447
+ TryStatement: entries(coverStatement),
448
+ VariableDeclaration: entries(),
449
+ VariableDeclarator: entries(coverVariableDeclarator),
450
+ IfStatement: entries(blockProp("consequent"), blockProp("alternate"), coverStatement, coverIfBranches),
451
+ ForStatement: entries(blockProp("body"), coverStatement),
452
+ ForInStatement: entries(blockProp("body"), coverStatement),
453
+ ForOfStatement: entries(blockProp("body"), coverStatement),
454
+ WhileStatement: entries(blockProp("body"), coverStatement),
455
+ DoWhileStatement: entries(blockProp("body"), coverStatement),
456
+ SwitchStatement: entries(createSwitchBranch, coverStatement),
457
+ SwitchCase: entries(coverSwitchCase),
458
+ WithStatement: entries(blockProp("body"), coverStatement),
459
+ FunctionDeclaration: entries(coverFunction),
460
+ FunctionExpression: entries(coverFunction),
461
+ LabeledStatement: entries(coverStatement),
462
+ ConditionalExpression: entries(coverTernary),
463
+ LogicalExpression: entries(coverLogicalExpression),
464
+ SequenceExpression: entries(coverSequenceExpression)
465
+ };
466
+ /**
467
+ * The rewire plugin (and potentially other babel middleware)
468
+ * may cause files to be instrumented twice, see:
469
+ * https://github.com/istanbuljs/babel-plugin-istanbul/issues/94
470
+ * we should only instrument code for coverage the first time
471
+ * it's run through lib-instrument.
472
+ */
473
+ function alreadyInstrumented(path, visitState) {
474
+ return path.scope.hasBinding(visitState.varName);
475
+ }
476
+ function getParentComments(path) {
477
+ if (!path?.parent) return [];
478
+ if (!("comments" in path.parent)) return [];
479
+ return path.parent.comments;
480
+ }
481
+ function shouldIgnoreFile(programNodePath) {
482
+ if (!programNodePath) return false;
483
+ return getParentComments(programNodePath).some((c) => COMMENT_FILE_RE.test(c.value));
484
+ }
485
+ /**
486
+ * `programVisitor` is a `babel` adaptor for instrumentation.
487
+ *
488
+ * It returns an object with two methods `enter` and `exit`.
489
+ * These should be assigned to or called from `Program` entry and exit functions
490
+ * in a babel visitor.
491
+ *
492
+ * These functions do not make assumptions about the state set by Babel and thus
493
+ * can be used in a context other than a Babel plugin.
494
+ *
495
+ * The exit function returns an object that currently has the following keys:
496
+ *
497
+ * `fileCoverage` - the file coverage object created for the source file.
498
+ * `sourceMappingURL` - any source mapping URL found when processing the file.
499
+ *
500
+ * @param types - an instance of babel-types.
501
+ * @param inputSourceMapConsumer - access object for the source map of the input.
502
+ * @param opts - additional options.
503
+ */
504
+ function programVisitor(types, inputSourceMapConsumer, opts) {
505
+ opts = { ...opts };
506
+ const visitState = new VisitState(types, inputSourceMapConsumer, opts.ignoreClassMethods, opts.reportLogic, opts.shouldInstrumentCallback);
507
+ return {
508
+ enter(path) {
509
+ if (shouldIgnoreFile(path.find((p) => p.isProgram()))) return;
510
+ if (alreadyInstrumented(path, visitState)) return;
511
+ path.traverse(codeVisitor, visitState);
512
+ },
513
+ exit(path) {
514
+ if (alreadyInstrumented(path, visitState)) return;
515
+ const originData = visitState.origins;
516
+ if (shouldIgnoreFile(path.find((p) => p.isProgram()))) return;
517
+ const body = path.node.body;
518
+ if (opts.codeToPrepend) {
519
+ const codeToPrependAst = (0, _babel_core.parse)(opts.codeToPrepend, { sourceType: "script" });
520
+ if (codeToPrependAst !== null) body.unshift(...codeToPrependAst.program.body);
521
+ }
522
+ for (const [originPath, originId] of originData.originToIdMap.entries()) {
523
+ const declaration = newStringConstDeclarationNode(originId, originPath);
524
+ body.unshift(declaration);
525
+ }
526
+ if (opts.isInstrumentedToken) types.addComment(path.node, "leading", opts.isInstrumentedToken, false);
527
+ }
528
+ };
529
+ }
530
+
531
+ //#endregion
532
+ //#region src/instrumenter.ts
533
+ function mapSourceMapsOption(produceSourceMap) {
534
+ if (produceSourceMap === "none") return false;
535
+ if (produceSourceMap === "external") return "both";
536
+ return produceSourceMap;
537
+ }
538
+ /**
539
+ * The main class of the instrumenter.
540
+ */
541
+ var Instrumenter = class {
542
+ opts;
543
+ constructor(opts) {
544
+ this.opts = { ...opts };
545
+ }
546
+ /**
547
+ * Instrument the supplied code with coverage statements.
548
+ * To instrument EcmaScript modules, make sure to set the
549
+ * `esModules` option to `true` when creating the instrumenter.
550
+ *
551
+ * @param code - the code to instrument
552
+ * @param filename - the name of the file the code stems from.
553
+ * @param inputSourceMap - the source map that maps the not instrumented code back to its original
554
+ * @param shouldInstrumentCallback - a callback to decide if a given code fragment should be instrumented
555
+ *
556
+ * @returns the instrumented code.
557
+ */
558
+ async instrument(code, filename, inputSourceMap, shouldInstrumentCallback) {
559
+ filename = filename ?? String((/* @__PURE__ */ new Date()).getTime()) + ".js";
560
+ const { opts } = this;
561
+ const sourceMapToUse = inputSourceMap ?? opts.inputSourceMap;
562
+ let inputSourceMapConsumer = void 0;
563
+ if (sourceMapToUse) inputSourceMapConsumer = await new source_map.SourceMapConsumer(sourceMapToUse);
564
+ return (0, _babel_core.transformSync)(code, {
565
+ configFile: false,
566
+ babelrc: false,
567
+ ast: true,
568
+ filename,
569
+ inputSourceMap,
570
+ sourceMaps: mapSourceMapsOption(opts.produceSourceMap),
571
+ compact: opts.compact,
572
+ comments: opts.preserveComments,
573
+ parserOpts: {
574
+ allowAwaitOutsideFunction: true,
575
+ allowReturnOutsideFunction: opts.autoWrap,
576
+ sourceType: opts.esModules ? "module" : "script",
577
+ plugins: opts.parserPlugins
578
+ },
579
+ plugins: [[({ types }) => {
580
+ const ee = programVisitor(types, inputSourceMapConsumer, {
581
+ reportLogic: opts.reportLogic,
582
+ coverageGlobalScopeFunc: opts.coverageGlobalScopeFunc,
583
+ ignoreClassMethods: opts.ignoreClassMethods,
584
+ inputSourceMap,
585
+ isInstrumentedToken: opts.isInstrumentedToken,
586
+ codeToPrepend: opts.codeToPrepend,
587
+ shouldInstrumentCallback: shouldInstrumentCallback ?? opts.shouldInstrumentCallback
588
+ });
589
+ return { visitor: { Program: {
590
+ enter: ee.enter,
591
+ exit(path) {
592
+ ee.exit(path);
593
+ }
594
+ } } };
595
+ }]]
596
+ }).code;
597
+ }
598
+ };
599
+
600
+ //#endregion
601
+ //#region src/index.ts
602
+ /**
603
+ * Creates a new coverage instrumenter.
604
+ *
605
+ * @param opts - instrumenter options
606
+ */
607
+ function createInstrumenter(opts) {
608
+ return new Instrumenter(opts);
609
+ }
610
+
611
+ //#endregion
612
+ exports.createInstrumenter = createInstrumenter;
613
+ exports.programVisitor = programVisitor;
@@ -0,0 +1,45 @@
1
+ import { NodePath } from "@babel/core";
2
+ import * as _babel_types0 from "@babel/types";
3
+ import { Program, SourceLocation } from "@babel/types";
4
+ import { ParserPlugin } from "@babel/parser";
5
+ import { RawSourceMap, SourceMapConsumer } from "source-map";
6
+
7
+ //#region src/utils.d.ts
8
+ type InstrumentationOptions = Partial<{
9
+ reportLogic: boolean;
10
+ coverageGlobalScopeFunc: boolean;
11
+ ignoreClassMethods: string[];
12
+ inputSourceMap?: RawSourceMap;
13
+ isInstrumentedToken?: string;
14
+ codeToPrepend?: string;
15
+ shouldInstrumentCallback?: (path: NodePath, loc: SourceLocation) => boolean;
16
+ }>;
17
+ //#endregion
18
+ //#region src/instrumenter.d.ts
19
+ type InstrumenterOptions = InstrumentationOptions & Partial<{
20
+ preserveComments: boolean;
21
+ compact: boolean;
22
+ esModules: boolean;
23
+ autoWrap: boolean;
24
+ produceSourceMap: 'none' | 'inline' | 'external';
25
+ debug: boolean;
26
+ parserPlugins: ParserPlugin[];
27
+ }>;
28
+ declare class Instrumenter {
29
+ private readonly opts;
30
+ constructor(opts?: Partial<InstrumenterOptions>);
31
+ instrument(code: string, filename: string | undefined, inputSourceMap: RawSourceMap | undefined, shouldInstrumentCallback?: (path: NodePath, location: SourceLocation) => boolean): Promise<string>;
32
+ }
33
+ //#endregion
34
+ //#region src/visitor.d.ts
35
+ type BabelTypes = typeof _babel_types0;
36
+ declare function programVisitor(types: BabelTypes, inputSourceMapConsumer: SourceMapConsumer | undefined, opts: InstrumentationOptions): {
37
+ enter(path: NodePath<Program>): void;
38
+ exit(path: NodePath<Program>): void;
39
+ };
40
+ //#endregion
41
+ //#region src/index.d.ts
42
+ declare function createInstrumenter(opts: InstrumenterOptions): Instrumenter;
43
+ //#endregion
44
+ export { type InstrumenterOptions, createInstrumenter, programVisitor };
45
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/utils.ts","../src/instrumenter.ts","../src/visitor.ts","../src/index.ts"],"mappings":";;;;;;;KAOY,sBAAA,GAAyB,OAAA;EAAA,WAAA;EAAA,uBAAA;EAAA,kBAAA;EAAA,cAAA,GAWhB,YAAA;EAAA,mBAAA;EAAA,aAAA;EAAA,wBAAA,IAAA,IAAA,EASiB,QAAA,EAAA,GAAA,EAAe,cAAA;AAAA;;;KCZzC,mBAAA,GAAsB,sBAAA,GAAyB,OAAA;EAAA,gBAAA;EAAA,OAAA;EAAA,SAAA;EAAA,QAAA;EAAA,gBAAA;EAAA,KAAA;EAAA,aAAA,EAoBxC,YAAA;AAAA;AAAA,cAgBN,YAAA;EAAA,iBAAA,IAAA;EAAA,YAAA,IAAA,GAIU,OAAA,CAAQ,mBAAA;EAAA,WAAA,IAAA,UAAA,QAAA,sBAAA,cAAA,EAgBkD,YAAA,cAAA,wBAAA,IAAA,IAAA,EAC1B,QAAA,EAAA,QAAA,EAAoB,cAAA,eAA6B,OAAA;AAAA;;;KC7DnG,UAAA,UAAU,aAAA;AAAA,iBA6nBC,cAAA,CAAA,KAAA,EAAsB,UAAA,EAAA,sBAAA,EACiB,iBAAA,cAAA,IAAA,EAClB,sBAAA;EAAA,KAAA,CAAA,IAAA,EAYjB,QAAA,CAAS,OAAA;EAAA,IAAA,CAAA,IAAA,EASV,QAAA,CAAS,OAAA;AAAA;;;iBCrpBZ,kBAAA,CAAA,IAAA,EAAyB,mBAAA,GAAsB,YAAA"}
@@ -0,0 +1,45 @@
1
+ import { NodePath } from "@babel/core";
2
+ import { RawSourceMap, SourceMapConsumer } from "source-map";
3
+ import * as _babel_types0 from "@babel/types";
4
+ import { Program, SourceLocation } from "@babel/types";
5
+ import { ParserPlugin } from "@babel/parser";
6
+
7
+ //#region src/utils.d.ts
8
+ type InstrumentationOptions = Partial<{
9
+ reportLogic: boolean;
10
+ coverageGlobalScopeFunc: boolean;
11
+ ignoreClassMethods: string[];
12
+ inputSourceMap?: RawSourceMap;
13
+ isInstrumentedToken?: string;
14
+ codeToPrepend?: string;
15
+ shouldInstrumentCallback?: (path: NodePath, loc: SourceLocation) => boolean;
16
+ }>;
17
+ //#endregion
18
+ //#region src/instrumenter.d.ts
19
+ type InstrumenterOptions = InstrumentationOptions & Partial<{
20
+ preserveComments: boolean;
21
+ compact: boolean;
22
+ esModules: boolean;
23
+ autoWrap: boolean;
24
+ produceSourceMap: 'none' | 'inline' | 'external';
25
+ debug: boolean;
26
+ parserPlugins: ParserPlugin[];
27
+ }>;
28
+ declare class Instrumenter {
29
+ private readonly opts;
30
+ constructor(opts?: Partial<InstrumenterOptions>);
31
+ instrument(code: string, filename: string | undefined, inputSourceMap: RawSourceMap | undefined, shouldInstrumentCallback?: (path: NodePath, location: SourceLocation) => boolean): Promise<string>;
32
+ }
33
+ //#endregion
34
+ //#region src/visitor.d.ts
35
+ type BabelTypes = typeof _babel_types0;
36
+ declare function programVisitor(types: BabelTypes, inputSourceMapConsumer: SourceMapConsumer | undefined, opts: InstrumentationOptions): {
37
+ enter(path: NodePath<Program>): void;
38
+ exit(path: NodePath<Program>): void;
39
+ };
40
+ //#endregion
41
+ //#region src/index.d.ts
42
+ declare function createInstrumenter(opts: InstrumenterOptions): Instrumenter;
43
+ //#endregion
44
+ export { type InstrumenterOptions, createInstrumenter, programVisitor };
45
+ //# sourceMappingURL=index.d.mts.map