avajs / babel-plugin-throws-helper

Babel plugin for protecting against improper use of `t.throws()` in AVA

Home Page:https://ava.li

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ready for review

jamestalmage opened this issue · comments

You can see sample transformations here. These examples are generated right in the tests, so you can check those out as well.

I think this is sufficient to generate good error messages.

Basically, if an Error surfaces that has a _avaTryCatchHelperData property on it, it was generated because they threw incorrectly.

Thoughts?

@novemberborn @sindresorhus @jfmengels @vdemedes

_avaTryCatchHelperData is a terrible name. Need to fix that.

Is there a need to check whether the file imports AVA, or does AVA handle that already? I see that it will replace stuff even without the import.

Here's an ASTexplorer to play with: https://astexplorer.net/#/fHekV5fC13

Will look more into this later ;)

Is there a need to check whether the file imports AVA

I don't think so. The wrapping should be entirely transparent no matter if it's an AVA assertion or not. You would only notice if you iterated over the properties on the emitted errors. Perhaps I should make the _avaTryCatchHelperData property non-enumerable, then it's really unlikely to cause a problem.

Assigning e._avaTryCatchHelperData = data may blow up if null or undefined were thrown, and be useless if a literal was thrown. Why not construct an AvaError in the helper function?

The babel-template examples in the plugin handbook construct the template outside of the plugin export. I assume that might be ever so slightly more performant? Same for the assertionVisitor.

Assigning e._avaTryCatchHelperData = data may blow up if null or undefined were thrown, and be useless if a literal was thrown

Good point. I should protect against that.

Why not construct an AvaError in the helper function?

I was trying to make it completely transparent (not change what is thrown). It is a lot safer not to wrap it. It is not likely to affect anyone (but it would be crazy confusing for that one person that was affected).

Also - if test.run() throws a non-Error, we should be printing a helpful message that explains they should be throwing Errors.

The babel-template examples in the plugin handbook construct the template outside of the plugin export. I assume that might be ever so slightly more performant? Same for the assertionVisitor.

Maybe? Still, I can't imagine it would be noticeable, even with hundreds of files. The current way keeps the dependency list in package.json down.

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'));

Since the content describes a simple function, would it make sense to define it as a simple function, and call toString() on it (then pass it to template)?

You'd gain readability (IMO), linting, etc. It's unfortunately harder to do for the wrapWithHelper as you'd get "unknown variable" linting errors.

Also - if test.run() throws a non-Error, we should be printing a helpful message that explains they should be throwing Errors.

That's for t.throws to handle IMO.

Maybe? Still, I can't imagine it would be noticeable, even with hundreds of files. The current way keeps the dependency list in package.json down.

Yea I dunno. It's the example that's given :) Dependency wise it shouldn't matter assuming npm@3 but at least this way you won't have to update the package much.

That's for t.throws to handle IMO.

I'm talking about if they throw synchronously without catching via t.throws (which is an obvious mistake):

test(t => {
  throw 'foo'; // oops!
});

In addition to reporting the throw as we currently do. It should really be telling them "you need to throw Errors".

@jamestalmage ah sorry I read test.run() as t.throws().