diff --git a/lib/instrumenter.js b/lib/instrumenter.js index dc04e3f0..6d6efbaf 100644 --- a/lib/instrumenter.js +++ b/lib/instrumenter.js @@ -402,6 +402,7 @@ this.walker = new Walker({ ArrowFunctionExpression: [ this.arrowBlockConverter ], ExpressionStatement: this.coverStatement, + ExportNamedDeclaration: this.coverExport, BreakStatement: this.coverStatement, ContinueStatement: this.coverStatement, DebuggerStatement: this.coverStatement, @@ -800,6 +801,7 @@ coverStatement: function (node, walker) { var sName, incrStatementCount, + parent, grandParent; this.maybeSkipNode(node, 'next'); @@ -815,10 +817,19 @@ } } } + if (node.type === SYNTAX.FunctionDeclaration.name) { - sName = this.statementName(node.loc, 1); + // Called for the side-effect of setting the function's statement count to 1. + this.statementName(node.loc, 1); } else { + // We let `coverExport` handle ExportNamedDeclarations. + parent = walker.parent(); + if (parent && parent.node.type === SYNTAX.ExportNamedDeclaration.name) { + return; + } + sName = this.statementName(node.loc); + incrStatementCount = astgen.statement( astgen.postIncrement( astgen.subscript( @@ -827,10 +838,31 @@ ) ) ); + this.splice(incrStatementCount, node, walker); } }, + coverExport: function (node, walker) { + var sName, incrStatementCount; + + if ( !node.declaration || !node.declaration.declarations ) { return; } + + this.maybeSkipNode(node, 'next'); + + sName = this.statementName(node.declaration.loc); + incrStatementCount = astgen.statement( + astgen.postIncrement( + astgen.subscript( + astgen.dot(astgen.variable(this.currentState.trackerVar), astgen.variable('s')), + astgen.stringLiteral(sName) + ) + ) + ); + + this.splice(incrStatementCount, node, walker); + }, + splice: function (statements, node, walker) { var targetNode = walker.isLabeled() ? walker.parent().node : node; targetNode.prepend = targetNode.prepend || []; @@ -1058,4 +1090,3 @@ } }(typeof module !== 'undefined' && typeof module.exports !== 'undefined' && typeof exports !== 'undefined')); - diff --git a/test/es6.js b/test/es6.js index ec7a63fb..e47c7fec 100644 --- a/test/es6.js +++ b/test/es6.js @@ -9,8 +9,14 @@ function tryThis(str, feature) { return false; } + // esprima parses sources with sourceType 'script' per default. + // The only way to enable `import`/`export` is to parse as sourceType 'module'. try { - esprima.parse(str); + try { + esprima.parse(str); + } catch (ex) { + esprima.parse(str, { sourceType: 'module' }); + } } catch (ex) { console.error('ES6 feature [' + feature + '] is not yet supported by esprima mainline'); return false; @@ -42,6 +48,7 @@ module.exports = { }, isExportAvailable: function () { - return tryThis('export default function foo() {}', 'export'); + // We can test instrumentation of exports even if the environment doesn't support them. + return true; } }; diff --git a/test/helper.js b/test/helper.js index b6ee52ec..59678417 100644 --- a/test/helper.js +++ b/test/helper.js @@ -104,6 +104,13 @@ function setup(file, codeArray, opts) { } return; } + + // `export`/`import` cannot be wrapped inside a function. + // For our purposes, simply remove the `export` from export declarations. + if ( opts.esModules ) { + generated = generated.replace(/export (var|function|let|const)/g, '$1'); + } + var wrappedCode = '(function (args) { var output;\n' + generated + '\nreturn output;\n})', fn; global[coverageVariable] = undefined; diff --git a/test/instrumentation/test-es6-export.js b/test/instrumentation/test-es6-export.js index 4ecefd9d..0efebbec 100644 --- a/test/instrumentation/test-es6-export.js +++ b/test/instrumentation/test-es6-export.js @@ -21,6 +21,24 @@ if (require('../es6').isExportAvailable()) { statements: {'1': 1, '2': 1, '3': 1} }); test.done(); + }, + + 'should cover export declarations': function (test) { + code = [ + 'export var a = 2, b = 3;', + 'output = a + b' + ]; + verifier = helper.verifier(__filename, code, { + esModules: true, + noAutoWrap: true + }); + verifier.verify(test, [], 5, { + lines: {'1':1, '2': 1}, + branches: {}, + functions: {}, + statements: {'1': 1, '2': 1} + }); + test.done(); } }; }