Repository: istanbuljs/istanbuljs (JavaScript)

Issue:
hookRunInThisContext doesn't use current runInThisContext API
#The `vm.runInThisContext` hook assumes the second argument to `runInThisContext` [is a string](https://github.com/istanbuljs/istanbuljs/blob/master/packages/istanbul-lib-hook/lib/hook.js#L139). However, the argument has been an options object for quite a while now (I believe since Node 0.10 or 0.12). This prevents Istanbul from instrumenting modules instantiated by calling `Module#_compile` (or by calling `runInThisContext` using the proper API).


Discussion:
@jason0x43 could you perform a test with the `next` version
istanbuljs/nyc#902
also this issue may be concerned theintern/intern#912



Patch:
diff --git a/packages/istanbul-api/lib/run-cover.js b/packages/istanbul-api/lib/run-cover.js
index f61e18e3..5e5f54d3 100644
--- a/packages/istanbul-api/lib/run-cover.js
+++ b/packages/istanbul-api/lib/run-cover.js
@@ -5,6 +5,7 @@
 var path = require('path'),
     fs = require('fs'),
     mkdirp = require('mkdirp'),
+    compareVersions = require('compare-versions'),
     matcherFor = require('./file-matcher').matcherFor,
     libInstrument = require('istanbul-lib-instrument'),
     libCoverage = require('istanbul-lib-coverage'),
