aboutsummaryrefslogtreecommitdiff
path: root/node_modules/typhonjs-istanbul-instrument-jspm/src/instrumentIstanbulSystem.js
blob: 754dd928b0a51979bba5b0df6276d420ac05d5da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
'use strict';

import Instrumenter from 'istanbul-lib-instrument';

// Good enough ES6 module detection regex.
// Format detections not designed to be accurate, but to handle the 99% use case.
const s_ESM_REGEX = /(^\s*|[}\);\n]\s*)(import\s*(['"]|(\*\s+as\s+)?[^"'\(\)\n;]+\s*from\s*['"]|\{)|export\s+\*\s+from\s+["']|export\s* (\{|default|function|class|var|const|let|async\s+function))/;

/**
 * Instruments JSPM / SystemJS replacing the `System.translate` hook adding Istanbul instrumentation of loaded source
 * code before deferring the instrumented source for translation. The instrumentation process occurs on the original
 * source code and supports ES5 and ES Modules (ES6+).
 *
 * @param {object}   System - An instance of SystemJS.
 *
 * @param {RegExp}   sourceFilePathRegex - A regex which defines which source files are instrumented; default excludes
 *                                         any sources with file paths that includes `jspm_packages`.
 */
export default function instrumentIstanbulSystem(System, sourceFilePathRegex = /^((?!jspm_packages).)*$/)
{
   /* istanbul ignore if */
   if (typeof System.translate !== 'function')
   {
      throw new TypeError("instrumentIstanbulSystem - 'System' is not an instance of the SystemJS API.");
   }

   /* istanbul ignore if */
   if (!(sourceFilePathRegex instanceof RegExp))
   {
      throw new TypeError("instrumentIstanbulSystem - 'sourceFilePathRegex' is not an instance of RexExp.");
   }

   // Coverage variable created by Istanbul and stored in global variables.
   const coverageVariable = Object.keys(global).filter((key) => { return key.startsWith('$$cov_'); })[0];

   /* istanbul ignore if */
   if (typeof coverageVariable === 'undefined')
   {
      throw new TypeError(
       'instrumentIstanbulSystem - Istanbul coverage variable could not be located in global variables.');
   }

   // ES5 instrumenter
   const instrumenter = Instrumenter.createInstrumenter({ coverageVariable });

   // ES6 / ES Modules instrumenter
   const instrumenterESM = Instrumenter.createInstrumenter({ coverageVariable, esModules: true });

   // Store the original `System.translate` hook.
   const systemTranslate = System.translate;

   // Override SystemJS translate hook to instrument original sources with Istanbul.
   System.translate = (load) =>
   {
      const filePath = load.address.substr(System.baseURL.length);

      // Use `sourceFilePathRegex` to test file path for source instrumentation.
      if (sourceFilePathRegex.test(filePath))
      {
         /* istanbul ignore next */
         try
         {
            // If a source file passes the ES6 / ES Module regex test then use the ESM instrumenter.
            if (s_ESM_REGEX.test(load.source) || load.metadata.format === 'esm')
            {
               load.source = instrumenterESM.instrumentSync(load.source, filePath);
            }
            else
            {
               load.source = instrumenter.instrumentSync(load.source, filePath);
            }
         }
         catch (err)
         {
            const newErr = new Error(`Unable to instrument '${load.name}' for Istanbul:\n\t${err.message}`);
            newErr.stack = `Unable to instrument '${load.name}' for istanbul:\n\t${err.stack}`;
            newErr.originalErr = err.originalErr || err;
            throw newErr;
         }
      }

      // Defer to the original `System.translate` hook.
      return systemTranslate.call(System, load);
   };
}