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

Enable type-checking in src #393

Closed
wants to merge 17 commits into from
Closed

Enable type-checking in src #393

wants to merge 17 commits into from

Conversation

ghost
Copy link

@ghost ghost commented Mar 2, 2017

Q A
Bug fix? no
Breaking change? no
New feature? no
Deprecations? no
Spec compliancy? no
Tests added/pass? Tests pass
Fixed tickets None
License MIT

This change enables type-checking in all src files (via all=true in .flowconfig).

Two big changes were needed:

  • Prototype assignment (Parser.prototype.foo = function () { ... }) doesn't get a type-checked this. Changed these to Parser.mixin(class extends Parser { ... }, and did the actual prototype assignment in mixin.
  • Similarly, instead of using parser.extend, a plugin is now a function (superClass: Class<Parser>): Class<Parser> => class extends superClass { ... }, and parser.usePlugin does the actual extending (and setting of super).

Other than those, I tried as much as possible to keep the code as it is, and just add type annotations and comments.

Notes:

  • For mixed-in methods used outside of the mixin class, they must be declared in parserTypes.js or they are invisible to the outside.
  • This disables eslint's indent rule so that it can get a readable diff. Indenting could be fixed in another PR.
  • This declares [key: string]: any on Node, since often a node will be downcast to another node type and writing all those downcasts would be a pain.
  • estree.js contains lots of $FlowIgnore since we're changing the node to a different type.
  • Added throw in front of this.raise(...) and this.unexpected(...) so that control-flow analysis can know about these.

@codecov
Copy link

codecov bot commented Mar 2, 2017

Codecov Report

Merging #393 into master will decrease coverage by 0.13%.
The diff coverage is 98.48%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #393      +/-   ##
==========================================
- Coverage   98.26%   98.13%   -0.14%     
==========================================
  Files          20       20              
  Lines        3511     3478      -33     
  Branches      934      936       +2     
==========================================
- Hits         3450     3413      -37     
- Misses         22       25       +3     
- Partials       39       40       +1
Flag Coverage Δ
#babel 82.2% <85.9%> (-0.14%) ⬇️
#babylon 96.89% <98.04%> (-0.12%) ⬇️
Impacted Files Coverage Δ
src/util/identifier.js 100% <100%> (ø) ⬆️
src/options.js 100% <100%> (ø) ⬆️
src/tokenizer/index.js 98.35% <100%> (ø) ⬆️
src/parser/location.js 100% <100%> (ø) ⬆️
src/parser/comments.js 96.07% <100%> (-0.08%) ⬇️
src/parser/statement.js 99.07% <100%> (ø) ⬆️
src/parser/node.js 96.87% <100%> (-0.19%) ⬇️
src/parser/lval.js 98.63% <100%> (ø) ⬆️
src/tokenizer/state.js 100% <100%> (ø) ⬆️
src/tokenizer/types.js 100% <100%> (ø) ⬆️
... and 8 more

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 1c07efb...0dd3b8a. Read the comment docs.

@@ -2,5 +2,8 @@
"extends": "babel",
"env": {
"node": true
},
"rules": {
"indent": 0
Copy link
Member

Choose a reason for hiding this comment

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

Why did you disable this rule? Where there problems with it?

Copy link
Author

Choose a reason for hiding this comment

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

If I indent things correctly, this PR will have an unreadable diff because the indent of most lines will change. Don't know a good solution for that.

Copy link
Member

@danez danez Mar 3, 2017

Choose a reason for hiding this comment

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

Huh? Wasn't it properly indented before?

Edit: Ah I see the mixin part. Nice catch. Yes we can enable and autofix later

👍

src/options.js Outdated
allowImportExportEverywhere: boolean;
allowSuperOutsideMethod: boolean;
plugins: $ReadOnlyArray<string>;
strictMode: any;
Copy link
Member

Choose a reason for hiding this comment

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

strictMode can be boolean or null

jsx_readToken() {
// $FlowFixMe This method doesn't override anything!
if (!this.state.inType) return super.jsx_readToken();
}
Copy link
Member

Choose a reason for hiding this comment

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

Looking at the history this was never working, so can you please remove this?

node.body.unshift(this.directiveToStmt(directive));
});
const directiveStatements = node.directives.map((d) => this.directiveToStmt(d));
node.body = directiveStatements.concat(node.body);
Copy link
Member

Choose a reason for hiding this comment

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

reverse() got missing.

Copy link
Author

Choose a reason for hiding this comment

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

reverse + forEach + unshift = concat. ☺️

end: number;
loc: SourceLocation;
range: [number, number];
leadingComments?: ?Array<Comment>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Flow has a bug with maybe-typed properties in interfaces. It's better to avoid interfaces completely unless they only contain methods

Copy link
Author

Choose a reason for hiding this comment

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

This is implemented by a class, so I think we need the interface?


pp.addComment = function (comment) {
Parser.mixin(class extends Parser {
addComment(comment: N.Comment): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can omit annotations if you don't export class or function

Copy link
Author

Choose a reason for hiding this comment

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

Some of them could be omitted, but I've tested around and some of them can't without breaking compilation. I also think it's nice to have the type annotations locally so I don't have to look them up in parserTypes.js. The code worked fine without any type annotations, the point was to make it easier to read.

this.type = "";
// $FlowNonNull
this.start = pos;
Copy link
Contributor

Choose a reason for hiding this comment

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

If it's always present, then why pos is optional?

Copy link
Author

Choose a reason for hiding this comment

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

new Node; is called in one place, but I just added a $FlowFixMe comment at that place and no longer need hacks here. Thanks!

@@ -238,6 +244,7 @@ pp.parseForStatement = function (node) {
if (forAwait) {
this.unexpected();
}
// $FlowCast node: N.ForStatement
Copy link
Contributor

Choose a reason for hiding this comment

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

It's better to do this explicitly with any: ((node: any): N.ForStatement)

Copy link
Author

Choose a reason for hiding this comment

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

Done


pp.shouldParseAsyncArrow = function () {
shouldParseAsyncArrow(): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

Does flow not infer this, or do you want to assert that it returns a boolean?

Copy link
Author

@ghost ghost Mar 10, 2017

Choose a reason for hiding this comment

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

For a function declared in parserTypes.ts, the return type should be carried along from there. But for a function declared only locally, you may get no (or confusing) errors without a type annotation. I think it's better to just consistently declare types. E.g., remove the return type from parseCallExpressionArguments, add elts.push(0); somewhere, and you won't get any errors.

const value = this.state.value;
node = this.parseLiteral(value.value, "RegExpLiteral");
const node = this.parseLiteral(value.value, "RegExpLiteral");
Copy link
Member

@Jessidhia Jessidhia Mar 10, 2017

Choose a reason for hiding this comment

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

A similar change could be made in most of the switch cases, but probably best to not make the diff much larger than it needs to be

Copy link
Author

Choose a reason for hiding this comment

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

Whoops, must have been trying something and forgot to change it back.

continue;
if (proto[key])
throw new Error(`Can't mixin '${key}', already defined`);
proto[key] = mixedProto[key];
Copy link
Member

Choose a reason for hiding this comment

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

The copied keys will be enumerable, but they are not when defined in the class expression.

On the other hand, before the change to class expression, they were enumerable anyway, so it might not matter.

Copy link
Author

Choose a reason for hiding this comment

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

I don't have an opinion on this.

}

superProto[key] = self[key];
self[key] = extenderProto[key];
Copy link
Member

Choose a reason for hiding this comment

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

Same thing in these two lines here w.r.t. enumerability.

@hzoo hzoo changed the base branch from 7.0 to master March 21, 2017 21:09
@hzoo
Copy link
Member

hzoo commented Mar 21, 2017

We moved the previous 7.0 branch to be master now, and master is now 6.x

@hzoo
Copy link
Member

hzoo commented Mar 21, 2017

Hey @andy-ms! It looks like one or more of your builds have failed. I've copied the relevant info below to save you some time.

@ghost
Copy link
Author

ghost commented Mar 21, 2017

It looks like the test failure is in babel somewhere -- did this PR change it or was the change in babel?

@hzoo
Copy link
Member

hzoo commented Mar 21, 2017

Yeah a different pr we need to fix that I merged, need to mereg babel/babel#5450

@ghost
Copy link
Author

ghost commented Mar 28, 2017

Hm, still failing.

TypeError: [BABEL] /home/travis/build/babel/babylon/node_modules/istanbul-lib-instrument/dist/index.js: Plugin 0 specified in "/home/travis/build/babel/babylon/.babelrc.env.test" was expected to return a function but returned "undefined" (While processing preset: "/home/travis/build/babel/babylon/node_modules/babel-preset-stage-0/lib/index.js") (While processing preset: "/home/travis/build/babel/babylon/node_modules/babel-preset-es2015/lib/index.js")

@existentialism
Copy link
Member

Yeah, looks like an unrelated (to this PR) issue with the latest b7 alpha, will take a look after lunch.

@ghost
Copy link
Author

ghost commented Apr 3, 2017

@existentialism Seems to have been fixed.

@ghost
Copy link
Author

ghost commented Apr 6, 2017

Again, test failures are probably due to some change in babel?
Update: It's failing on master, made an issue: #453

@danez
Copy link
Member

danez commented Apr 7, 2017

Besides it being a massive diff I do not like they idea of restructuring code just to please flow. It might have been easier to start small and just enable flow file by file and PR them separately.
This current approach with the inheriting classes and then the separate file with the type definitions seems rather complicated and unusual to me, and we maybe should think about a better structure of the Parser instance that would also work with flow.

Ohh and we should keep an eye on performance, as a drastic change like this might also have performance impacts.

@loganfsmyth
Copy link
Member

I'll second performance. The super change could certainly introduce performance changes since it has to look up the prototype chain instead of calling a reference to the function it already has.

That said, I don't have a problem with the structure. I think having the node types in their own file is inevitable since they are used all over the place, and the class extension + mixin approach is a little weird, not doesn't seem too bad to me.

@ghost
Copy link
Author

ghost commented Apr 7, 2017

Bad news: It looks like with plugins applied, this branch does make parsing a bit slower.
Good news: We can make it a lot faster!

Currently, plugins work by writing functions directly to the instance.
This branch modifies plugins to be functions from a superclass to a new subclass.
But it then takes the methods off of that subclass and directly assigns them...

I have modified this to instead just create a class, and never directly assign to an instance.
These classes can be reused across multiple parse calls, and node seems to have an easier time optimizing them.

Here are my measurements parsing jquery with the flow and jsx plugins applied:

Name Time
master, no plugins 7.86 8.07 8.02 8.14 7.84
type-check, no plugins 7.93 8.05 7.80 7.97 7.96
master with plugins 22.0 21.2 21.6 21.0
type-check with plugins 25.3 24.1 23.0 22.9
type-check with cached plugins 10.6 10.5 10.3 10.0

It looks like the existence of instance functions was making it run significantly slower, but moving that all into a prototype brought the time back down. There is of course still some increase in time due to the actual work that the plugin does.

Shall I make a pull request doing just this? (UPDATE: #459)

@ghost
Copy link
Author

ghost commented May 19, 2017

Closing since this has been broken into many separate PRs.

#459 Convert each plugin to a function from a class to an overriding class
#460 Type-check tokenizer/index.js
#480 Add type declarations for AST nodes
#481 Convert each file with parser methods to a class in an inheritance chain
#482 Move plugin helpers out of Parser.prototype and into the plugin itself
#484 Type-check CommentsParser and LocationParser
#485 Type-check UtilParser
#486 Type-check node.js
#487 Type-check LValParser
#488 Type-check ExpressionParser
#489 Type-check StatementParser
#490 Type-check options.js and index.js
#491 Type-check utils
#492 Type-check State
#493 Type-check tokenizer/types.js
#494 Type-check estree plugin
#495 Type-check flow plugin
#496 Type-check JSX plugin
#521 Fix type check errors

@ghost ghost closed this May 19, 2017
@ghost ghost deleted the type-check branch May 19, 2017 21:03
This pull request was closed.
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.

6 participants