Skip to content
This repository has been archived by the owner on Jan 14, 2019. It is now read-only.

Offer semantic services alongside AST #24

Merged
merged 33 commits into from
Nov 22, 2018

Conversation

uniqueiniquity
Copy link
Contributor

General organization of changes:

  • Added option to parser called project, which takes either a single project file path or an array of project file paths (top-level parser.js)
  • If option is given, program(s) are created from their corresponding project files (tsconfig-parser.js)
    • Changes to programs over the lifetime of the ESLint process (i.e. when changes are made to the file being linted with --fix or when ESLint is used as a server, as it is in Visual Studio and Visual Studio Code) are tracked by the TS watch API.
  • During the existing TS AST traversal to build the ESTree AST, we build up two maps for a two-way mapping between TS AST nodes and ESTree AST nodes. (maps are built in convert.js, passed up through ast-converter.js to top-level).
    • References to the whole AST are provided in many places in convert.js to avoid having to ensure parent pointers are set in the TS AST (that is to say, for efficiency).
  • We return the program corresponding to the file being linted and the maps in addition to the AST (parser.js)

Note that the features added by this PR are entirely enabled or disabled (from the consumer's perspective) by the project option; the fallback behavior is the existing behavior.

I also added test coverage to ensure the two-way mapping is consistent (i.e. ts2es(es2ts(node)) = node) and that the exposed checker is usable as a rule might expect.

Map approach is based on eslint/typescript-eslint-parser#415

@uniqueiniquity
Copy link
Contributor Author

Will fix the merge conflict on Monday (and push a couple more commits that I don't have access to right now), but the core approach will stay unchanged.

@JamesHenry
Copy link
Owner

JamesHenry commented Nov 4, 2018

Really exciting stuff, @uniqueiniquity! Thanks again for contributing, @DanielRosenwasser mentioned this might be coming up soon.

I am sorry to have made more work for you when it comes to reconciling conflicts, but I unfortunately don't have scope to work on this during working hours right now, and have to seize any free-time opportunity I get (which happened to be this weekend).

In #25 I have brute force (via a gazillion type assertions) converted the repo to finally use TypeScript for its own source. The idea being that I didn't want to make runtime changes/improvements along the way which might get lost within the sheer size of the PR. There can now be lots of smaller, more focused follow ups to improve the way type information flows through.

I am sure a number of your improvements in this PR can further utilize that now too.

Thanks so much again!

@uniqueiniquity
Copy link
Contributor Author

That's totally fine! Of course I'm biased, but I'm more than happy to convert this to TypeScript. 😄

tsconfig.json Outdated Show resolved Hide resolved
@JamesHenry
Copy link
Owner

@uniqueiniquity Is this ready for review?

@uniqueiniquity
Copy link
Contributor Author

@JamesHenry yes please!

src/node-utils.ts Outdated Show resolved Hide resolved
src/parser.ts Outdated Show resolved Hide resolved
src/parser.ts Outdated Show resolved Hide resolved
src/parser.ts Outdated Show resolved Hide resolved
@@ -183,5 +257,13 @@ export { version };
const version = packageJSON.version;

export function parse(code: string, options: ParserOptions) {
Copy link
Owner

@JamesHenry JamesHenry Nov 8, 2018

Choose a reason for hiding this comment

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

I think it would be good to keep the parse() function as being as simple as possible:

parse(CODE, OPTIONS): AST

Maybe we could add an alternative method:

parseAndGenerateServices(CODE, OPTIONS): YOUR INTERFACE

(open to suggestions on the name)

Then the opt-in is even more explicit for consumers, and the parse function interface will match what other parsers in this space expose.

We did a similar thing for ESLint: we have parse and parseForESLint exposed in custom parsers.

WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

You could also put the parser services in the AST.

Copy link
Owner

Choose a reason for hiding this comment

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

That would make them harder to serialize though right?

Copy link
Contributor Author

@uniqueiniquity uniqueiniquity Nov 9, 2018

Choose a reason for hiding this comment

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

I'm fine with adding a second method for explicit opt in.

One thing that I'm not sure about in general is whether or not to always give the ts<->estree maps, since (as it stands) we always create them.
So this could give three levels:

  • parse gives ast
  • parseAndGenerate w/o project flag gives ast and maps
  • parseAndGenerate w/ project flag gives ast, maps, and program

Does it make any sense to set it up that way? The TS AST does make it easier to do some things that don't involve semantic information, like finding the position of keywords within nodes.

Copy link
Contributor

Choose a reason for hiding this comment

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

That would make them harder to serialize though right?

You’d just need to add a toJSON method:

JSON.stringify({ foo: { toJSON: () => undefined } })
// => "{}"

Or is that not what you meant @JamesHenry?

Copy link
Owner

Choose a reason for hiding this comment

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

I think for this initial PR we could start with just two: all or nothing.

  • parse() will be as it is today, intended for consumers like prettier where we want an ESTree-compatible AST as quickly as possible (i.e. skip any work that isn't conversion related)

  • parseAndGenerateServices() which exposes everything (ast, maps, and program), intended for consumers like ESLint who want to consume semantic/type information for advanced rule creation.

By doing it this way all the new stuff is additive and we don't have to worry about changing the experience for existing consumers. If the new stuff is not quite right for the advanced use-case, we can then further refine it in follow up PRs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense to me. I've exposed a separate top-level function called parseAndGenerateServices and made all of the additions (i.e. maps and program) only run if that new function is called and the project option is given.

src/tsconfig-parser.ts Outdated Show resolved Hide resolved
Copy link
Owner

@JamesHenry JamesHenry left a comment

Choose a reason for hiding this comment

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

@uniqueiniquity
Copy link
Contributor Author

@JamesHenry I've made the changes you requested, I think. Would love for you to take another look!

@JamesHenry JamesHenry merged commit 8787f16 into JamesHenry:master Nov 22, 2018
@JamesHenry
Copy link
Owner

Awesome, thanks so much again!

@JamesHenry
Copy link
Owner

🎉 This PR is included in version 5.1.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Successfully merging this pull request may close these issues.

3 participants