Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Point out when a plugin needs to be enabled for encountered syntax (#169) #178

Closed
wants to merge 11 commits into from

Conversation

motiz88
Copy link
Contributor

@motiz88 motiz88 commented Oct 14, 2016

Ref #169
This mainly replaces the if (!this.hasPlugin("foo")) this.unexpected(); idiom with a new parser helper method, expectPlugin.

Right now the error is worded like this: This experimental syntax requires the foo parser plugin. Check out babel-plugin-syntax-foo, babel-plugin-transform-foo or the parserOpts.foo Babel option. Open for different suggestions of course.

Another idiom we may want to change is where we currently check for things like this.hasPlugin("asyncGenerators") && this.eat(tt.star). Does it make sense to flip the order in some places? Match the special token (here *) without advancing, then assert that we have the plugin, then advance. In cases where we can be sure of the user's intent this looks like a usability win.

@motiz88
Copy link
Contributor Author

motiz88 commented Oct 15, 2016

@codecov-io
Copy link

codecov-io commented Oct 15, 2016

Codecov Report

❗ No coverage uploaded for pull request base (master@57aacea). Click here to learn what that means.
The diff coverage is 77.77%.

@@            Coverage Diff            @@
##             master     #178   +/-   ##
=========================================
  Coverage          ?   96.28%           
=========================================
  Files             ?       20           
  Lines             ?     3205           
  Branches          ?      836           
=========================================
  Hits              ?     3086           
  Misses            ?       61           
  Partials          ?       58
Impacted Files Coverage Δ
src/parser/statement.js 97.1% <100%> (ø)
src/plugins/directory.js 100% <100%> (ø)
src/parser/expression.js 92.81% <52%> (ø)
src/parser/util.js 88.63% <84.61%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 57aacea...880fbdd. Read the comment docs.

# Conflicts:
#	test/fixtures/experimental/no-async-generators/error-without-plugin/options.json
@motiz88
Copy link
Contributor Author

motiz88 commented Oct 16, 2016

#182 will probably take care of most of the gap in code coverage

@motiz88
Copy link
Contributor Author

motiz88 commented Oct 17, 2016

Thanks @danez! Do you have an opinion on this.hasPlugin("asyncGenerators") && this.eat(tt.star) ➡️ this.match(tt.star) && this.expectPlugin("asyncGenerators"), this.next() etc?

@danez
Copy link
Member

danez commented Oct 17, 2016

I think this would also be the only way to do it in this cases right? Because otherwise doing this.eat() && this.expectPlugin()would change the location of the error.
So yes that sounds good

@motiz88
Copy link
Contributor Author

motiz88 commented Oct 17, 2016

Yeah. The thing to watch out for, if we eagerly match() && expectPlugin(), is cases where there is an alternative correct parse we can't rule out yet. Async generators are not generally such a case - after async function, if we see * it's either an async generator (with the plugin) or an error (under any other parser config), so it's a safe bet the user meant it as an async generator.

@danez
Copy link
Member

danez commented Oct 17, 2016

True, so probably we need to decide for each occurrence separately.

@motiz88
Copy link
Contributor Author

motiz88 commented Oct 17, 2016

OK, I'll just add some of the easy cases here real quick.

this.state.inFunction = oldInFunction;
this.state.labels = oldLabels;
return this.finishNode(node, "DoExpression");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a fall-through here before without doExpression, but that looks like a mistake anyway. Why should do fall-through to regexp?

this.expectPlugin("functionSent");
} else if (!this.hasPlugin("functionSent")) {
// They didn't actually say `function.sent`, just `function.`, so a simple error would be less confusing.
this.unexpected();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uncovered. We could test this I think easily.

if (this.hasPlugin("asyncGenerators") && this.state.inAsync && this.isContextual("await")) {
forAwait = true;
this.next();
if (this.isContextual("await") && this.state.inAsync) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe switch the order here like it was before, checking the bool value first is maybe also faster in case it is false?
Or did you have something in mind by changing the order.

if (this.hasPlugin("asyncGenerators") && this.eat(tt.star)) isGenerator = true;
if (this.match(tt.star)) {
this.expectPlugin("asyncGenerators");
this.next();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this pattern quite often if (match) expectPlugin(), next() So maybe we could make this easier in future by doing:

if (this.expectPluginIfMatch("plugin", tt.start)) {
 is = true
}

What do you think?
But should do that anyway later, to not make this PR even more bigger.

if (pluginInfo.babelSyntaxPlugin) {
pointers.push(`babel-plugin-${pluginInfo.babelSyntaxPlugin}`);
}
if (pluginInfo.babelTransformPlugins) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make babelTransformPlugins mandatory to be an array, so we don't have to check here?
And also as long as we have syntax-plugins for everything, maybe also remove the if for it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we do have syntax plugins for every transform (otherwise we would just remove the syntax plugins entirely)


export const asyncGenerators = {
babelSyntaxPlugin: "syntax-async-generators",
babelTransformPlugins: ["transform-regenerator"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should recommend the new transform, transform-async-generators


export const classConstructorCall = {
babelSyntaxPlugin: "syntax-class-constructor-call",
babelTransformPlugins: ["transform-class-constructor-call"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is deprecated so can remove

babelTransformPlugins: ["transform-decorators"]
};

// export const dynamicImport = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be added back


export const decorators = {
babelSyntaxPlugin: "syntax-decorators",
babelTransformPlugins: ["transform-decorators"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess recommend transform-legacy atm?

const leadingPointersOr = pointers.length ? ` ${pointers.join(", ")} or ` : " ";

this.unexpected(null, `This experimental syntax requires the ${name} parser plugin. `
+ `Check out${ leadingPointersOr }the parserOpts.${name} Babel option.`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's an array so the wording is off (maybe we shouldn't recommend that option).

{
  parserOpts: {
    plugins: []
  }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should change it so that Babel users will use the transform plugin, and babylon users the syntax, and for Babel users not to use both.

@motiz88
Copy link
Contributor Author

motiz88 commented Dec 16, 2016 via email

@motiz88
Copy link
Contributor Author

motiz88 commented Feb 14, 2017

@hzoo I saw your comment on #300 mentioning this feature. Totally willing to pick this up again. Can you please confirm that the approach I outlined above is how you'd prefer this to be handled? Or do you have something else in mind?

@hzoo
Copy link
Member

hzoo commented Feb 14, 2017

Hmm yeah it would be interesting to just expose metadata that says the babylon plugin that could be applied to not error.

2 users of babylon: either a tool or a Babel user. I would focus on the Babel user since the tool user would be more likely to understand how to enable a plugin anyway?

@motiz88
Copy link
Contributor Author

motiz88 commented Feb 14, 2017

Btw just resolved the obvious conflicts for now. Will do a more complete update soon.

@@ -0,0 +1,59 @@
export const classProperties = {
Copy link
Member

@hzoo hzoo Feb 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this again...

most if not all of the plugins have a corresponding babel plugin that is just the same name.

objectRestSpread -> transform-object-rest-spread
asyncGenerators -> transform-async-generators

the only 2 that don't do that have a corresponding preset as well now

flow -> preset-flow (strip-flow-types)
react -> preset-react (react-jsx)

So we could do this automatically + 2 exceptions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most is right. There's also the question of transform vs syntax (even if we ignore the rarer preset and ...-legacy etc) which is still an editorial decision basically

Copy link
Member

@hzoo hzoo Feb 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-legacy will be removed (there's a pr to make it the regular one) babel/babel#5283

and ^ I said we always have a transform/syntax version so far at least

Copy link
Contributor Author

@motiz88 motiz88 Feb 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, sorry, I was commenting with just a fuzzy recollection of how my own PR worked 🤦‍♂️ . So transform vs syntax is accounted for, by way of both being mentioned in the message.

And now I see your edit above. So yeah, that's a way to encode the same information less explicitly. However I would still kinda prefer to move this out of Babylon if possible, as discussed below.

@motiz88
Copy link
Contributor Author

motiz88 commented Feb 14, 2017

@hzoo I read "focus on the Babel user" to mean a relaxing of your suggestion above that

We should change it so that Babel users will use the transform plugin, and babylon users the syntax, and for Babel users not to use both.

So that - showing only the Babel oriented message - would be one simple way to slice it, albeit one that sacrifices some "purity" I guess by coding knowledge about Babel plugins into Babylon (funnily enough, I would be more comfortable with this philosophically if Babylon was in the Babel monorepo... Ooh can we get that in 7.0? :half_joking:)

@hzoo
Copy link
Member

hzoo commented Feb 14, 2017

(funnily enough, I would be more comfortable with this philosophically if Babylon was in the Babel monorepo... Ooh can we get that in 7.0? :half_joking:)

Yeah I've wanted that for a while now (doesn't have to be in 7.0 either but based on time probably so unless someone really wants to do it).

Although I guess it would be a weird error message if the user isn't using Babel but Babylon itself

@motiz88
Copy link
Contributor Author

motiz88 commented Feb 14, 2017

it would be a weird error message if the user isn't using Babel but Babylon itself

Yep, that's what I'm least happy about here. I totally get the "focus on Babel users" argument, but Babylon is its own package too and I don't love the idea of making that kind of a second-class story. It's a subtle point though.

To expand on the alternative: It's more work, but if we make the "plugin required" exception positively identifiable to Babel 1, e.g. via a custom property on the thrown object 2, then we can both

  1. put the Babylon option name in the Babylon-thrown message, but then catch it in Babel 3 and rethrow with the appropriate Babel plugin name;
    and
  2. remove knowledge from Babylon about which Babel plugins exist/are recommended for each syntax feature. ( & move into babel-core)

1: Or anyone invoking Babylon - i.e this becomes public API.
2: Or subclassing Error but fragile; or bringing in VError but probably overkill; or parsing the actual message prop but eww.
3: In basically just one place, right? babel-core somewhere.

this.unexpected(null, `This experimental syntax requires the ${name} parser plugin. `
+ `Check out${ leadingPointersOr }the parserOpts.${name} Babel option.`);
}
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to return from this given that the alternative is to throw? Then we could reduce the nesting by just doing if (this.hasPlugin(name)) return; at the top of the function.

));
}
}
const leadingPointersOr = pointers.length ? ` ${pointers.join(", ")} or ` : " ";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would people feel about skipping this pointers logic and having babylon just throw an error with the plugin name in the message, and a .missingPlugin property?

They we can have the try/catch in babel-core catch the exception and handle mapping the error to a Babel plugin? That way we don't need Babylon to be aware of Babel stuff.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that was my thoughts dono if it was in the notes. Babylon would just throw and babel-core would pick that up and throw the real error message since you might be using babylon for something else (prettier, babel-eslint) that is unrelated to using a plugin

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants