npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@williamthorsen/eslint-config-typescript

v8.5.0

Published

ESLint config for TypeScript, JSON & YML

Readme

@williamthorsen/eslint-config-typescript

Flat-config ESLint preset for TypeScript projects. Covers TypeScript, JavaScript, JSON, YAML, and package.json from a single default export, with opt-in framework configs for React, Next.js, JSX A11y, Vitest, and React Testing Library.

Release notes — v8.5.0 (2026-08-05)

🎉 Features

  • Disable conflicting class-member-order rule

    Disables unicorn/consistent-class-member-order because it conflicts with a similar @typescript-eslint rule.

Installation

pnpm add -D @williamthorsen/eslint-config-typescript eslint typescript

Requires ESLint 9+ and TypeScript 5+.

Quick start

// eslint.config.ts
import { defineConfig } from 'eslint/config';

import tsConfig from '@williamthorsen/eslint-config-typescript';

export default defineConfig(tsConfig, {
  languageOptions: {
    parserOptions: {
      // Anchor type-aware linting at your repo root.
      tsconfigRootDir: import.meta.dirname,
    },
  },
  // your overrides
});

Everything this package exports is typed with ESLint core's own Config, so the same composition typechecks unchanged in an eslint.config.ts — no tseslint.config(), type assertion, or widening cast.

Type-aware linting

The TypeScript rules are type-aware, and the preset enables typescript-eslint's project service (parserOptions.projectService), so each file's owning tsconfig.json is discovered automatically — you do not set parserOptions.project. Two requirements follow:

  • Every linted .ts/.tsx file must belong to a discoverable tsconfig.json through its include. A file outside every project — for example a test directory excluded from your build config — must be added to some tsconfig.json's include, or ESLint reports it as not found in any project.
  • Set tsconfigRootDir (as in Quick start) to anchor resolution at your repo root. Without it, resolution falls back to the current working directory, which varies by how ESLint is launched.

Migrating from parserOptions.project

This section covers the parser change alone. For the complete v5 → v6 upgrade — the Node and ESLint floors, the package bump, and post-upgrade cleanup — see Migrating to v6.

Earlier versions left type-information wiring to the consumer: you set parserOptions.project and usually kept a dedicated tsconfig.eslint.json. This version supplies projectService itself, so:

  1. Remove parserOptions.project from your ESLint config. Leaving it set now throws Enabling "project" does nothing when "projectService" is enabled.
  2. Fold any lint-only tsconfig.eslint.json include entries into the real tsconfig.json, then delete the tsconfig.eslint.json. Widening include is safe when the config is typecheck-only.
  3. Keep only tsconfigRootDir: import.meta.dirname in your parserOptions.

What's included

The default export bundles configs for the following surfaces:

