TypeScript ESLint Typed Linting
Parent: TypeScript Expert · researched 2026-06-04T17:05:49.712Z· 10 sources · 7 concepts · skill typescript-eslint-typed-linting
A lang-js-ts reference for linting TypeScript with typescript-eslint v8: stand up an
typescript-eslint & Type-Aware Linting — flat config, `projectService`, typed rules
- A lang-js-ts reference for linting TypeScript with typescript-eslint v8: stand up an [source]
- eslint.config.js flat config, turn on type-aware (typed) linting via [source]
- parserOptions.projectService, pick the right shared config, and know which high-value rules need [source]
- type information versus which are purely syntactic. The goal: a correct, version-appropriate ESLint [source]
- setup the first time, with the typed-linting performance cost understood and scoped. Defer the [source]
- TypeScript compiler API / parserServices internals, general bundler/linter choice, tsconfig [source]
- strictness, and non-TS ESLint config to the siblings listed below. [source]
Overview
- typescript-eslint is the toolkit that lets ESLint understand TypeScript. Two packages do the work, [source]
- both re-exported from the umbrella typescript-eslint package: [source]
- @typescript-eslint/parser - replaces ESLint's default (Espree) parser so ESLint can read TS [source]
- syntax (types, generics, decorators) into an AST. [source]
- @typescript-eslint/eslint-plugin - the rules themselves (~100+), including the typed rules. [source]
- Linting is not type-checking. ESLint + typescript-eslint finds bad practices and likely bugs [source]
- (floating promises, unsafe any, dead conditions). It does not replace tsc: you still run [source]
- tsc --noEmit as the type gate. The two are complementary - tsc proves the program type-checks; [source]
- typed linting enforces opinions the compiler doesn't (e.g. "you ignored this promise"). [source]
- Version anchor (memorize - these drive "is this available / how do I configure it" questions): [source]
- > Flat config only. This reference uses eslint.config.js/.mjs. If you're on a legacy [source]
- > .eslintrc, migrate first - ESLint 9 made flat config the default and v8 of typescript-eslint [source]
- > documents it exclusively. [source]
The two config helpers (and the one thing that breaks copy-paste)
- typescript-eslint ships shared configs as arrays of flat-config objects. How you splice them in [source]
- depends on which helper you use, and the spread (...) is load-bearing: [source]
- tseslint.config(...) - typescript-eslint's own helper. Takes config objects as positional [source]
- arguments, so array-valued configs must be spread: ...tseslint.configs.recommendedTypeChecked. [source]
- Forgetting the spread passes an array where an object is expected → broken config. [source]
- defineConfig(...) from eslint/config (ESLint 9.x) - the newer, framework-native helper. [source]
- It flattens arrays for you, so you do not spread: pass tseslint.configs.recommendedTypeChecked [source]
- directly, either positionally or inside an extends: [...] array. [source]
- Both are valid in v8. Lead with whichever your project already uses; the rules and parserOptions [source]
- are identical between them. [source]
Enabling type-aware (typed) linting
- "Typed linting" means rules can call into the TypeScript type checker (parserServices / [source]
- getTypeChecker()) to reason about the types of expressions, not just their syntax. That's what [source]
- makes no-floating-promises (is this expression a Promise?) possible at all. [source]
- To turn it on you (1) extend a *TypeChecked config and (2) tell the parser how to find type info via [source]
- parserOptions.projectService: [source]
- The same setup with the newer defineConfig helper (no spread; extends: arrays): [source]
- tsconfigRootDir anchors relative tsconfig lookups to the config file's directory. Pair [source]
- projectService: true with tsconfigRootDir: import.meta.dirname (ESM) or __dirname (CJS) - omit [source]
- it and the parser resolves tsconfigs relative to the CWD, a real "works on my machine" footgun. [source]
`projectService` vs `project` (and the `EXPERIMENTAL_` history)
- For files outside any tsconfig (root config files, scripts), projectService takes an options [source]
- object instead of true: [source]
- allowDefaultProject is a glob of out-of-project files to lint with type information - no extra [source]
- tsconfig or compiler options needed. (Mechanics of parserServices/the checker API itself → [source]
- typescript-compiler-api.) [source]
Shared configs (which require type info)
- Extend a preset rather than enabling rules one by one. The ***TypeChecked** variants require typed [source]
- linting (projectService/project); the plain ones do not. [source]
- Picking: no type info → recommended (+ stylistic). Type info → recommendedTypeChecked [source]
- (+ stylisticTypeChecked). Reach for strict* only if a real share of the team is highly [source]
- TS-proficient and will tolerate the friction. [source]
High-value typed rules (and the one syntactic exception)
- These are the rules that justify paying the typed-linting cost. All but the last **require type [source]
- consistent-type-imports is the exception worth calling out: it's purely about import syntax, so it [source]
- runs without type info. It pairs with TypeScript's verbatimModuleSyntax / isolatedModules to make [source]
- type-only imports explicit and prevent a single-file transpiler from emitting a broken value import. [source]
- (The tsconfig flags themselves → typescript-compiler-config.) [source]
Turning off ESLint rules that conflict with TypeScript
- Several core ESLint rules are wrong or redundant under TypeScript - the compiler already covers them, [source]
- or they false-positive on TS syntax. The classic is no-undef: TS already errors on undefined [source]
- identifiers, and no-undef flags valid TS (global types, ambient declarations). **You don't disable [source]
- these by hand** - typescript-eslint's recommended* configs include eslintRecommended, which [source]
- turns off the core rules TS subsumes (no-undef, no-dupe-class-members, no-redeclare, etc.). [source]
- Likewise, prefer the typescript-eslint extension rules (e.g. @typescript-eslint/no-unused-vars, [source]
- @typescript-eslint/no-shadow) over the core versions, and disable the core one when you enable the [source]
Tools / Frameworks
- typescript-eslint (umbrella package) - install eslint typescript typescript-eslint (plus [source]
- @eslint/js for js.configs.recommended). Exposes tseslint.config, tseslint.parser, [source]
- tseslint.plugin, and tseslint.configs.*. [source]
- @eslint/js - provides ESLint's own js.configs.recommended base. [source]
- defineConfig / eslint/config (ESLint 9) - the framework-native flat-config helper; the [source]
- alternative to tseslint.config(). [source]
- tsgolint - a Go-based engine running typescript-eslint's typed rules natively, used as the [source]
- backend for oxlint's type-aware preview. The fast-typed-linting frontier. [source]
- Biome / oxlint - Rust-based all-in-one lint/format tools. Fast, but see Anti-Patterns for the [source]
- typed-linting gap. *Choosing between them and ESLint is out of scope → [source]
- javascript-build-tooling-bundlers.* [source]
Methodology
- Start from a preset, not hand-rolled rules. Extend recommended (no types) or [source]
- recommendedTypeChecked (types) and only add/override specific rules afterward. [source]
- Decide if you want typed linting. If yes, set parserOptions.projectService: true + [source]
- tsconfigRootDir: import.meta.dirname and extend a *TypeChecked config. If you only want fast [source]
- syntactic linting, stay on recommended and skip projectService entirely. [source]
- Get the helper/spread right. tseslint.config(...) → spread array configs [source]
- (...tseslint.configs.recommendedTypeChecked). defineConfig(...) → no spread. [source]
- Scope out non-TS files. Add a { files: ['**/*.js'], extends: [tseslint.configs.disableTypeChecked] } [source]
- block so plain JS / config files don't trip typed rules (or error for lacking a Program). [source]
- Enable the flagship typed rules deliberately if the preset doesn't already: at minimum [source]
- no-floating-promises and no-misused-promises - they catch real production bugs. [source]
- Keep tsc --noEmit in CI. Lint and type-check are separate gates; run both. [source]
- Verify by running eslint . and confirming typed rules fire on a known floating promise. [source]
Practical Patterns
Anti-Patterns
- Forgetting the spread under tseslint.config(). tseslint.configs.recommendedTypeChecked [source]
- (no ...) passes an array where a config object is expected → silent misconfiguration or a crash. [source]
- Spread it. (Under defineConfig, do the opposite: don't spread.) [source]
- **Enabling *TypeChecked without projectService/project.** Typed rules need a Program; without [source]
- one you get "parserOptions.project has been set … but … was not found" or the rules simply don't [source]
- run. Set projectService: true. [source]
- Running typed rules over plain JS / config files. They error (no Program) or noise. [source]
- disableTypeChecked on a **/*.js block. [source]
- Omitting tsconfigRootDir. Relative tsconfig resolution then depends on the CWD - flaky across [source]
- editor vs CLI vs CI. Always set import.meta.dirname / __dirname. [source]
- Treating eslint as the type-checker. Linting ≠ tsc. Typed linting catches practices, not [source]
- type errors; you still run tsc --noEmit. [source]
- Hand-disabling no-undef and friends. The recommended* configs already do this via [source]
- eslintRecommended. Manually toggling core rules that conflict with TS just duplicates that. [source]
- Expecting Biome/oxlint to fully replace typed linting. As of 2025–26, Biome 2.0 added type [source]
- inference (~85% of typescript-eslint's typed coverage) and oxlint added a tsgolint-backed [source]
- type-aware preview - but the flagship typed rules (no-floating-promises, the no-unsafe-* [source]
- family) are exactly the guarantees a purely-syntactic Rust linter loses. If those rules matter, [source]
- keep typescript-eslint. (Picking a toolchain overall → javascript-build-tooling-bundlers.) [source]
- Using the all config. Many rules conflict; it's not semver-stable. Extend a recommended* or [source]
- strict* preset instead. [source]
- consistent-type-imports "needs type info." It doesn't - it's syntactic. Don't gate it behind [source]
Troubleshooting
- "You have used a rule which requires type information, but … parserOptions … not set" → you [source]
- extended a *TypeChecked config without projectService/project. Add projectService: true. [source]
- "… was not found by the project service. Consider … allowDefaultProject" → an out-of-project [source]
- file (a root config) hit a typed rule. Add it to allowDefaultProject, or disableTypeChecked for [source]
- Lint is very slow / high memory → typed linting builds the TS program. Levers: prefer [source]
- projectService over project; narrow files; disableTypeChecked on non-source globs; run [source]
- typed lint as its own CI step; ensure tsconfig include isn't pulling in the world. [source]
- Rules don't fire / config seems ignored → flat config resolution. Confirm the file is [source]
- eslint.config.js/.mjs, that you're on ESLint 9 (flat-config default), and (under [source]
- tseslint.config) that array configs are spread. [source]
- no-undef flags valid TS (global types, ambient decls) → you re-enabled it manually; the [source]
- recommended* configs disable it on purpose. Remove the override. [source]
- EXPERIMENTAL_useProjectService errors / deprecation → renamed to projectService in v8; [source]
- import type rule won't activate → that's consistent-type-imports, which is syntactic; it [source]
- needs no projectService. Make sure it's actually enabled in rules, not assumed via a preset. [source]
- Editor and CLI disagree on types → divergent tsconfigs. projectService uses the editor's [source]
- Project Service APIs, which helps; ensure both resolve the same tsconfig.json via [source]
References
- typescript-eslint - Getting Started (flat config quick start): https://typescript-eslint.io/getting-started/ [source]
- typescript-eslint - Typed Linting (projectService, recommendedTypeChecked): https://typescript-eslint.io/getting-started/typed-linting/ [source]
- typescript-eslint - Shared Configs (every preset, type-info matrix): https://typescript-eslint.io/users/configs/ [source]
- typescript-eslint - Rules (the 💭 "requires type information" marker): https://typescript-eslint.io/rules/ [source]
- typescript-eslint - @typescript-eslint/parser (projectService, project, tsconfigRootDir): https://typescript-eslint.io/packages/parser/ [source]
- typescript-eslint - "Typed Linting with Project Service" blog: https://typescript-eslint.io/blog/project-service/ [source]
- typescript-eslint - Announcing v8 (EXPERIMENTAL_useProjectService → projectService): https://typescript-eslint.io/blog/announcing-typescript-eslint-v8/ [source]
- ESLint - Configuration Files (flat config, defineConfig): https://eslint.org/docs/latest/use/configure/configuration-files [source]
- Biome 2.0 - type inference / type-aware rules: https://biomejs.dev/blog/biome-v2-0-0/ [source]
- oxc - Oxlint Type-Aware Preview (tsgolint): https://oxc.rs/blog/2025-08-17-oxlint-type-aware [source]
Children
- No children recorded.