diff --git a/example-output.md b/example-output.md index 7dee3ec..37297bf 100644 --- a/example-output.md +++ b/example-output.md @@ -7,32 +7,21 @@ t.throws(foo()) becomes: ```js -function _avaThrowsHelper(fn, data) { - try { - return fn(); - } catch (e) { - var type = typeof e; - - if (e !== null && (type === "object" || type === "function")) { - try { - Object.defineProperty(e, "_avaThrowsHelperData", { - value: data - }); - } catch (e) {} - } - - throw e; +function _avaThrowsHelperStart(t, assertion, file, line) { + if (t._throwsArgStart) { + t._throwsArgStart(assertion, file, line); } } -t.throws(_avaThrowsHelper(function () { - return foo(); -}, { - line: 1, - column: 9, - source: "foo()", - filename: "some-file.js" -})); +function _avaThrowsHelperEnd(t, arg) { + if (t._throwsArgEnd) { + t._throwsArgEnd(); + } + + return arg; +} + +t.throws((_avaThrowsHelperStart(t, "throws", "some-file.js", 1), _avaThrowsHelperEnd(t, foo()))); ``` --- @@ -47,40 +36,22 @@ t.throws(bar()); becomes: ```js -function _avaThrowsHelper(fn, data) { - try { - return fn(); - } catch (e) { - var type = typeof e; - - if (e !== null && (type === "object" || type === "function")) { - try { - Object.defineProperty(e, "_avaThrowsHelperData", { - value: data - }); - } catch (e) {} - } - - throw e; +function _avaThrowsHelperStart(t, assertion, file, line) { + if (t._throwsArgStart) { + t._throwsArgStart(assertion, file, line); + } +} + +function _avaThrowsHelperEnd(t, arg) { + if (t._throwsArgEnd) { + t._throwsArgEnd(); } + + return arg; } -t.throws(_avaThrowsHelper(function () { - return foo(); -}, { - line: 1, - column: 9, - source: "foo()", - filename: "some-file.js" -})); -t.throws(_avaThrowsHelper(function () { - return bar(); -}, { - line: 2, - column: 9, - source: "bar()", - filename: "some-file.js" -})); +t.throws((_avaThrowsHelperStart(t, "throws", "some-file.js", 1), _avaThrowsHelperEnd(t, foo()))); +t.throws((_avaThrowsHelperStart(t, "throws", "some-file.js", 2), _avaThrowsHelperEnd(t, bar()))); ``` --- @@ -108,32 +79,21 @@ t.notThrows(baz()) becomes: ```js -function _avaThrowsHelper(fn, data) { - try { - return fn(); - } catch (e) { - var type = typeof e; - - if (e !== null && (type === "object" || type === "function")) { - try { - Object.defineProperty(e, "_avaThrowsHelperData", { - value: data - }); - } catch (e) {} - } - - throw e; +function _avaThrowsHelperStart(t, assertion, file, line) { + if (t._throwsArgStart) { + t._throwsArgStart(assertion, file, line); + } +} + +function _avaThrowsHelperEnd(t, arg) { + if (t._throwsArgEnd) { + t._throwsArgEnd(); } + + return arg; } -t.notThrows(_avaThrowsHelper(function () { - return baz(); -}, { - line: 1, - column: 12, - source: "baz()", - filename: "some-file.js" -})); +t.notThrows((_avaThrowsHelperStart(t, "notThrows", "some-file.js", 1), _avaThrowsHelperEnd(t, baz()))); ``` --- diff --git a/index.js b/index.js index 17c2fdb..e370480 100644 --- a/index.js +++ b/index.js @@ -1,74 +1,66 @@ 'use strict'; -var t = require('babel-types'); -var template = require('babel-template'); -var wrapWithHelper = template([ - 'HELPER_ID(function () {', - ' return EXP;', - '}, {', - ' line: LINE,', - ' column: COLUMN,', - ' source: SOURCE,', - ' filename: FILE', - '});' -].join('\n')); +module.exports = babelCore => { + const t = babelCore.types; + const wrapArg = babelCore.template('(START(t, ASSERTION, FILE, LINE), END(t, ARG))'); + const helpers = babelCore.template(`function START(t, assertion, file, line) { + if (t._throwsArgStart) { + t._throwsArgStart(assertion, file, line); + } +} + +function END(t, arg) { + if (t._throwsArgEnd) { + t._throwsArgEnd(); + } -var buildHelper = template([ - 'function HELPER_ID(fn, data) {', - ' try {', - ' return fn();', - ' } catch (e) {', - ' var type = typeof e;', - ' if (e !== null && (type === "object" || type === "function")) {', - ' try {', - ' Object.defineProperty(e, "_avaThrowsHelperData", {', - ' value: data', - ' });', - ' } catch (e) {}', - ' }', - ' throw e;', - ' }', - '}' -].join('\n')); + return arg; +}`); -var assertionVisitor = { - CallExpression: function (path, state) { - if (isThrowsMember(path.get('callee'))) { - var arg0 = path.node.arguments[0]; + const assertionVisitor = { + CallExpression(path, state) { + const callee = path.get('callee'); + if (!callee.isMemberExpression() || !callee.get('object').isIdentifier({name: 't'}) || !callee.get('property').isIdentifier()) { + return; + } + const assertion = callee.get('property').get('name').node; + if (assertion !== 'throws' && assertion !== 'notThrows') { + return; + } + + const arg0 = path.node.arguments[0]; if (!(arg0 && arg0.loc && (typeof arg0.start === 'number') && (typeof arg0.end === 'number'))) { return; } - path.node.arguments[0] = wrapWithHelper({ - HELPER_ID: t.identifier(this.avaThrowHelper()), - EXP: arg0, - LINE: t.numericLiteral(arg0.loc.start.line), - COLUMN: t.numericLiteral(arg0.loc.start.column), - SOURCE: t.stringLiteral(state.file.code.substring(arg0.start, arg0.end)), - FILE: t.stringLiteral(state.file.opts.filename) - }).expression; + // Wrap the argument expression, so that the stack trace of the assertion + // isn't affected. + path.node.arguments[0] = wrapArg(Object.assign({ + ARG: arg0, + ASSERTION: t.stringLiteral(assertion), + FILE: t.stringLiteral(state.file.opts.filename), + LINE: t.numericLiteral(arg0.loc.start.line) + }, this.installHelper())).expression; } - } -}; + }; -module.exports = function () { return { visitor: { - Program: function (path, state) { - var HELPER_ID = path.scope.generateUid('avaThrowsHelper'); - var created = false; + Program(path, state) { + const START = t.identifier(path.scope.generateUid('avaThrowsHelperStart')); + const END = t.identifier(path.scope.generateUid('avaThrowsHelperEnd')); + const helperIdentifiers = {START, END}; + let created = false; path.traverse(assertionVisitor, { - avaThrowHelper: function () { + installHelper() { if (!created) { created = true; - path.unshiftContainer('body', buildHelper({ - HELPER_ID: t.identifier(HELPER_ID) - })); + path.unshiftContainer('body', helpers(helperIdentifiers)); } - return HELPER_ID; + return helperIdentifiers; }, file: state.file }); @@ -76,10 +68,3 @@ module.exports = function () { } }; }; - -function isThrowsMember(path) { - return path.isMemberExpression() && path.get('object').isIdentifier({name: 't'}) && ( - path.get('property').isIdentifier({name: 'throws'}) || - path.get('property').isIdentifier({name: 'notThrows'}) - ); -} diff --git a/package.json b/package.json index 16ae145..a01c7bb 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,8 @@ "assertion", "throws" ], - "dependencies": { - "babel-template": "^6.7.0", - "babel-types": "^6.7.2" - }, "devDependencies": { - "ava": "^0.17.0", + "ava": "^0.18.2", "babel-core": "^6.7.5", "xo": "^0.17.0" } diff --git a/test.js b/test.js index b975040..0703f9f 100644 --- a/test.js +++ b/test.js @@ -2,7 +2,6 @@ import path from 'path'; import fs from 'fs'; import {serial as test} from 'ava'; import * as babel from 'babel-core'; -import * as types from 'babel-types'; import fn from './'; function transform(input) { @@ -12,7 +11,7 @@ function transform(input) { }); } -var examples = []; +const examples = []; function addExample(input, output) { examples.push( @@ -33,45 +32,29 @@ function addExample(input, output) { ); } -const HELPER = `function _avaThrowsHelper(fn, data) { - try { - return fn(); - } catch (e) { - var type = typeof e; - - if (e !== null && (type === "object" || type === "function")) { - try { - Object.defineProperty(e, "_avaThrowsHelperData", { - value: data - }); - } catch (e) {} - } - - throw e; +const HELPERS = `function _avaThrowsHelperStart(t, assertion, file, line) { + if (t._throwsArgStart) { + t._throwsArgStart(assertion, file, line); } -}\n`; - -function wrapped(throws, expression, line, column) { - return `t.${throws}(_avaThrowsHelper(function () { - return ${expression}; -}, { - line: ${line}, - column: ${column}, - source: "${expression}", - filename: "some-file.js" -}));`; +} + +function _avaThrowsHelperEnd(t, arg) { + if (t._throwsArgEnd) { + t._throwsArgEnd(); + } + + return arg; +}\n\n`; + +function wrapped(throws, expression, line) { + return `t.${throws}((_avaThrowsHelperStart(t, "${throws}", "some-file.js", ${line}), _avaThrowsHelperEnd(t, ${expression})));`; } test('creates a helper', t => { const input = 't.throws(foo())'; const {code} = transform(input); - const expected = [ - HELPER, - wrapped('throws', 'foo()', 1, 9) - ].join('\n'); - - t.is(code, expected); + t.is(code, HELPERS + wrapped('throws', 'foo()', 1)); addExample(input, code); }); @@ -79,13 +62,7 @@ test('creates the helper only once', t => { const input = 't.throws(foo());\nt.throws(bar());'; const {code} = transform(input); - const expected = [ - HELPER, - wrapped('throws', 'foo()', 1, 9), - wrapped('throws', 'bar()', 2, 9) - ].join('\n'); - - t.is(code, expected); + t.is(code, HELPERS + wrapped('throws', 'foo()', 1) + '\n' + wrapped('throws', 'bar()', 2)); addExample(input, code); }); @@ -101,28 +78,23 @@ test('helps notThrows', t => { const input = 't.notThrows(baz())'; const {code} = transform(input); - const expected = [ - HELPER, - wrapped('notThrows', 'baz()', 1, 12) - ].join('\n'); - - t.is(code, expected); + t.is(code, HELPERS + wrapped('notThrows', 'baz()', 1)); addExample(input, code); }); test('does not throw on generated code', () => { - var statement = types.expressionStatement(types.callExpression( - types.memberExpression( - types.identifier('t'), - types.identifier('throws') + const statement = babel.types.expressionStatement(babel.types.callExpression( + babel.types.memberExpression( + babel.types.identifier('t'), + babel.types.identifier('throws') ), - [types.callExpression( - types.identifier('foo'), + [babel.types.callExpression( + babel.types.identifier('foo'), [] )] )); - var program = types.program([statement]); + const program = babel.types.program([statement]); babel.transformFromAst(program, null, { plugins: [fn],