Skip to content

Conversation

@rossng
Copy link

@rossng rossng commented Jan 30, 2022

Initial checklist

  • I read the support docs
  • I read the contributing guide
  • I agree to follow the code of conduct
  • I searched issues and couldn’t find anything (or linked relevant results below)
  • If applicable, I’ve added docs and tests

Description of changes

This PR improves the JSDoc types that come with unist-util-map.

The existing type of map is not generic. This is fine if you just want to map Nodes to Nodes, but painful if you want to do typesafe mapping of (e.g.) hast-specific node types in your TypeScript code. I came across this scenario while I was working on addressing some bugs in the rehype-twemojify plugin.

This PR updates the type of map to the following effective type:

function map<OutputNode extends Node<any> = Node<Data>, InputNode extends Node<any> = OutputNode>(tree: InputNode, iteratee: MapFunction<OutputNode, InputNode>): OutputNode

@github-actions github-actions bot added 👋 phase/new Post is being triaged automatically 🤞 phase/open Post is being triaged manually and removed 👋 phase/new Post is being triaged automatically labels Jan 30, 2022
rossng and others added 3 commits January 31, 2022 10:10
Co-authored-by: Christian Murphy <christian.murphy.42@gmail.com>
Co-authored-by: Christian Murphy <christian.murphy.42@gmail.com>
Co-authored-by: Christian Murphy <christian.murphy.42@gmail.com>
@rossng
Copy link
Author

rossng commented Jan 31, 2022

Thanks for the review 🙂 - I've applied your suggestions.

Copy link
Member

@wooorm wooorm left a comment

Choose a reason for hiding this comment

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

* @returns {Node}
* @template {Node} NodeLike
* @param {NodeLike} node
* @returns {NodeLike}
Copy link
Member

Choose a reason for hiding this comment

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

this one’s broken? (it apparently already was?)

@rossng
Copy link
Author

rossng commented Jan 31, 2022

The new behavior should be tested with tsd. Examples:

* https://github.com/syntax-tree/unist-util-visit/blob/main/index.test-d.ts

* https://github.com/syntax-tree/hast-util-is-element/blob/main/index.test-d.ts

It might be easier for generics to write a d.ts file manually, and use them in the JavaScript code. Examples:

* https://github.com/syntax-tree/unist-util-visit/blob/main/complex-types.d.ts

* https://github.com/syntax-tree/mdast-util-math/blob/main/complex-types.d.ts

Hmm, fair point. I did find it pretty tricky to get the types right with just JSDoc, and I'm still not totally happy. I'll take another pass at this later.

@rossng
Copy link
Author

rossng commented Feb 2, 2022

I spent some more time looking at this and came to the conclusion that it is extraordinarily difficult to type this function correctly. As such, I'm going to close this PR.

Here's about as far as I got. I just can't find a way to make the output type of the mapping function dependent on the input type - and I'm not 100% sure that it's actually possible.

/**
 * Function called with a node to produce a new node.
 *
 * @param node Current node being processed
 * @param index Index of `node`, or `null`
 * @param parent Parent of `node`, or `null`
 * @returns {OutputNode} Node to be used in the new tree. Its children are not used: if the original node has children, those are mapped.
 */
type MapFunction<
  InputNode extends Node<any> = Node,
  OutputNode extends Node<any> = Node
> = (
  node: InputNode,
  index: number | null,
  parent: Parent<InputNode> | null
) => OutputNode

type Foo<
  RootNode extends Node<any>,
  OutputNode extends Node<any>,
  MapFunctionAccumulator = MapFunction<RootNode, OutputNode>
> = RootNode extends Parent<infer InputChildNode>
  ? OutputNode extends Parent<infer OutputChildNode>
    ? MapFunctionAccumulator extends MapFunction<
        InputChildNode,
        OutputChildNode
      >
      ? MapFunctionAccumulator
      : Foo<
          InputChildNode,
          OutputChildNode,
          MapFunctionAccumulator & MapFunction<InputChildNode, OutputChildNode>
        >
    : MapFunction<RootNode, OutputNode>
  : MapFunction<RootNode, OutputNode>

@rossng rossng closed this Feb 2, 2022
@github-actions

This comment has been minimized.

@wooorm
Copy link
Member

wooorm commented Feb 2, 2022

Yeah, this stuff can get really complex. And also slow. Not for the faint of heart. ;)
Thanks for trying your hand at this!

@wooorm wooorm added 👎 phase/no Post cannot or will not be acted on 🤷 no/invalid This cannot be acted upon and removed ☂️ area/types This affects typings 🤞 phase/open Post is being triaged manually labels Feb 2, 2022
@JuanM04 JuanM04 mentioned this pull request Apr 30, 2022
5 tasks
wooorm pushed a commit that referenced this pull request May 2, 2022
Related-to GH-3.
Closes GH-4.

Reviewed-by: Titus Wormer <tituswormer@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤷 no/invalid This cannot be acted upon 👎 phase/no Post cannot or will not be acted on

Development

Successfully merging this pull request may close these issues.

3 participants