@@ -33,6 +34,8 @@ function getCoverFunctions(config, includes, callback) {
         sourceMapStore = libSourceMaps.createSourceMapStore({}),
         instrumenter,
         transformer,
+        runInContextTransformer,
+        runInThisContextTransformer,
         fakeRequire,
         requireTransformer,
         reportInitFn,
@@ -51,18 +54,26 @@ function getCoverFunctions(config, includes, callback) {
         return global[coverageVar];
     };
     instrumenter = libInstrument.createInstrumenter(instOpts);
-    transformer = function (code, file) {
-        return instrumenter.instrumentSync(code, file);
+    transformer = function (code, options) {
+      var filename = typeof options === 'string' ? options : options.filename;
+      return instrumenter.instrumentSync(code, filename);
     };
-    requireTransformer = function (code, file) {
-        var cov,
-            ret = transformer(code, file);
-        if (fakeRequire) {
-            cov = coverageFinderFn();
-            cov[file] = instrumenter.lastFileCoverage();
-            return 'function x() {}';
-        }
-        return ret;
+    runInContextTransformer = function (code, options) {
+      return transformer(code, options);
+    };
+    runInThisContextTransformer = function (code, options) {
+      return transformer(code, options);
+    };
+    requireTransformer = function (code, options) {
+      var cov,
+        ret = transformer(code, options),
+        filename = typeof options === 'string' ? options : options.filename;
+      if (fakeRequire) {
+        cov = coverageFinderFn();
+        cov[filename] = instrumenter.lastFileCoverage();
+        return 'function x() {}';
+      }
+      return ret;
     };
 
     coverageSetterFn = function (cov) {
@@ -102,12 +113,16 @@ function getCoverFunctions(config, includes, callback) {
         reportInitFn();
 
         if (config.hooks.hookRunInContext()) {
-            hook.hookRunInContext(matchFn, transformer, hookOpts);
+            hook.hookRunInContext(matchFn, runInContextTransformer, hookOpts);
         }
         if (config.hooks.hookRunInThisContext()) {
-            hook.hookRunInThisContext(matchFn, transformer, hookOpts);
+            hook.hookRunInThisContext(matchFn, runInThisContextTransformer, hookOpts);
+            if(compareVersions(process.versions.node, "6.0.0") === -1) {
+              disabler = hook.hookRequire(matchFn, requireTransformer, hookOpts);
+            }
+        } else {
+            disabler = hook.hookRequire(matchFn, requireTransformer, hookOpts);
         }
-        disabler = hook.hookRequire(matchFn, requireTransformer, hookOpts);
     };
 
     unhookFn = function (matchFn) {
diff --git a/packages/istanbul-api/package.json b/packages/istanbul-api/package.json
index b0956833..fbe4342b 100644
--- a/packages/istanbul-api/package.json
+++ b/packages/istanbul-api/package.json
@@ -36,6 +36,7 @@
   },
   "dependencies": {
     "async": "^2.1.4",
+    "compare-versions": "^3.1.0",
     "fileset": "^2.0.2",
     "istanbul-lib-coverage": "^1.1.2",
     "istanbul-lib-hook": "^1.1.0",
diff --git a/packages/istanbul-lib-hook/lib/hook.js b/packages/istanbul-lib-hook/lib/hook.js
index df3162fa..cba40fd2 100644
--- a/packages/istanbul-lib-hook/lib/hook.js
+++ b/packages/istanbul-lib-hook/lib/hook.js
@@ -11,20 +11,25 @@ var path = require('path'),
 
 function transformFn(matcher, transformer, verbose) {
 
-    return function (code, filename) {
-        var shouldHook = typeof filename === 'string' && matcher(path.resolve(filename)),
+    return function (code, options) {
+        options = options || {};
+        if (typeof options === 'string') {
+            options = { filename: options };
+        }
+
+        var shouldHook = typeof options.filename === 'string' && matcher(path.resolve(options.filename)),
             transformed,
             changed = false;
 
         if (shouldHook) {
             if (verbose) {
-                console.error('Module load hook: transform [' + filename + ']');
+                console.error('Module load hook: transform [' + options.filename + ']');
             }
             try {
-                transformed = transformer(code, filename);
+                transformed = transformer(code, options);
                 changed = true;
             } catch (ex) {
-                console.error('Transformation error for', filename, '; return original code');
+                console.error('Transformation error for', options.filename, '; return original code');
                 console.error(ex.message || String(ex));
                 if (verbose) {
                     console.error(ex.stack);
@@ -127,19 +132,19 @@ function unhookCreateScript() {
  * hooks `vm.runInThisContext` to return transformed code.
  * @method hookRunInThisContext
  * @static
- * @param matcher {Function(filePath)} a function that is called with the filename passed to `vm.createScript`
+ * @param matcher {Function(filePath)} a function that is called with the filename passed to `vm.runInThisContext`
  *  Should return a truthy value when transformations need to be applied to the code, a falsy value otherwise
- * @param transformer {Function(code, filePath)} a function called with the original code and the filename passed to
- *  `vm.createScript`. Should return the transformed code.
+ * @param transformer {Function(code, options)} a function called with the original code and the filename passed to
+ *  `vm.runInThisContext`. Should return the transformed code.
  * @param opts {Object} [opts={}] options
  * @param {Boolean} [opts.verbose] write a line to standard error every time the transformer is called
  */
 function hookRunInThisContext(matcher, transformer, opts) {
     opts = opts || {};
     var fn = transformFn(matcher, transformer, opts.verbose);
-    vm.runInThisContext = function (code, file) {
-        var ret = fn(code, file);
-        return originalRunInThisContext(ret.code, file);
+    vm.runInThisContext = function (code, options) {
+        var ret = fn(code, options);
+        return originalRunInThisContext(ret.code, options);
     };
 }
 /**


Test patch:
diff --git a/packages/istanbul-api/test/run-check-coverage.test.js b/packages/istanbul-api/test/run-check-coverage.test.js
index a9aa4130..da818e3f 100644
--- a/packages/istanbul-api/test/run-check-coverage.test.js
+++ b/packages/istanbul-api/test/run-check-coverage.test.js
@@ -16,9 +16,6 @@ describe('run check-coverage', function () {
     function getConfig(overrides) {
         var cfg = configuration.loadObject({
             verbose: false,
-            hooks: {
-                'hook-run-in-this-context': true
-            },
             instrumentation: {
                 root: codeRoot,
                 'include-all-sources': true
diff --git a/packages/istanbul-api/test/sample-code/context.js b/packages/istanbul-api/test/sample-code/context.js
index 805c9988..2794bfd1 100644
--- a/packages/istanbul-api/test/sample-code/context.js
+++ b/packages/istanbul-api/test/sample-code/context.js
@@ -4,4 +4,6 @@ var vm = require('vm'),
     file = path.resolve(__dirname, 'foo.js'),
     code = fs.readFileSync(file, 'utf8');
 
-vm.runInThisContext(code, file);
+vm.runInThisContext(code, { 
+    filename: file 
+});
diff --git a/packages/istanbul-lib-hook/test/hook.test.js b/packages/istanbul-lib-hook/test/hook.test.js
index e8d5e75c..5967df29 100644
--- a/packages/istanbul-lib-hook/test/hook.test.js
+++ b/packages/istanbul-lib-hook/test/hook.test.js
@@ -144,10 +144,10 @@ describe('hooks', function () {
         it('transforms foo', function () {
             var s;
             hook.hookRunInThisContext(matcher, scriptTransformer);
-            s = require('vm').runInThisContext('(function () { return 10; }());', '/bar/foo.js');
+            s = require('vm').runInThisContext('(function () { return 10; }());', { filename: '/bar/foo.js' });
             assert.equal(s, 42);
             hook.unhookRunInThisContext();
-            s = require('vm').runInThisContext('(function () { return 10; }());', '/bar/foo.js');
+            s = require('vm').runInThisContext('(function () { return 10; }());', { filename: '/bar/foo.js' });
             assert.equal(s, 10);
         });
         it('does not transform code with no filename', function () {
@@ -155,14 +155,14 @@ describe('hooks', function () {
             hook.hookRunInThisContext(matcher, scriptTransformer);
             s = require('vm').runInThisContext('(function () { return 10; }());');
             assert.equal(s, 10);
-            hook.unhookCreateScript();
+            hook.unhookRunInThisContext();
         });
         it('does not transform code with non-string filename', function () {
             var s;
             hook.hookRunInThisContext(matcher, scriptTransformer);
             s = require('vm').runInThisContext('(function () { return 10; }());', {});
             assert.equal(s, 10);
-            hook.unhookCreateScript();
+            hook.unhookRunInThisContext();
         });
     });
     describe('runInContext', function () {