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

New: Add some parserServices #415

Closed
wants to merge 6 commits into from
Closed

Conversation

j-f1
Copy link
Contributor

@j-f1 j-f1 commented Dec 16, 2017

This lets you get the ts.Program instance for a file, as well as the TypeScript AST node corresponding to a particular node, and the type of any node.

@j-f1 j-f1 changed the title Add some parserServices New: Add some parserServices Dec 16, 2017
@j-f1 j-f1 force-pushed the add-parser-services branch from b4b3f38 to b92dfa7 Compare December 16, 2017 19:29
@JamesHenry
Copy link
Member

I have a branch from a year ago(!) which was looking to address this: https://github.com/eslint/typescript-eslint-parser/compare/parser-services

I think we should use that Map based implementation rather than adding things to every AST node, and I don't think it is necessary for the parser to concern itself with the type checker yet. Exposing the ts.Program gives plugin authors everything they need right? I think we should look to add the bare minimum that is useful (as this effectively a new public API we are supporting) and then keep an eye on the actual usage. WDYT?

@j-f1
Copy link
Contributor Author

j-f1 commented Dec 18, 2017

Exposing the ts.Program gives plugin authors everything they need right?

As seen above in my PR to desktop/desktop, the current usage doesn’t properly handle types imported from other files, or from globals.d.ts files. I’m working on a better system, but it needs a patch in ESLint core to send the path of the file being parsed to the parser.

I think we should use that Map based implementation rather than adding things to every AST node.

👍 Will take a look at using Map/WeakMap.

@j-f1
Copy link
Contributor Author

j-f1 commented Jan 5, 2018

WeakMap added. Sorry for the delay.

@@ -186,12 +210,15 @@ function generateAST(code, options, additionalParsingContext) {
exports.version = require("./package.json").version;

exports.parse = function parse(code, options) {
return generateAST(code, options, { isParseForESLint: false });
Copy link
Member

Choose a reason for hiding this comment

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

The name generateAST no longer makes sense for this function after this change

@@ -58,6 +58,9 @@ module.exports = function convert(config) {
loc: nodeUtils.getLoc(node, ast)
};

nodeUtils.map.set(result, node);
Copy link
Member

Choose a reason for hiding this comment

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

I think these should come right before we return the result, that way you don't need to have two lots of them (i.e. there is no need to have them again in node-utils)

Copy link
Member

Choose a reason for hiding this comment

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

map and reverseMap are not clear names IMO

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I set them when the nodes are created so that in e.g.

export class Foo {}

the ClassDeclaration and ExportNamedDeclaration have a TS AST node attached to them (ClassDeclaration).

@@ -98,6 +98,12 @@ TOKEN_TO_TEXT[SyntaxKind.CaretEqualsToken] = "^=";
TOKEN_TO_TEXT[SyntaxKind.AtToken] = "@";
TOKEN_TO_TEXT[SyntaxKind.InKeyword] = "in";

// ASTNode => TSNode
Copy link
Member

Choose a reason for hiding this comment

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

We refer to them as ESTreeNode throughout the codebase so please go for that

@@ -553,6 +561,9 @@ function fixExports(node, result, ast) {
loc: getLocFor(exportKeyword.getStart(), result.range[1], ast)
};

map.set(newResult, node);
Copy link
Member

Choose a reason for hiding this comment

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

As per comment above I don't think these Maps need to be in node-utils at all

return {
program: result.program,
getTSNode: node => nodeUtils.map.get(node),
getESNode: tsNode => nodeUtils.reverseMap.get(tsNode),
Copy link
Member

Choose a reason for hiding this comment

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

Again, ESTreeNode is the name that has been used.

I'm a little unsure about the use-case for getESNode and reverseMap as they are currently called. I feel which should err on adding as little as possible to this API surface, particularly in this initial PR, we can always add more later

program: result.program,
getTSNode: node => nodeUtils.map.get(node),
getESNode: tsNode => nodeUtils.reverseMap.get(tsNode),
typeChecker,
Copy link
Member

Choose a reason for hiding this comment

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

As per my comment above about minimal API surface - why do we need a reference to the typeChecker, if we are already exposing the created Program?

getTSNode: node => nodeUtils.map.get(node),
getESNode: tsNode => nodeUtils.reverseMap.get(tsNode),
typeChecker,
getType(node) {
Copy link
Member

Choose a reason for hiding this comment

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

Helper functions like this could always be added later if we found that the demand is high, but again, we have already exposed everything you would need to get this yourself

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found it really useful when I was writing a rule using this — otherwise you have to do

context.parserServices.program.getTypeChecker().getTypeAtLocation(context.parserServices.getTSNode(node))

just to get the type of a node. It’s also arguably the most common use of the parser services.

@JamesHenry
Copy link
Member

@j-f1 I am going to try and get a TS 2.7 release out ASAP, but do you think we could pick this up again after that. No worries, if you don't have any availability right now, just let me know.

@j-f1
Copy link
Contributor Author

j-f1 commented Feb 9, 2018

I gave up on the rule I was writing this for, partially because there were some issues (I don’t remember exactly what) that prevented me from getting accurate type information.

@j-f1
Copy link
Contributor Author

j-f1 commented Feb 9, 2018

I feel like the best way to implement this would be to use TS’s language service API.

@kaicataldo
Copy link
Member

Is this something we still want to do?

@j-f1
Copy link
Contributor Author

j-f1 commented Jun 11, 2018

I’m not working on the project I was going to use this for right now so it’s pretty far down my priority list, and I don’t think I’ll be able to work on this in the near future. Feel free to work off my branch though!

@kaicataldo
Copy link
Member

Thanks for the update! Should we just close this for now and revisit this issue if it’s ever needed again?

@j-f1
Copy link
Contributor Author

j-f1 commented Jun 11, 2018

Sounds good 👍

@j-f1 j-f1 closed this Jun 11, 2018
@uniqueiniquity
Copy link
Contributor

@j-f1 @JamesHenry Do you know if anyone has been working on this recently? I'm on the TypeScript team, and we'd love to see if there's a way to get semantic information available for rules. If it's not already being tackled, I'd love to pick up this effort and see where we can get with it!

@j-f1
Copy link
Contributor Author

j-f1 commented Aug 3, 2018

@uniqueiniquity Go ahead! It’d be really cool to see this implemented. Feel free to start from my branch (or create your own branch if you prefer).

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.

4 participants