Hacker News new | ask | show | jobs
by dllthomas 1460 days ago
> If you do find a need to use type operations, please—for the sake of any developer who has to read your code, including a future you—try to keep them to a minimum if possible. Use readable names that help readers understand the code as they read it. Leave descriptive comments for anything you think future readers might struggle with.

Also, as you start getting complicated logic in your types, you need to test your types; make sure they admit things they should admit and reject things that they should reject. Ideally these tests can also serve some role as examples for your documentation.

4 comments

We do a _lot_ of this in the Redux library repos (examples: [0] [1] [2] ). We have some incredibly complicated types in our libraries, and we have a bunch of type tests to confirm expected behavior.

Generally, these can just be some TS files that get compiled with `tsc`, but it helps to have a bunch of type-level assertions about expected types.

I actually recently gave a talk on "Lessons Learned Maintaining TS Libraries" [3], and had a couple slides covering the value of type tests and some techniques.

[0] Redux Toolkit's `createSlice`: https://github.com/reduxjs/redux-toolkit/blob/9e24958e6146cd...

[1] Reselect's `createSelector`: https://github.com/reduxjs/reselect/blob/f53eb41d76da0ea5897...

[2] React-Redux's `connect`: https://github.com/reduxjs/react-redux/blob/720f0ba79236cdc3...

[3] https://blog.isquaredsoftware.com/2022/05/presentations-ts-l...

Hey Mark, I’m actually currently looking at a similar problem.

I’m writing a HTTP client based on composition. The exact details aren’t important, but one of the goals is to have a strong type system for describing a valid pipeline of things like response parsers. Imagine something like

Doing “type tests” alone isn’t too hard - we can just use conditional types and the extends keyword. If the code compiles, fine.

But the harder part is negative type tests. “Given this code, the developer should get this error from TSC”. But this is just as important a part of the API; your types are there to convince the consumer that they can call a type-checked API with confidence.

In theory it should be plausible to run TSC programmatically. The issue is that TypeScript’s ScriptProcessor API really wants to be called with files on the filesystem rather than source text. So I am having to do some bodging. If I can get something sorted I may write a repo to demo it, I think it is a common problem.

Hmm. While it may not be the immediate answer to your question, my Redux teammate Lenz Weber ( @phryneas ) wrote a Remark plugin that parses TS codeblocks out of Markdown and actually runs them through the TS compiler. As part of that I know he generates a bunch of "virtual files", including some parsing that lets you add an extra section of the codeblock representing another file to be compiled along with the actual example. The source for that may at least help give you some examples of how to use TS programmatically:

https://github.com/phryneas/remark-typescript-tools

I use @ts-expect-error for testing the negative case, but if there was a practical way of testing the actual error reported that would be wonderful.
My solution to this was to run tsc from Jest tests and do snapshot tests for the error messages

https://github.com/noppa/get-optional/tree/master/tests/typi...

I lost it when at my previous job I found a 20 multiline super complex type defined by another dev, asked him to describe it because I was in a tight deadline and had not time to parse whatever he was defining. He starts with "it's pretty simple" and then used like 10 minutes to describe me what he meant while writing on paper the various pieces getting confused two times. At the end of the day it could be simplified in a one line union type of a few strings which would cover 99% of the usecases and the other 1% was something we had never used and would never use.

I really wish people would focus more on keeping types as simple as possible instead of using that complexity just because the language allowed it.

It isn’t unusual that a complex working solution can be simplified over time. Isn’t that part of eliminating tech debt or something?

Typescript is just one of those languages where things can get carried away but then reigned in again with a nice complexity gradient.

>I lost it when at my previous job I found a 20 multiline super complex type defined by another dev, asked him to describe it because I was in a tight deadline and had not time to parse whatever he was defining. He starts with "it's pretty simple" and then used like 10 minutes to describe me what he meant while writing on paper the various pieces getting confused two time

sorry about that

@ts-expect-error is useful for this
There are probably newer tools for this, but we use `eslint-plugin-expect-type` [0] for this:

https://github.com/cypress-io/cypress/blob/develop/cli/types...

[0]: https://github.com/JoshuaKGoldberg/eslint-plugin-expect-type