eslint-plugin-typed-jsdoc: or if type errors were lint errors
I wanted TypeScript's type checking without switching languages.
Types at API boundaries catch real bugs. Editor autocomplete powered by type inference is standard practice. But I've been writing JavaScript for over two decades. I know its quirks. I don't want to leave.
I'm not alone. Svelte rewrote their codebase from TypeScript to JavaScript with JSDoc annotations -no compilation step, full type checking via checkJs. Turbo 8 dropped TypeScript entirely, calling the type gymnastics "considerable grief."
So I built a tool that verifies your JSDoc types match what TypeScript infers -without leaving JavaScript.
I Spent Days Building the Wrong Thing
If I want to verify JSDoc comments match actual types, I need to know what the actual types are. That means parsing code, building a symbol table, tracking assignments, inferring types through control flow.
I spent days on this. Built an AST walker. Started tracking variable bindings. Got recursive function calls working. IIFEs, module imports, but while the coverage kept growing, so did the amount of tasks necessary.
Every feature I added revealed three more I needed. To give the experience I wanted, I'd need an LSP to integrate with editors. I realized I was building a TypeScript competitor in my spare time. Microsoft has hundreds of engineers on that.
Day 4: What Do I Actually Want?
I stepped back. What am I trying to accomplish?
- Find places where JSDoc types don't match inferred types
- Fix those mismatches automatically
- Add JSDoc to exported functions that don't have it
That's it. Find problems, fix problems. That's what linters do.
And why was I building type inference when tsc already does this? TypeScript's compiler analyzes plain JavaScript files. It infers types from usage. It's battle-tested against every weird JavaScript pattern.
I didn't need to build a type checker. I needed to use the one that already exists.
3 Hours Later
The @typescript-eslint/parser gives ESLint access to TypeScript's type checker:
const checker = services.program.getTypeChecker();
One line. Now I can ask "what type does TypeScript think this parameter is?" for any node in the AST. All the hard inference work? Handled.
From there, I wrote rules that compare JSDoc annotations to what TypeScript infers, flag mismatches, and auto-fix them. Run eslint --fix and your JSDoc comments get corrected.
Hats off to all the code I didn't write: type inference, parser services, caching, the linting engine itself. Millions of lines maintained by TypeScript and typescript-eslint. I wrote about 1,200 lines of glue.
Does It Work?
I ran it against 282 npm packages. 9,947 files. Found 711 type mismatches where JSDoc didn't match what TypeScript inferred.
Some packages were clean. Others had JSDoc that drifted from reality over time.
3 hours of work. 4 days of false starts taught me what I actually needed.
The Ecosystem Is the Feature
ESLint has 70 million weekly downloads. Editor integrations. CI pipelines. Ignore patterns. Configuration presets. A decade of polish.
By building an ESLint plugin instead of a standalone tool, I inherited all of that. I didn't have to think about VS Code integration or GitHub Actions or config file formats. It just works because ESLint works.
In 2015, React was exploding but JSX wasn't valid JavaScript. JSHint couldn't parse it. ESLint's pluggable parser architecture meant eslint-plugin-react could exist. That flexibility saved ESLint from decisions its creators never anticipated. I'm benefiting from the same design.
Presenting eslint-plugin-typed-jsdoc
JSDoc that matches TypeScript.
The best code is code you don't write. I wanted TypeScript's verification without TypeScript's language. I almost built a type checker. Instead I spent a few hours connecting tools that already exist.
Comments
Leave a Comment