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

ts-operator-overload

v0.1.6

Published

Typed operator overloading for TypeScript using `// @operator...` annotations

Downloads

815

Readme

ts-operator-overload

Typed operator overloading for TypeScript using // @operator... annotations

Write normal operators in your code (a + b, ~x, x += y) and let the transformer/plugin rewrite them to method calls with type-aware resolution

Table of Contents

What This Is

ts-operator-overload lets you define operator behavior on your own types with method annotations

Example:

class Vec {
    constructor(public x: number) {
    }

    // @operator+
    add(y: number): Vec {
        return new Vec(this.x + y)
    }
}

const v = new Vec(1)
const r = v + 2 // rewritten to v.add(2)

This package supports:

  • compile-time rewriting for tsc transformer usage
  • editor-time diagnostics and type feedback through a tsserver plugin

Quick Start (2 Minutes)

1) Install

npm install ts-operator-overload

2) Annotate methods

class A {
    constructor(public x: number) {
    }

    // @operator+
    add(y: number): string {
        return `Adding ${y} to ${this.x}`
    }
}

const c = new A(5)
const d = c + 5 // becomes c.add(5)

3) Enable plugin in tsconfig.json for editor behavior

{
  "compilerOptions": {
    "plugins": [
      {
        "name": "ts-operator-overload",
        "mode": "shadow"
      }
    ]
  }
}

How It Chooses Which Method to Call

For a binary expression like:

A + B

the resolver checks in this order and picks the first match:

  1. A.add(B)
  2. A.add(A, B) as a static method
  3. B.add(A, B) as a static method

So left-operand methods are preferred before reverse/right-side fallback

For unary operators, the resolver checks:

  1. zero-arg form, for example x.unaryBitNot()
  2. one-arg self form, for example x.unaryBitNot(x)

For compound assignment and increments:

  • x += y resolves as assignment using @operator+
  • x++ and ++x resolve as assignment using @operator+ with 1
  • similarly -= and -- resolve via @operator-

Smart fallbacks are included for missing operators:

  • if a != b is missing but a == b exists, it rewrites to !(a == b)
  • for comparisons, it prefers single-op negation fallbacks like a >= b -> !(a < b)
  • if a - b is missing but a + ... exists, it rewrites as a + (-b) (using native or overloaded unary -)

Supported Operators

Binary operators

  • @operator+
  • @operator-
  • @operator*
  • @operator/
  • @operator%
  • @operator**
  • @operator==
  • @operator!=
  • @operator===
  • @operator!==
  • @operator>
  • @operator>=
  • @operator<
  • @operator<=
  • @operator&&
  • @operator||
  • @operator??
  • @operator&
  • @operator|
  • @operator^
  • @operator<<
  • @operator>>
  • @operator>>>

Unary operators

  • @operator+
  • @operator-
  • @operator!
  • @operator~

Compound and increment forms

Mapped through annotated binary operators:

  • +=, -=, *=, /=, %=, **=
  • &&=, ||=, ??=
  • &=, |=, ^=, <<=, >>=, >>>=
  • ++, --

Valid Annotation Signatures

The annotation validator enforces accepted method shapes

Binary form (@operator+, etc)

Accepted:

// one-arg left form
add(b: number): Result

// two-arg left form (must be static)
static add(a: ThisType, b: number): Result

// two-arg right/reverse form (on rhs type, must be static)
static add(a: number, b: ThisType): Result

Unary form (@operator!, @operator~, unary + and -)

Accepted:

unaryNot(): Result
unaryNot(a: ThisType): Result

Rejected example (custom error):

// @operator~
unaryBitNot(a: Ops, b: number): string // invalid

TSConfig / tsserver Plugin

Set in compilerOptions.plugins:

{
  "compilerOptions": {
    "plugins": [
      {
        "name": "ts-operator-overload",
        "mode": "shadow"
      }
    ]
  }
}

Modes:

  • suppress suppresses operator diagnostics when a valid overload exists
  • shadow builds a rewritten virtual program for richer types, completions, and inlay hints
  • hybrid responds fast like suppress, then warms shadow typing after edits settle

Performance options for shadow mode:

  • shadowScope: "file" (default, rewrites only requested file) or "project"
  • maxShadowFiles: auto-fallback threshold for large projects (0 disables threshold)
  • autoFallbackToSuppress: true by default
  • shadowFeatures: selectively enable expensive editor features

