How to Do a TypeScript Conversion
v5.chriskrycho.com
v5.chriskrycho.com
https://medium.com/airbnb-engineering/ts-migrate-a-tool-for-...
And also open sourced the tooling:
An additional problem with doing partial migrations is that it leaves people in a position to just not fix the file they’re working on, since half converted files are the norm, and they have more important things to do.
So we just write new code with types by convention but have no actual linting or checks because it’s too hard to actually static typing to an existing repository.
1. Give up on pre-commit. It would be nice if it just worked, but it doesn't. Just make a type checking script and run it as the first thing in CI.
2. Use Pyright, not Mypy. It's much much better.
3. You can start with a very loose config and gradually make it stricter.
4. Make liberal use of `type: ignore` (and add TODOs to make it clear they're not intentional).
5. You can also make an alias for Any and use that for types that you intend to figure out but haven't yet.
Typing Python that wasn't written with type hints in the first place is always going to be a complete nightmare though. People that write untyped Python tend to write it in highly dynamic ways and Python's type hints often aren't expressive enough to describe them (e.g. they only very recently added support for properly typing kwargs).
Couldn’t agree more. I joined a new team and recently finished converting a 20K line repo. It was a grueling grind to get mypy running in strict mode.
I’m continually finding that a huge benefit of typed python code isn’t autocomplete or catching bad argument passing, but preventing developers from creating patterns that are unmaintainable.
If your code is too complex to type correctly, its probably too complex in general :)
Ha I definitely agree with that. There are some exceptions where you want to do a reasonable thing that the types just aren't expressive enough for, but in general if you can't explain it so the type checker understands then your coworkers won't either.
I’ve been slowly reaching this conclusion after a few years of running into little issues. We run our pre-commit hooks as their own CI test via tox. I’m beginning to feel both tox and pre-commit are more trouble than they are worth, and most of that trouble comes from them trying to create and manage their own mini-environments. Which is never going to match having explicit envs, state, and deps via docker files that can be granularity inspected and/or deployed to other contexts.
> 2. Use Pyright, not Mypy. It's much much better.
Does this work well with pycharm? I think I looked into this a while ago and it seemed really tightly coupled to VSC. And I know some people love it, but I’ve found VSC’s config for linting/type checks/ auto formatting to be a complete confusing mess, and that it is generally much slower and buggier than pycharm (e.g. it will highlight errors that don’t exist, not update syntax highlighting instantly, etc).
I know it’s entirely possible that it’s just me who is an idiot here, but I’ve set up VSC on like 3 separate machines now in the last few years and it invariably ends up in some weird broken state, especially regarding highlighting code performantly and correctly using the same rules we have in CI.
Nah pre-commit is great for lots of quick checks (formatting, trailing whitespace, linting shell scripts etc), it's just that type checking Python requires the Python dependencies to be already installed and that means setting up a venv and installing stuff and that's just a bit too complex.
> Does this work well with pycharm? I think I looked into this a while ago and it seemed really tightly coupled to VSC.
Possibly not. You can definitely run it from the command line without VSCode since Pyright the type checker is open source. The VSCode Python LSP server Pylance is closed source though (this provides additional features beyond type checking like completion and refactoring).
Haven't used Pycharm for decades but I'd guess it has its own thing.
While I have never done a js to ts conversation, trying to modularize a ts monolith into even some rudimentary packages was very difficult because of circular dependencies at the file level (which is entirely possible).
Ended up giving up at the time because of cost/benefit.
In one of our single-package repos, I added some ESLint rules to effectively forbid circular module dependencies. Not only are they a code smell, but when modules have side effects (which is really common), they can break circular dependencies at arbitrary places in the cycle.
We’ve been gradually moving to a very small number of monorepos and can better achieve modularity by liberally creating small packages and forbidding package-level cyclic dependencies.
1. writing types for the big, central libraries that everyone uses (and make them "smart enough" to infer as much as possible).
2. teaching everyone at the company a brand new language without them hating it, and without their team losing (much) velocity.
https://codeascraft.com/2021/11/08/etsys-journey-to-typescri...
Either put in the correct type or leave it as is. Placing 'any' in the code just fossilizes badness. Implicit any is just easier to accept, because when you finally have time to get serious you can disable it an write the correct types. You don't have to hunt for 'any' throughout the code.
How do you track where you have them? Just \bany\b regexp?
It’s not like you can’t of course, it’s just that it doesn’t “encourage” your developers to type things as they build them, which will eventually lead to shortcuts for a lot of people (myself included). Even if it doesn’t, it’s usually better for people to think about their types earlier rather than later, especially the less senior they are, or at least that is our experience. It’s much easier to get new people onboard when they aren’t doing “bad habits” that they then have to fix before their code is allowed to pass through the production pipeline.
I routinely ask it to convert javascript to typescript.
It does a great job for the most part in seconds, leaving me to clean up where it got it wrong, and then fix the bugs that typescript exposed.
Surely this makes the task easier?
The only trap to avoid is it takes a conscious decision to do no other factoring than the typescript conversion. Don’t start “just fixing this other little thing while I’m here”.
You can, for example, make invalid states impossible to represent. This can catch a huge number of bugs and prevent some types of bugs from ever appearing.
If you're using something like ChatGPT or some other automated system to add types to your code you have adopted a type system with receiving very little of the benefits. You end up with many of the drawbacks and only a little of the reward.
smoothest workflow ever.
ChatGPT here doesn't need to do a good job. You get value just by getting a codebase type-annotated.
I've yet to see these claims realised not by a handful of type gurus with ad-hoc type-level DSLs, but in a more-or-less generic way applicable in every day use.
// this will (hopefully) not compile in your favorite language
int i = "Hello World"
Using type systems to enforce business logic is applying the same concept, just at higher levels of abstraction.
Usually you will want (or need) to use the more advanced type features that your language offers, like generics and algebraic data types.
There certainly is a cost to learning these concepts, but the good thing is that many languages borrow these same concepts, so the knowledge easily transfers.
The gulf between this and "make invalid states impossible to represent" is a vast as the void between two galaxies.
> Using type systems to enforce business logic is applying the same concept, just at higher levels of abstraction.
"Just"
> There certainly is a cost to learning these concepts, but the good thing is that many languages borrow these same concepts, so the knowledge easily transfers.
To repeat myself: I've yet to see these claims realised not by a handful of type gurus with ad-hoc type-level DSLs, but in a more-or-less generic way applicable in every day use.
Your new code (typescript in this case) should never reference the old code. Old code is the only thing that knows about new code.
The reason being is if your new code ever knows about your old code, your new code will carry on old code forever. You can never escape.
But if only old code knows about new code. Your new code is perfectly clean.
For this typescript conversion problem the nice thing is you can configure typescript to allowJs and your existing code can import compiled typescript
The solution for me would be make new libraries (without circle) and migrate old code to reference them. Instead of pulling old code into the new libraries with the circular deps coming with them
Or, you’d have to continue expanding the old code using only the old language/methods (here: JS instead of TS).
That means that sprinkling any around your code isn't a great way to silence errors.