Skip to content

[estree] make function declaration id nullable#24854

Merged
RyanCavanaugh merged 3 commits intoDefinitelyTyped:masterfrom
stas-vilchik:estree-nullable-function-declaration-id
Apr 17, 2018
Merged

[estree] make function declaration id nullable#24854
RyanCavanaugh merged 3 commits intoDefinitelyTyped:masterfrom
stas-vilchik:estree-nullable-function-declaration-id

Conversation

@stas-vilchik
Copy link
Copy Markdown
Contributor

A FunctionDeclaration id can be null if this function declaration is a part of the ExportDefaultDeclaration (ast explorer gist):

export default function() {}

This PR updates the FunctionDeclaration interface.

@typescript-bot typescript-bot added Popular package This PR affects a popular package (as counted by NPM download counts). Awaiting reviewer feedback labels Apr 9, 2018
@typescript-bot
Copy link
Copy Markdown
Contributor

typescript-bot commented Apr 9, 2018

@stas-vilchik Thank you for submitting this PR!

🔔 @RReverser - please review this PR in the next few days. Be sure to explicitly select Approve or Request Changes in the GitHub UI so I know what's going on.

If no reviewer appears after a week, a DefinitelyTyped maintainer will review the PR instead.

@RReverser
Copy link
Copy Markdown
Contributor

For compatibility with existing code and better typecheck, I'd prefer this to be forked into separate interface which could only appear in export.

@stas-vilchik
Copy link
Copy Markdown
Contributor Author

@RReverser I'm not sure what could be the best way to do that. If I created a new interface with nullable id:

export interface ExportDefaultFunctionDeclaration extends BaseFunction, BaseDeclaration {
  type: "FunctionDeclaration";
  id: Identifier | null;
  body: BlockStatement;
}

it would share the same type as the normal FunctionDeclaration. Later if I wanted to create an eslint rule using this typings, I would write something like:

const rule: Rule.RuleModule = {
  create(context: Rule.RuleContext) {
    return {
      FunctionDeclaration(node: Node) {
        (node as FunctionDeclaration).id; // nullable!
      },

In this case I'd need to remember (or even know) that I can't safely cast node to FunctionDeclaration, instead I should cast it to FunctionDeclaration | ExportDefaultFunctionDeclaration. I think this approach is quite error-prone.

export interface FunctionDeclaration extends BaseFunction, BaseDeclaration {
type: "FunctionDeclaration";
id: Identifier;
// `id` is null when a function declaration is a part of the `export default function` statement
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This could be a JSDoc comment

// Declarations
var functionDeclaration: ESTree.FunctionDeclaration;
identifier = functionDeclaration.id;
var identifierOrNull: ESTree.Identifier | null = functionDeclaration.id;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

functionDeclaration.id = null would be a better test, as the identifierOrNull test wouldn't fail even without the change to the declaration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think I'll keep both. In any case you can assign nullable functionDeclaration.id to a not nullable identifier.

@RReverser
Copy link
Copy Markdown
Contributor

Not sure what is RuleModule in your snippet, but if it's kind of a visitor, it should be redefined to accept the broader FunctionDeclaration variant with nullable id, while the non-nullable one would still be used for statement level.

@Jessidhia
Copy link
Copy Markdown
Member

Jessidhia commented Apr 12, 2018

The only way to do that would be to know what the type of the parent node is, and that is not part of the type of a node.

Even if you do add another interface, say, NonNullableFunctionDeclaration, the moment you | it with FunctionDeclaration inside the definition of Declaration, it'd make FunctionDeclaration widen to be the exact same type as NonNullableFunctionDeclaration.

By the way, while we're at it, I just noticed that ClassDeclaration also has non-nullable id in the definition but it also is supposed to be nullable if it's the export default, like export default class {}.


Possible idea to do something close to what you want: change Statement to use a new type, BlockDeclaration instead of Declaration, that would have only the non-nullable declarations. Or remove Declaration entirely from Statement and change BlockStatement to explicitly accept BlockDeclaration.

@stas-vilchik
Copy link
Copy Markdown
Contributor Author

@RReverser my bad, I should have mentioned that it was an example of eslint rule. Its visitor relies on the node type and returns a generic Node. Therefore as a custom rule author, I'd need to remember that the FunctionDeclaration visitor doesn't simply return a estree.FunctionDeclaration, but something different.


@Kovensky thanks for the review comments and for the ClassDeclaration case. I'm not sure to understand your idea though.

Anyway I think it'd better if the estree.FunctionDeclaration represented a node with the type FunctionDeclaration with all possible variants.

@stas-vilchik stas-vilchik force-pushed the estree-nullable-function-declaration-id branch from 2b8f795 to b1d6e26 Compare April 12, 2018 13:40
@RReverser
Copy link
Copy Markdown
Contributor

I surely understand the complexities of updating this in a clean way, but want to find a way that would break as little code out there as possible.

@stas-vilchik
Copy link
Copy Markdown
Contributor Author

@RReverser I understand, I just don't know how to achieve that.

@typescript-bot typescript-bot added the Unmerged The author did not merge the PR when it was ready. label Apr 17, 2018
@typescript-bot
Copy link
Copy Markdown
Contributor

typescript-bot commented Apr 17, 2018

A definition author has approved this PR ⭐️. A maintainer will merge this PR shortly. If it shouldn't be merged yet, please leave a comment saying so and we'll wait. Thank you for your contribution to DefinitelyTyped!

@RyanCavanaugh
Copy link
Copy Markdown
Member

The TypeScript syntax tree suffers the same problem and it's a big source of bugs.

I think the fix as-is is honestly the right one - attempting to split out the tree differently doesn't really work because it's really a matrix (export, default, named, async, etc) rather than a directable graph.

@RReverser without a concrete better PR I think this one should be merged. Any objections?

@RReverser
Copy link
Copy Markdown
Contributor

Yeah, I think at this point trying to split out turns out to be too complicated to be worth it. We can always try to improve AST in future. @stas-vilchik sorry for the delay on this one!

@typescript-bot typescript-bot added Author Approved and removed Awaiting reviewer feedback Unmerged The author did not merge the PR when it was ready. labels Apr 17, 2018
@RyanCavanaugh RyanCavanaugh merged commit 8c3fdbc into DefinitelyTyped:master Apr 17, 2018
@RyanCavanaugh
Copy link
Copy Markdown
Member

🌟 🎈 🎉 🏆 🎂 ✨ ⭐️

Congratulations on your first DefinitelyTyped contribution!

🌟 🎈 🎉 🏆 🎂 ✨ ⭐️

@stas-vilchik
Copy link
Copy Markdown
Contributor Author

Great, thanks!

@stas-vilchik stas-vilchik deleted the estree-nullable-function-declaration-id branch April 18, 2018 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Popular package This PR affects a popular package (as counted by NPM download counts).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants