Summary: I describe why and how we built "Autobang", a tool that automatically converts JS projects to strict TypeScript. This is an unusual solution to a problem that previously seemed intractable for large projects.
We built a tool to automatically convert a JS codebase to strict TypeScript with some interesting trade-offs. This is a hard and interesting problem I've never heard discussed
- enables devs to express intent of code
- dev tools
- catch silly errors early
- enhanced expressivity, dev tooling
- avoiding "cannot call method
foo
of 'undefined'" and similar runtime errors
--strictNullChecks
--noImplicitAny
--noImplicitThis
--strictBindCallApply
--strictNullChecks
--strictPropertyInitialization
- and more
--strictNullChecks
const arr = [1, 2, 3];
while (arr.length) {
console.log(arr.pop() + 1); // Error
}
--noImplicitAny
const sum = (n1, n2) => n1 + n2; // error
sum({}, {});
--strictNullChecks
--strictPropertyChecks
class Circle {
diameter: number; // error
constructor(diameter: number) {
this.setUpDimensions(diameter);
}
setUpDimensions(diameter: number) {
this.diameter = diameter;
}
}
Goal: convert a 2 million lines of JS to strict TS
Constraints
- must be incremental
- can't ever manually refactor all the code
Goal: convert millions of lines of JS to strict TS
Unusualy Goal!
Bad Solutions:
- JS --> manually convert to strict TS (manual)
- JS --> loose TS (manual) --> strict TS (manual)
Weird Solution (ours, coming later)
Existing solutions for loose TS --> strict TS:
- TS Team's solution
- VS Code Team's solution
Mostly manual!
Converted todo loc in todo weeks
remaining "TODO"s where !
is used: todo
todo link to GitHub issue todo fact check
GH issue: microsoft/vscode#60565
2 tsconfigs:
- main tsconfig (loose) actually compiles the project
- supplementary tsconfig (strict) for type-checking subset of files
over time:
- add more files to
includes
in supplementary tsconfig until all files checked
First convert JS to loose TypeScript:
- rename files *.ts -> *.js
- declare properties for classes
Then convert loose TS to strict TS:
- tell TS compiler "I know what I'm doing"
- teams can incrementally remove "todos"
- tell TS compiler "I know what I'm doing"
type TODO = any
const sum = (n1: TODO, n2: TODO) => n1 + n2
- teams can incrementally remove "todos"
type TODO = never
- tell TS compiler "I know what I'm doing"
const arr = [1, 2, 3];
while (arr.length) {
console.log(arr.pop() ! + 1); // Error
}
Note the bang:
!
- teams can incrementally remove "todos"
Find and remove all instances of ' !': %s/ !//g
Good:
- All new code is checked
- Completely automated and fast
- Relatively easy to add types incrementally
Bad:
- Assertions cause us to miss obvious bugs!
type Dog = { bark(): void };
let fido: Dog;
fido !.bark(); // assertion hides the mistake
- identify difference between "TS needs more info here" and "this is an obvious bug"
- incorporate something like TypeWhiz into the workflow
- identify portions of codebases most in need of manual typing