TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
54: Integrating TypeScript with ESLint
One of the first things I see when I onboard a new dev to a project is a .eslintrc file that looks like it was copy-pasted from a 2018 blog post. Usually, they've got ESLint installed, but it's fighting with TypeScript. You'll see those annoying red squiggles in the IDE that aren't coming from the compiler, but from a linter that doesn't actually understand what an interface is. It's frustrating, and usually, the reaction is to just turn off the rules that are complaining. Don't do that.
The struggle of treating TypeScript like JavaScript
The naive way to set this up is to treat TypeScript as just "JavaScript with some extra noise." In this scenario, you install ESLint and maybe a few basic plugins, but you leave the parser as the default. Because the default ESLint parser only knows standard JavaScript, it hits a type annotation or a generic and essentially panics. To "fix" this, people often start adding eslint-disable comments everywhere or disabling rules that seem to trigger incorrectly on TS files.
Even if you get the basic @typescript-eslint/parser installed, most people stop there. They get "syntax-aware" linting, which is fine for catching unused variables or formatting issues. But they're missing the real magic. For example, consider this snippet:
async function getUserData(id: string) {
const user = await fetchUserFromCache(id);
return user;
}
function fetchUserFromCache(id: string) {
return { id, name: "Alex" }; // This is a synchronous function!
}
If you're just using basic linting, ESLint sees the await keyword and thinks, "Yep, that's valid JavaScript syntax." TypeScript's compiler also won't stop you; it'll just implicitly wrap the result in a Promise. But from a code quality perspective, this is a "smell." You're awaiting something that isn't a promise, which adds unnecessary overhead and signals a misunderstanding of the data flow.
The power of type-aware linting
The better way is to implement "type-aware linting." This is where we tell ESLint not just how to parse the code, but how to actually ask the TypeScript compiler about the types of the variables it's looking at. This requires a bit more configuration in your parserOptions—specifically, you have to point ESLint to your tsconfig.json.
When you do this, you unlock a whole different class of rules. In the example above, a type-aware rule like @typescript-eslint/await-thenable would immediately flag that await fetchUserFromCache(id) is useless because fetchUserFromCache returns a plain object, not a Promise. I love this because it catches logic errors that are technically "legal" TypeScript but are almost certainly bugs or remnants of a refactor gone wrong.
The cost of the "Full" setup
I should be honest with you: there is a trade-off here. Type-aware linting is significantly slower. Why? Because ESLint now has to essentially run a partial TypeScript compilation in the background to understand the types of your expressions. On a small project, you won't notice. On a massive monorepo with thousands of files, your IDE might start to lag, or your CI pipeline might take an extra few minutes.
If you find the performance hit is too high, the professional compromise is to split your linting. You run the "cheap" syntax rules on every file save, but you run the "expensive" type-aware rules as a pre-commit hook or during your CI build. It's a balance between developer velocity and absolute correctness. Personally, I'd rather wait an extra 30 seconds in CI than spend an hour debugging why a "synchronous" function is behaving like an async one.
📋 Practical Task
Exercise: Fixing the Order Processing Service
You have been handed a small project containing an OrderProcessingService.ts file. The project has ESLint and TypeScript installed, but it is currently configured for "syntax-only" linting. The compiler is happy, but the code is sloppy.
Your goals:
- Modify the
.eslintrc.js(or.eslintrc.json) to enable type-aware linting by adding theproject: true(or path totsconfig.json) insideparserOptions. - Enable the
@typescript-eslint/no-floating-promisesand@typescript-eslint/await-thenablerules. - Identify and fix the three linting errors that appear in
OrderProcessingService.ts:- An
awaitcall on a function that returns a non-promise value. - A Promise-returning function called without a
.catch()or anawait(a floating promise). - An unnecessary type cast (e.g.,
as any) that the linter can now prove is redundant because it knows the actual type.
- An
There are no comments for now.