source: trip-planner-front/node_modules/istanbul-lib-instrument/dist/visitor.js@ ceaed42

Last change on this file since ceaed42 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 19.8 KB
Line 
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = void 0;
7
8var _crypto = require("crypto");
9
10var _core = require("@babel/core");
11
12var _schema = require("@istanbuljs/schema");
13
14var _sourceCoverage = require("./source-coverage");
15
16var _constants = require("./constants");
17
18// pattern for istanbul to ignore a section
19const COMMENT_RE = /^\s*istanbul\s+ignore\s+(if|else|next)(?=\W|$)/; // pattern for istanbul to ignore the whole file
20
21const COMMENT_FILE_RE = /^\s*istanbul\s+ignore\s+(file)(?=\W|$)/; // source map URL pattern
22
23const SOURCE_MAP_RE = /[#@]\s*sourceMappingURL=(.*)\s*$/m; // generate a variable name from hashing the supplied file path
24
25function genVar(filename) {
26 const hash = (0, _crypto.createHash)(_constants.SHA);
27 hash.update(filename);
28 return 'cov_' + parseInt(hash.digest('hex').substr(0, 12), 16).toString(36);
29} // VisitState holds the state of the visitor, provides helper functions
30// and is the `this` for the individual coverage visitors.
31
32
33class VisitState {
34 constructor(types, sourceFilePath, inputSourceMap, ignoreClassMethods = []) {
35 this.varName = genVar(sourceFilePath);
36 this.attrs = {};
37 this.nextIgnore = null;
38 this.cov = new _sourceCoverage.SourceCoverage(sourceFilePath);
39
40 if (typeof inputSourceMap !== 'undefined') {
41 this.cov.inputSourceMap(inputSourceMap);
42 }
43
44 this.ignoreClassMethods = ignoreClassMethods;
45 this.types = types;
46 this.sourceMappingURL = null;
47 } // should we ignore the node? Yes, if specifically ignoring
48 // or if the node is generated.
49
50
51 shouldIgnore(path) {
52 return this.nextIgnore || !path.node.loc;
53 } // extract the ignore comment hint (next|if|else) or null
54
55
56 hintFor(node) {
57 let hint = null;
58
59 if (node.leadingComments) {
60 node.leadingComments.forEach(c => {
61 const v = (c.value ||
62 /* istanbul ignore next: paranoid check */
63 '').trim();
64 const groups = v.match(COMMENT_RE);
65
66 if (groups) {
67 hint = groups[1];
68 }
69 });
70 }
71
72 return hint;
73 } // extract a source map URL from comments and keep track of it
74
75
76 maybeAssignSourceMapURL(node) {
77 const extractURL = comments => {
78 if (!comments) {
79 return;
80 }
81
82 comments.forEach(c => {
83 const v = (c.value ||
84 /* istanbul ignore next: paranoid check */
85 '').trim();
86 const groups = v.match(SOURCE_MAP_RE);
87
88 if (groups) {
89 this.sourceMappingURL = groups[1];
90 }
91 });
92 };
93
94 extractURL(node.leadingComments);
95 extractURL(node.trailingComments);
96 } // for these expressions the statement counter needs to be hoisted, so
97 // function name inference can be preserved
98
99
100 counterNeedsHoisting(path) {
101 return path.isFunctionExpression() || path.isArrowFunctionExpression() || path.isClassExpression();
102 } // all the generic stuff that needs to be done on enter for every node
103
104
105 onEnter(path) {
106 const n = path.node;
107 this.maybeAssignSourceMapURL(n); // if already ignoring, nothing more to do
108
109 if (this.nextIgnore !== null) {
110 return;
111 } // check hint to see if ignore should be turned on
112
113
114 const hint = this.hintFor(n);
115
116 if (hint === 'next') {
117 this.nextIgnore = n;
118 return;
119 } // else check custom node attribute set by a prior visitor
120
121
122 if (this.getAttr(path.node, 'skip-all') !== null) {
123 this.nextIgnore = n;
124 } // else check for ignored class methods
125
126
127 if (path.isFunctionExpression() && this.ignoreClassMethods.some(name => path.node.id && name === path.node.id.name)) {
128 this.nextIgnore = n;
129 return;
130 }
131
132 if (path.isClassMethod() && this.ignoreClassMethods.some(name => name === path.node.key.name)) {
133 this.nextIgnore = n;
134 return;
135 }
136 } // all the generic stuff on exit of a node,
137 // including reseting ignores and custom node attrs
138
139
140 onExit(path) {
141 // restore ignore status, if needed
142 if (path.node === this.nextIgnore) {
143 this.nextIgnore = null;
144 } // nuke all attributes for the node
145
146
147 delete path.node.__cov__;
148 } // set a node attribute for the supplied node
149
150
151 setAttr(node, name, value) {
152 node.__cov__ = node.__cov__ || {};
153 node.__cov__[name] = value;
154 } // retrieve a node attribute for the supplied node or null
155
156
157 getAttr(node, name) {
158 const c = node.__cov__;
159
160 if (!c) {
161 return null;
162 }
163
164 return c[name];
165 } //
166
167
168 increase(type, id, index) {
169 const T = this.types;
170 const wrap = index !== null ? // If `index` present, turn `x` into `x[index]`.
171 x => T.memberExpression(x, T.numericLiteral(index), true) : x => x;
172 return T.updateExpression('++', wrap(T.memberExpression(T.memberExpression(T.callExpression(T.identifier(this.varName), []), T.identifier(type)), T.numericLiteral(id), true)));
173 }
174
175 insertCounter(path, increment) {
176 const T = this.types;
177
178 if (path.isBlockStatement()) {
179 path.node.body.unshift(T.expressionStatement(increment));
180 } else if (path.isStatement()) {
181 path.insertBefore(T.expressionStatement(increment));
182 } else if (this.counterNeedsHoisting(path) && T.isVariableDeclarator(path.parentPath)) {
183 // make an attempt to hoist the statement counter, so that
184 // function names are maintained.
185 const parent = path.parentPath.parentPath;
186
187 if (parent && T.isExportNamedDeclaration(parent.parentPath)) {
188 parent.parentPath.insertBefore(T.expressionStatement(increment));
189 } else if (parent && (T.isProgram(parent.parentPath) || T.isBlockStatement(parent.parentPath))) {
190 parent.insertBefore(T.expressionStatement(increment));
191 } else {
192 path.replaceWith(T.sequenceExpression([increment, path.node]));
193 }
194 }
195 /* istanbul ignore else: not expected */
196 else if (path.isExpression()) {
197 path.replaceWith(T.sequenceExpression([increment, path.node]));
198 } else {
199 console.error('Unable to insert counter for node type:', path.node.type);
200 }
201 }
202
203 insertStatementCounter(path) {
204 /* istanbul ignore if: paranoid check */
205 if (!(path.node && path.node.loc)) {
206 return;
207 }
208
209 const index = this.cov.newStatement(path.node.loc);
210 const increment = this.increase('s', index, null);
211 this.insertCounter(path, increment);
212 }
213
214 insertFunctionCounter(path) {
215 const T = this.types;
216 /* istanbul ignore if: paranoid check */
217
218 if (!(path.node && path.node.loc)) {
219 return;
220 }
221
222 const n = path.node;
223 let dloc = null; // get location for declaration
224
225 switch (n.type) {
226 case 'FunctionDeclaration':
227 /* istanbul ignore else: paranoid check */
228 if (n.id) {
229 dloc = n.id.loc;
230 }
231
232 break;
233
234 case 'FunctionExpression':
235 if (n.id) {
236 dloc = n.id.loc;
237 }
238
239 break;
240 }
241
242 if (!dloc) {
243 dloc = {
244 start: n.loc.start,
245 end: {
246 line: n.loc.start.line,
247 column: n.loc.start.column + 1
248 }
249 };
250 }
251
252 const name = path.node.id ? path.node.id.name : path.node.name;
253 const index = this.cov.newFunction(name, dloc, path.node.body.loc);
254 const increment = this.increase('f', index, null);
255 const body = path.get('body');
256 /* istanbul ignore else: not expected */
257
258 if (body.isBlockStatement()) {
259 body.node.body.unshift(T.expressionStatement(increment));
260 } else {
261 console.error('Unable to process function body node type:', path.node.type);
262 }
263 }
264
265 getBranchIncrement(branchName, loc) {
266 const index = this.cov.addBranchPath(branchName, loc);
267 return this.increase('b', branchName, index);
268 }
269
270 insertBranchCounter(path, branchName, loc) {
271 const increment = this.getBranchIncrement(branchName, loc || path.node.loc);
272 this.insertCounter(path, increment);
273 }
274
275 findLeaves(node, accumulator, parent, property) {
276 if (!node) {
277 return;
278 }
279
280 if (node.type === 'LogicalExpression') {
281 const hint = this.hintFor(node);
282
283 if (hint !== 'next') {
284 this.findLeaves(node.left, accumulator, node, 'left');
285 this.findLeaves(node.right, accumulator, node, 'right');
286 }
287 } else {
288 accumulator.push({
289 node,
290 parent,
291 property
292 });
293 }
294 }
295
296} // generic function that takes a set of visitor methods and
297// returns a visitor object with `enter` and `exit` properties,
298// such that:
299//
300// * standard entry processing is done
301// * the supplied visitors are called only when ignore is not in effect
302// This relieves them from worrying about ignore states and generated nodes.
303// * standard exit processing is done
304//
305
306
307function entries(...enter) {
308 // the enter function
309 const wrappedEntry = function (path, node) {
310 this.onEnter(path);
311
312 if (this.shouldIgnore(path)) {
313 return;
314 }
315
316 enter.forEach(e => {
317 e.call(this, path, node);
318 });
319 };
320
321 const exit = function (path, node) {
322 this.onExit(path, node);
323 };
324
325 return {
326 enter: wrappedEntry,
327 exit
328 };
329}
330
331function coverStatement(path) {
332 this.insertStatementCounter(path);
333}
334/* istanbul ignore next: no node.js support */
335
336
337function coverAssignmentPattern(path) {
338 const n = path.node;
339 const b = this.cov.newBranch('default-arg', n.loc);
340 this.insertBranchCounter(path.get('right'), b);
341}
342
343function coverFunction(path) {
344 this.insertFunctionCounter(path);
345}
346
347function coverVariableDeclarator(path) {
348 this.insertStatementCounter(path.get('init'));
349}
350
351function coverClassPropDeclarator(path) {
352 this.insertStatementCounter(path.get('value'));
353}
354
355function makeBlock(path) {
356 const T = this.types;
357
358 if (!path.node) {
359 path.replaceWith(T.blockStatement([]));
360 }
361
362 if (!path.isBlockStatement()) {
363 path.replaceWith(T.blockStatement([path.node]));
364 path.node.loc = path.node.body[0].loc;
365 path.node.body[0].leadingComments = path.node.leadingComments;
366 path.node.leadingComments = undefined;
367 }
368}
369
370function blockProp(prop) {
371 return function (path) {
372 makeBlock.call(this, path.get(prop));
373 };
374}
375
376function makeParenthesizedExpressionForNonIdentifier(path) {
377 const T = this.types;
378
379 if (path.node && !path.isIdentifier()) {
380 path.replaceWith(T.parenthesizedExpression(path.node));
381 }
382}
383
384function parenthesizedExpressionProp(prop) {
385 return function (path) {
386 makeParenthesizedExpressionForNonIdentifier.call(this, path.get(prop));
387 };
388}
389
390function convertArrowExpression(path) {
391 const n = path.node;
392 const T = this.types;
393
394 if (!T.isBlockStatement(n.body)) {
395 const bloc = n.body.loc;
396
397 if (n.expression === true) {
398 n.expression = false;
399 }
400
401 n.body = T.blockStatement([T.returnStatement(n.body)]); // restore body location
402
403 n.body.loc = bloc; // set up the location for the return statement so it gets
404 // instrumented
405
406 n.body.body[0].loc = bloc;
407 }
408}
409
410function coverIfBranches(path) {
411 const n = path.node;
412 const hint = this.hintFor(n);
413 const ignoreIf = hint === 'if';
414 const ignoreElse = hint === 'else';
415 const branch = this.cov.newBranch('if', n.loc);
416
417 if (ignoreIf) {
418 this.setAttr(n.consequent, 'skip-all', true);
419 } else {
420 this.insertBranchCounter(path.get('consequent'), branch, n.loc);
421 }
422
423 if (ignoreElse) {
424 this.setAttr(n.alternate, 'skip-all', true);
425 } else {
426 this.insertBranchCounter(path.get('alternate'), branch, n.loc);
427 }
428}
429
430function createSwitchBranch(path) {
431 const b = this.cov.newBranch('switch', path.node.loc);
432 this.setAttr(path.node, 'branchName', b);
433}
434
435function coverSwitchCase(path) {
436 const T = this.types;
437 const b = this.getAttr(path.parentPath.node, 'branchName');
438 /* istanbul ignore if: paranoid check */
439
440 if (b === null) {
441 throw new Error('Unable to get switch branch name');
442 }
443
444 const increment = this.getBranchIncrement(b, path.node.loc);
445 path.node.consequent.unshift(T.expressionStatement(increment));
446}
447
448function coverTernary(path) {
449 const n = path.node;
450 const branch = this.cov.newBranch('cond-expr', path.node.loc);
451 const cHint = this.hintFor(n.consequent);
452 const aHint = this.hintFor(n.alternate);
453
454 if (cHint !== 'next') {
455 this.insertBranchCounter(path.get('consequent'), branch);
456 }
457
458 if (aHint !== 'next') {
459 this.insertBranchCounter(path.get('alternate'), branch);
460 }
461}
462
463function coverLogicalExpression(path) {
464 const T = this.types;
465
466 if (path.parentPath.node.type === 'LogicalExpression') {
467 return; // already processed
468 }
469
470 const leaves = [];
471 this.findLeaves(path.node, leaves);
472 const b = this.cov.newBranch('binary-expr', path.node.loc);
473
474 for (let i = 0; i < leaves.length; i += 1) {
475 const leaf = leaves[i];
476 const hint = this.hintFor(leaf.node);
477
478 if (hint === 'next') {
479 continue;
480 }
481
482 const increment = this.getBranchIncrement(b, leaf.node.loc);
483
484 if (!increment) {
485 continue;
486 }
487
488 leaf.parent[leaf.property] = T.sequenceExpression([increment, leaf.node]);
489 }
490}
491
492const codeVisitor = {
493 ArrowFunctionExpression: entries(convertArrowExpression, coverFunction),
494 AssignmentPattern: entries(coverAssignmentPattern),
495 BlockStatement: entries(),
496 // ignore processing only
497 ExportDefaultDeclaration: entries(),
498 // ignore processing only
499 ExportNamedDeclaration: entries(),
500 // ignore processing only
501 ClassMethod: entries(coverFunction),
502 ClassDeclaration: entries(parenthesizedExpressionProp('superClass')),
503 ClassProperty: entries(coverClassPropDeclarator),
504 ClassPrivateProperty: entries(coverClassPropDeclarator),
505 ObjectMethod: entries(coverFunction),
506 ExpressionStatement: entries(coverStatement),
507 BreakStatement: entries(coverStatement),
508 ContinueStatement: entries(coverStatement),
509 DebuggerStatement: entries(coverStatement),
510 ReturnStatement: entries(coverStatement),
511 ThrowStatement: entries(coverStatement),
512 TryStatement: entries(coverStatement),
513 VariableDeclaration: entries(),
514 // ignore processing only
515 VariableDeclarator: entries(coverVariableDeclarator),
516 IfStatement: entries(blockProp('consequent'), blockProp('alternate'), coverStatement, coverIfBranches),
517 ForStatement: entries(blockProp('body'), coverStatement),
518 ForInStatement: entries(blockProp('body'), coverStatement),
519 ForOfStatement: entries(blockProp('body'), coverStatement),
520 WhileStatement: entries(blockProp('body'), coverStatement),
521 DoWhileStatement: entries(blockProp('body'), coverStatement),
522 SwitchStatement: entries(createSwitchBranch, coverStatement),
523 SwitchCase: entries(coverSwitchCase),
524 WithStatement: entries(blockProp('body'), coverStatement),
525 FunctionDeclaration: entries(coverFunction),
526 FunctionExpression: entries(coverFunction),
527 LabeledStatement: entries(coverStatement),
528 ConditionalExpression: entries(coverTernary),
529 LogicalExpression: entries(coverLogicalExpression)
530};
531const globalTemplateAlteredFunction = (0, _core.template)(`
532 var Function = (function(){}).constructor;
533 var global = (new Function(GLOBAL_COVERAGE_SCOPE))();
534`);
535const globalTemplateFunction = (0, _core.template)(`
536 var global = (new Function(GLOBAL_COVERAGE_SCOPE))();
537`);
538const globalTemplateVariable = (0, _core.template)(`
539 var global = GLOBAL_COVERAGE_SCOPE;
540`); // the template to insert at the top of the program.
541
542const coverageTemplate = (0, _core.template)(`
543 function COVERAGE_FUNCTION () {
544 var path = PATH;
545 var hash = HASH;
546 GLOBAL_COVERAGE_TEMPLATE
547 var gcv = GLOBAL_COVERAGE_VAR;
548 var coverageData = INITIAL;
549 var coverage = global[gcv] || (global[gcv] = {});
550 if (!coverage[path] || coverage[path].hash !== hash) {
551 coverage[path] = coverageData;
552 }
553
554 var actualCoverage = coverage[path];
555 {
556 // @ts-ignore
557 COVERAGE_FUNCTION = function () {
558 return actualCoverage;
559 }
560 }
561
562 return actualCoverage;
563 }
564`, {
565 preserveComments: true
566}); // the rewire plugin (and potentially other babel middleware)
567// may cause files to be instrumented twice, see:
568// https://github.com/istanbuljs/babel-plugin-istanbul/issues/94
569// we should only instrument code for coverage the first time
570// it's run through istanbul-lib-instrument.
571
572function alreadyInstrumented(path, visitState) {
573 return path.scope.hasBinding(visitState.varName);
574}
575
576function shouldIgnoreFile(programNode) {
577 return programNode.parent && programNode.parent.comments.some(c => COMMENT_FILE_RE.test(c.value));
578}
579/**
580 * programVisitor is a `babel` adaptor for instrumentation.
581 * It returns an object with two methods `enter` and `exit`.
582 * These should be assigned to or called from `Program` entry and exit functions
583 * in a babel visitor.
584 * These functions do not make assumptions about the state set by Babel and thus
585 * can be used in a context other than a Babel plugin.
586 *
587 * The exit function returns an object that currently has the following keys:
588 *
589 * `fileCoverage` - the file coverage object created for the source file.
590 * `sourceMappingURL` - any source mapping URL found when processing the file.
591 *
592 * @param {Object} types - an instance of babel-types
593 * @param {string} sourceFilePath - the path to source file
594 * @param {Object} opts - additional options
595 * @param {string} [opts.coverageVariable=__coverage__] the global coverage variable name.
596 * @param {string} [opts.coverageGlobalScope=this] the global coverage variable scope.
597 * @param {boolean} [opts.coverageGlobalScopeFunc=true] use an evaluated function to find coverageGlobalScope.
598 * @param {Array} [opts.ignoreClassMethods=[]] names of methods to ignore by default on classes.
599 * @param {object} [opts.inputSourceMap=undefined] the input source map, that maps the uninstrumented code back to the
600 * original code.
601 */
602
603
604function programVisitor(types, sourceFilePath = 'unknown.js', opts = {}) {
605 const T = types;
606 opts = { ..._schema.defaults.instrumentVisitor,
607 ...opts
608 };
609 const visitState = new VisitState(types, sourceFilePath, opts.inputSourceMap, opts.ignoreClassMethods);
610 return {
611 enter(path) {
612 if (shouldIgnoreFile(path.find(p => p.isProgram()))) {
613 return;
614 }
615
616 if (alreadyInstrumented(path, visitState)) {
617 return;
618 }
619
620 path.traverse(codeVisitor, visitState);
621 },
622
623 exit(path) {
624 if (alreadyInstrumented(path, visitState)) {
625 return;
626 }
627
628 visitState.cov.freeze();
629 const coverageData = visitState.cov.toJSON();
630
631 if (shouldIgnoreFile(path.find(p => p.isProgram()))) {
632 return {
633 fileCoverage: coverageData,
634 sourceMappingURL: visitState.sourceMappingURL
635 };
636 }
637
638 coverageData[_constants.MAGIC_KEY] = _constants.MAGIC_VALUE;
639 const hash = (0, _crypto.createHash)(_constants.SHA).update(JSON.stringify(coverageData)).digest('hex');
640 coverageData.hash = hash;
641 const coverageNode = T.valueToNode(coverageData);
642 delete coverageData[_constants.MAGIC_KEY];
643 delete coverageData.hash;
644 let gvTemplate;
645
646 if (opts.coverageGlobalScopeFunc) {
647 if (path.scope.getBinding('Function')) {
648 gvTemplate = globalTemplateAlteredFunction({
649 GLOBAL_COVERAGE_SCOPE: T.stringLiteral('return ' + opts.coverageGlobalScope)
650 });
651 } else {
652 gvTemplate = globalTemplateFunction({
653 GLOBAL_COVERAGE_SCOPE: T.stringLiteral('return ' + opts.coverageGlobalScope)
654 });
655 }
656 } else {
657 gvTemplate = globalTemplateVariable({
658 GLOBAL_COVERAGE_SCOPE: opts.coverageGlobalScope
659 });
660 }
661
662 const cv = coverageTemplate({
663 GLOBAL_COVERAGE_VAR: T.stringLiteral(opts.coverageVariable),
664 GLOBAL_COVERAGE_TEMPLATE: gvTemplate,
665 COVERAGE_FUNCTION: T.identifier(visitState.varName),
666 PATH: T.stringLiteral(sourceFilePath),
667 INITIAL: coverageNode,
668 HASH: T.stringLiteral(hash)
669 }); // explicitly call this.varName to ensure coverage is always initialized
670
671 path.node.body.unshift(T.expressionStatement(T.callExpression(T.identifier(visitState.varName), [])));
672 path.node.body.unshift(cv);
673 return {
674 fileCoverage: coverageData,
675 sourceMappingURL: visitState.sourceMappingURL
676 };
677 }
678
679 };
680}
681
682var _default = programVisitor;
683exports.default = _default;
Note: See TracBrowser for help on using the repository browser.