Hybrid options:

  • hybridDebounceMs: wait time after edits before warm shadow rebuild (default 800)
  • hybridWarmOn: per-feature shadow enablement in hybrid mode
  • hybridMaxBuildMs: optional budget for a warm rebuild before it is treated as failed
  • hybridFailureDisableAfter: disable hybrid warmup temporarily after repeated failures
  • hybridCooldownMs: cooldown duration before retrying warmups

Example hybrid config:

{
  "compilerOptions": {
    "plugins": [
      {
        "name": "ts-operator-overload",
        "mode": "hybrid",
        "shadowScope": "file",
        "maxShadowFiles": 400,
        "hybridDebounceMs": 800,
        "hybridWarmOn": {
          "quickInfo": true,
          "completions": true,
          "completionDetails": true,
          "inlayHints": false,
          "diagnostics": false
        }
      }
    ]
  }
}

Large-project recommended config (fast editor feedback):

{
  "compilerOptions": {
    "plugins": [
      {
        "name": "ts-operator-overload",
        "mode": "shadow",
        "shadowScope": "file",
        "maxShadowFiles": 400,
        "autoFallbackToSuppress": true,
        "shadowFeatures": {
          "diagnostics": true,
          "quickInfo": true,
          "completions": false,
          "completionDetails": false,
          "inlayHints": false
        }
      }
    ]
  }
}

If you only want fast error suppression and don't need shadow-typed editor features, use "mode": "suppress".

Programmatic API

From index.d.ts:

  • createOperatorOverloadTransformer(program, options?)
  • createTscTransformer(program, options?)
  • shouldSuppressOperatorDiagnostic(diagnostic, program, options?)
  • tsserverPlugin

Current option field:

  • allowRightOperand?: boolean

Diagnostics and Custom Errors

Standard TS operator diagnostics may appear when no valid overload is found, such as:

  • TS2362
  • TS2363
  • TS2365
  • TS2367

This package also emits custom annotation validation diagnostics:

  • TS93001 (source: ts-operator-overload)

Typical causes:

  • unsupported @operator... token
  • invalid arity for unary/binary operator
  • invalid two-arg form that does not include the owning type

Examples

Reverse add fallback

class A {
    constructor(public x: number) {
    }
}

class B {
    constructor(public y: number) {
    }

    // @operator+
    add(a: A, b: B): string {
        return `${a.x}+${b.y}`
    }
}

const a = new A(1)
const b = new B(2)
const out = a + b // falls back to b.add(a, b)

Equality overload

class EqClass {
    // @operator==
    eq(y: number): string {
        return `${y}`
    }
}

const r = new EqClass() == 5 // rewritten to .eq(5), type string
r.split("")

IDE Quick Setup (VS Code and JetBrains)

Use each project's local TypeScript version so the plugin behavior matches the project dependencies.

VS Code (fast path)

  1. Open the target project folder (example: test/vscode-basic)
  2. Install dependencies in that folder
  3. Point VS Code to local TypeScript at node_modules/typescript/lib
  4. Run TypeScript: Select TypeScript Version and choose Use Workspace Version

Example commands:

npm --prefix test/vscode-basic install

If needed, set this in .vscode/settings.json inside that project:

{
  "typescript.tsdk": "./node_modules/typescript/lib"
}

JetBrains IDEs (WebStorm / IntelliJ)

  1. Open the target project folder in the IDE
  2. Install dependencies in that folder
  3. Go to Settings > Languages & Frameworks > TypeScript
  4. Enable TypeScript service
  5. Set TypeScript package to the local path: <project>/node_modules/typescript

Example command:

npm --prefix test/nextjs-basic install

This ensures editor diagnostics and types use the project's own TypeScript instead of a global version.

Troubleshooting

  • operator not rewritten: check method annotation and signature shape
  • type still looks native in editor: ensure plugin is loaded and mode is shadow
  • unexpected custom error TS93001: method has an invalid operator signature
  • right-side fallback not used: left matches always win and number-like rhs fallback is restricted

Development and Tests

Run full matrix:

npm install
npm test

Included test areas:

  • test/tsc-* transformer output and expected-error cases
  • test/tsserver-smoke diagnostic suppression behavior
  • test/tsserver-typed quick-info, inlay, and completion type propagation
  • test/eslint-basic eslint integration and TypeScript parsing/linting compatibility
  • test/vite-basic and test/nextjs-basic integration builds