| Surface | File pattern | Notable plugins | | -------------- | ------------------------------ | ----------------------------------------------------------------- | | TypeScript | **/*.{ts,cts,mts,tsx} | typescript-eslint (type-aware), sky-pilot | | JavaScript | **/*.{js,cjs,mjs,jsx} | core rules, JS-specific conventions | | Cross-cutting | all code files | eslint-comments, import, n, simple-import-sort, unicorn | | JSON / JSON5 | **/*.{json,json5} | jsonc | | YAML | **/*.{yaml,yml} | yml | | package.json | **/package.json | package-json (recommended + stylistic) | | Tests | **/*.{spec,test}.{js,ts,...} | strict TypeScript rules relaxed |

Test files (*.spec.* / *.test.*) have several strict rules disabled (e.g., no-unsafe-assignment, unbound-method, no-extraneous-class) so spec files don't fight the type checker. Declaration files (*.d.ts) have import/no-duplicates turned off.

Granular config access

The default export is the catch-all "everything on" preset. For à la carte composition, import individual configs:

import { defineConfig } from 'eslint/config';

import { configs } from '@williamthorsen/eslint-config-typescript';

export default defineConfig(
  configs.typeScript,
  configs.import,
  // skip configs.unicorn entirely
);

| Config | Targets | | -------------------------- | ------------------------------------------ | | configs.javaScript | core JavaScript rules | | configs.typeScript | TypeScript rules (type-aware) | | configs.eslintComments | eslint-disable comment hygiene | | configs.import | import order and resolution | | configs.n | Node.js (eslint-plugin-n) rules | | configs.simpleImportSort | sorted imports | | configs.unicorn | eslint-plugin-unicorn rules | | configs.json | JSON rules | | configs.json5 | JSON5 rules — apply after configs.json | | configs.packageJson | package.json rules | | configs.yaml | YAML rules |

Framework configs (lazy-loaded)

Framework-specific configs are exposed via createConfig so their plugin dependencies (eslint-plugin-react, @next/eslint-plugin-next, etc.) load only when used. Every factory resolves to a config array, so spread each one (or pass them through extends):

import { defineConfig } from 'eslint/config';

import config, { createConfig } from '@williamthorsen/eslint-config-typescript';

export default defineConfig(
  config,
  ...(await createConfig.react()),
  ...(await createConfig.jsxA11y()),
  ...(await createConfig.next()),
);

Scope the test-oriented factories to your test files rather than spreading them across the whole project. Applied to ordinary source, vitest/require-hook reports on every top-level statement:

import { defineConfig } from 'eslint/config';

import config, { createConfig, patterns } from '@williamthorsen/eslint-config-typescript';

export default defineConfig(config, {
  files: patterns.testFiles,
  extends: [await createConfig.vitest(), await createConfig.reactTestingLibrary()],
});

patterns.testFiles covers JavaScript as well as TypeScript test files. Three of the Vitest rules read type information — unbound-method, valid-title, and prefer-describe-function-title — and each aborts the ESLint run rather than degrading when a file has no parser services, so createConfig.vitest() disables all three on JavaScript globs. That makes the scoping above safe whether or not your JavaScript test files get a type-aware parser.

| Method | Loads | | ------------------------------------ | -------------------------------------------------- | | createConfig.react() | eslint-plugin-react, eslint-plugin-react-hooks | | createConfig.next() | @next/eslint-plugin-next | | createConfig.jsxA11y() | eslint-plugin-jsx-a11y | | createConfig.reactTestingLibrary() | eslint-plugin-testing-library | | createConfig.vitest() | @vitest/eslint-plugin |

These plugins are declared as devDependencies of this package. Install them yourself in projects that use them.

createConfig.react() pins settings.react.version to a recent default, because eslint-plugin-react's 'detect' mode is incompatible with ESLint 10 (it calls a removed API). Override it to match your React version by appending a settings block:

export default defineConfig(config, ...(await createConfig.react()), {
  settings: { react: { version: '18.3' } },
});

File patterns

For composing your own scoped configs without re-deriving the globs:

import { defineConfig } from 'eslint/config';

import { patterns } from '@williamthorsen/eslint-config-typescript';

export default defineConfig({
  files: patterns.typeScriptFiles,
  rules: {
    // TypeScript-only overrides
  },
});

| Constant | Value | | ------------------------------- | ---------------------------------------------------------------------------- | | patterns.javaScriptFiles | ['**/*.{js,cjs,mjs,jsx}'] | | patterns.typeScriptFiles | ['**/*.{ts,cts,mts,tsx}'] | | patterns.codeFiles | both of the above | | patterns.testFiles | ['**/*.{spec,test}.{js,cjs,mjs,jsx}', '**/*.{spec,test}.{ts,cts,mts,tsx}'] | | patterns.javaScriptExtensions | ['{js,cjs,mjs,jsx}'] | | patterns.typeScriptExtensions | ['{ts,cts,mts,tsx}'] | | patterns.codeExtensions | both |

Advisory rule severities

advisoryRuleSeverities maps the rules this config sets to 'warn' because they report style and modernization advice rather than defects — @typescript-eslint/no-deprecated, most of the unicorn prefer-* set, and their neighbours. Rules this config disables outright are not included.

Use it with @williamthorsen/strict-lint to exempt them from error promotion, so a stricter CI run still fails on genuine defects only:

// .config/strict-lint.config.ts
import { advisoryRuleSeverities } from '@williamthorsen/eslint-config-typescript';
import { defineConfig } from '@williamthorsen/strict-lint/config';

export default defineConfig({
  maxSeverity: { ...advisoryRuleSeverities },
});

Or spread it into an ordinary flat-config rules block to set those severities directly:

import { advisoryRuleSeverities } from '@williamthorsen/eslint-config-typescript';

export default [{ rules: { ...advisoryRuleSeverities } }];

An unscoped block applies 'warn' everywhere, including in test files, where this config turns unicorn/consistent-function-scoping and unicorn/no-useless-undefined off. Scope the block with files to keep those exceptions.

Peer dependencies

| Dependency | Required | | ------------ | -------- | | eslint | >=10 | | typescript | >=5 |

License

ISC.