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

eslint-plugin-ts-type-preferences

v1.0.6

Published

ESLint plugin for TypeScript with two rules: prefer interface extends over type intersection and prefer merged type literal over intersection

Readme

eslint-plugin-ts-type-preferences

An ESLint plugin for TypeScript with two rules:

  • Prefer interface extends when object inheritance is expressed via intersections.
  • Prefer merging object-only intersections into a single type literal.

Why

This plugin is based on Matt Pocock's article "Type vs Interface: Which Should You Use?" The article recommends using interface specifically for object inheritance because extends lets TypeScript reuse cached interface information and is slightly more optimal than & intersections.

It also recommends defaulting to type for everything else due to interface declaration merging pitfalls. These rules follow that guidance by only targeting object inheritance and object-only intersections expressed via &.

Reference: https://www.totaltypescript.com/type-vs-interface-which-should-you-use

Rules

prefer-interface-extends-over-type-intersection

Reports type aliases that intersect at least one type reference with object literals and suggests an equivalent interface declaration.

It will:

  • Convert type A = B & { ... } to interface A extends B { ... }.
  • Only emit an interface when the intersection includes at least one type reference.
  • Leave intersections that include unsupported types (like unions, primitives, or indexed access types) unchanged.

The rule is fixable and will automatically rewrite the type alias to an interface.

prefer-merged-type-literal-over-intersection

Reports object-only intersections and suggests merging them into a single type literal.

It will:

  • Convert type A = { ... } & { ... } to type A = { ... }.
  • Keep the declaration as a type instead of converting to an interface.

Examples

prefer-interface-extends-over-type-intersection

Invalid

type WithId = {
  id: string;
};

type User = WithId & {
  name: string;
};

Valid (after fix)

type WithId = {
  id: string;
};

interface User extends WithId {
  name: string;
}

Invalid (multiple bases)

type Audited = { createdAt: Date };
type SoftDelete = { deletedAt?: Date };

type Record = Audited &
  SoftDelete & {
    id: string;
  };

Valid (after fix)

type Audited = { createdAt: Date };
type SoftDelete = { deletedAt?: Date };

interface Record extends Audited, SoftDelete {
  id: string;
}

Invalid (generic base)

type Paginated<T> = {
  items: T[];
  total: number;
};

type Users = Paginated<User> & {
  page: number;
};

Valid (after fix)

type Paginated<T> = {
  items: T[];
  total: number;
};

interface Users extends Paginated<User> {
  page: number;
}

prefer-merged-type-literal-over-intersection

Invalid (multiple object intersections)

type A = {
  fieldA: string;
} & {
  fieldB: number;
};

Valid (after fix)

type A = {
  fieldA: string;
  fieldB: number;
};

Invalid (methods and signatures)

type Handler = {
  handle(value: string): void;
  [key: string]: number;
  (): void;
} & {
  status: "ok" | "error";
};

Valid (after fix)

type Handler = {
  handle(value: string): void;
  [key: string]: number;
  (): void;
  status: "ok" | "error";
};

Invalid (nested intersection)

type Flags = ({
  enabled: boolean;
} & {
  level: number;
}) & {
  label: string;
};

Valid (after fix)

type Flags = {
  enabled: boolean;
  level: number;
  label: string;
};

Options

prefer-interface-extends-over-type-intersection

mergeObjects (boolean, default: true)

When true, object literal types in the intersection are merged into the interface body.

When false, the rule only converts intersections that contain at most one object literal type and at least one type reference. Intersections with multiple object literals are left unchanged.

Left untouched when mergeObjects: false

type A = {
  fieldA: string;
} & {
  fieldB: number;
};

prefer-merged-type-literal-over-intersection

No options.

Installation

pnpm add -D eslint-plugin-ts-type-preferences

Usage

Flat config (ESLint v9+)

import tsTypePreferences from "eslint-plugin-ts-type-preferences";

export default [
  {
    plugins: {
      "ts-type-preferences": tsTypePreferences,
    },
    rules: {
      "ts-type-preferences/prefer-interface-extends-over-type-intersection":
        "error",
      "ts-type-preferences/prefer-merged-type-literal-over-intersection":
        "error",
    },
  },
];

Legacy config (.eslintrc)

{
  "plugins": ["ts-type-preferences"],
  "rules": {
    "ts-type-preferences/prefer-interface-extends-over-type-intersection": "error",
    "ts-type-preferences/prefer-merged-type-literal-over-intersection": "error"
  }
}

Recommended config

Flat config (ESLint v9+)

import tsTypePreferences from "eslint-plugin-ts-type-preferences";

export default [
  tsTypePreferences.configs.recommended,
];

Legacy config (.eslintrc)

{
  "extends": ["plugin:ts-type-preferences/recommended"]
}

Limitations

When merging object literals, the fixer preserves all member kinds and order, but it does not attempt to resolve duplicate or conflicting members. If you rely on advanced merging semantics, review the fix output before applying.

Development

pnpm install
pnpm test