question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[TypeScript] TransformCallback's Result argument as non-optional

See original GitHub issue

Hello there!

I noticed that the TransformCallback (return value from postcss.plugin()) in the type definitions receives an optional result argument.

https://github.com/postcss/postcss/blob/3a44f17aadb81f984e760d05cceac2e6e5b6471d/lib/postcss.d.ts#L36

I cannot think of a reason why result would be undefined. But leaving its type this way forces my plugin to deal with the possibility. I have to add an ugly block of code near the top of my transform callback that looks like this, just so that that I don’t have to check for undefined later:

    if (result === undefined) {
      throw new Error('Cannot run without a result defined');
    }

I propose that we change the definition to the following:

interface TransformCallback {
    /**
     * @returns Asynchronous plugins should return a promise.
     */
     (root: Root, result: Result): Promise<any> | any;
  }

This should not break existing TypeScript implementations since a function which doesn’t use the result argument would still implement this interface. You’ll also notice that the return value is changed, which also shouldn’t break anything, since both the existing return type and the one I’ve proposed uses a union with any, which is effectively still any. I just think this reads clearer, since asynchronous plugins should return a Promise, and otherwise the return value doesn’t really matter.

If this sounds good to the maintainers, I’m happy to make the PR.

cc @jedmao (seems to be the resident typescript expert)

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
aoberoicommented, Jan 4, 2019

Hmm… I guess the meaning of any in this situation is subjective. It’s totally valid that you feel it could mislead the developer into thinking the value has meaning, and yet it’s also totally valid that I think void could signal that it’s significant that there’s no value returned, hence TypeScript exhibiting the behavior that number is not assignable to void.

I’d like you to consider an example, which is actually the situation for the plugin I’m writing. I return a function that can call itself. When it calls itself, it’s return value is important, and I chose that it returns a Promise<Container>. I use the Container value to recursively merge declarations back up to the Root. This isn’t really an edge case, as any plugin dealing with importing or processing nested structures would likely design them to do the same. From an implementation point of view, this works. However if the return type of TransformCallback was Promise<void>, I would need to do something like the following just to satisfy the type system:

postcss.plugin('foo', (opts) => {
  // i’d like to just return myRecursiveFunc, but instead...
  return (root, result) => myRecursiveFunc(root, result).then(() => {});
});

I’d have to effectively erase the resolved value of my recursive function so that it’s resolved value is assignable to void. That seems just as bad (actually worse, there’s a performance cost) than the throw I used to convince the type system that result is never undefined. This demonstrates my interpretation above: TypeScript says Container is not assignable to void, so I must interpret the return type of void as meaningful.

How do you feel about keeping it as I’ve suggested, but in order to clarify the subjective interpretation, we add to the doc comments to explicitly let the user know any is being used to stand in for a value that will be ignored.

0reactions
jednanocommented, Jan 4, 2019

Recursion is a great example of the need for a more loose type.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Do's and Don'ts - TypeScript
Optional Parameters in Callbacks ... This has a very specific meaning: the done callback might be invoked with 1 argument or might be...
Read more >
How to pass optional parameters to callback according to ...
If you invoke the callback with three parameters, then none of them are optional in the declaration. Any caller can then create a...
Read more >
Typescript type checks passing a callback with non-optional ...
Typescript type checks passing a callback with non-optional parameter for optional one, but throws error at runtime #18466.
Read more >
The TypeScript Handbook, Optional Parameters and Postel's ...
This has a very specific meaning: the done callback might be invoked with 1 argument or might be invoked with 2 arguments.
Read more >
Don't use functions as callbacks unless they're designed for it
Convert some numbers into human-readable strings: import ... An AbortController was not designed to be an option object to addEventListener ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found