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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@rccyx/eslint-plugin-classes

v1.1.1

Published

Deterministic ES6 class discipline for humans and AI swarms

Downloads

553

Readme

@rccyx/eslint-plugin-classes

Large teams and AI-generated code both create the same problem: classes slowly rot. Members drift out of order, private internals leak into public surfaces, and scanning a file gets exhaustive. This rule forces a strict, predictable layout so intent is never ambiguous. It stabilizes structure across humans and machines and prevents the entropy that usually builds up in big TypeScript codebases.

Enforce a readable class layout with explicit visibility, consistent naming, and a fixed member order.

Install

pnpm i -D eslint @rccyx/eslint-plugin-classes
# or
bun add -d eslint @rccyx/eslint-plugin-classes

Peer Dependencies

This plugin requires:

  • eslint: >=8.57 <11
  • typescript: ^5

Flat config usage

// eslint.config.js
import classStructure from "@rccyx/eslint-plugin-classes";

export default [
  {
    files: ["**/*.ts", "**/*.tsx"],
    plugins: { "class-structure": classStructure },
    rules: {
      "class-structure/enforce": "error",
    },
  },
];

What it enforces

1. Member ordering

Members must appear in this exact order:

  1. Static fields
  2. Static methods (including static accessors)
  3. Instance fields (including method-like fields: foo = () => {})
  4. Constructor
  5. Instance accessors (get / set)
  6. Public instance methods
  7. Protected instance methods
  8. Private instance methods
// Good
class Example {
  static readonly VERSION = "1.0";
  static init() {}
  static get meta() {
    return "ok";
  }

  public id = "x";
  private _cache = new Map();

  constructor(id: string) {
    this.id = id;
  }

  get state() {}
  set state(v) {}

  public run() {}
  protected compute() {}
  private _cleanup() {}
}

2. Visibility requirements

All non-accessor instance methods and all method-like fields must explicitly declare visibility.

// Bad
class Bad {
  foo() {} // error
  handler = () => {}; // error
}

// Good
class Good {
  public foo() {}
  public handler = () => {};
}

Exceptions:

  • Constructors may omit visibility
  • Accessors may omit visibility
  • Static members may omit visibility

3. Naming rules

Private members

Private fields and methods must start with # or _.

// Bad
private hidden() {}
private value: number = 1;

// Good
private _hidden() {}
private #value: number;

Public members

Public members must not start with _ or #.

// Bad
public _call() {}
_id = 1;

// Good
public call() {}
id = 1;

Note: Accessors may start with _ without error.

Constants

Static readonly fields must be SCREAMING_SNAKE_CASE.

// Bad
static readonly defaultLimit = 3;

// Good
static readonly MAX_LIMIT = 3;
private static readonly _CACHE_SIZE = 10;

4. Structural constraints

No fields after constructor

// Bad
class Bad {
  constructor() {}
  value = 1; // error
}

No static/instance interleaving

Static members of a category must all appear before instance members of that category.

// Bad
static A = 1;
a = 2;
static B = 3; // error

Class must have behavior

A class must declare at least one method, accessor, or method-like field.

// Bad
class Bad {
  value = 1; // no behavior
}

What it flags

class Bad {
  value = 1;
  static init() {} // instance field before static method → error

  foo() {} // visibility missing
  private hidden() {} // private name must start with # or _
  public _call() {} // public name must not start with _ or #
  static readonly limit = 3; // constant naming error

  constructor() {}
  data = 2; // field after constructor → error

  public run() {}
  get name() {} // accessor after methods → error
}

What passes

class Good {
  static readonly VERSION = "1.0";
  static init() {}
  static get meta() {
    return "ok";
  }

  public id = "x";
  protected mode = 0;
  private _cache = new Map<string, unknown>();
  public handler = () => {};

  constructor(id: string) {
    this.id = id;
  }

  get state() {
    return this.mode;
  }

  set state(v: number) {
    this.mode = v;
  }

  public run() {}
  protected compute() {}
  private _cleanup() {}
}

Notes

  • Method-like fields (foo = () => {}) behave like fields for ordering and methods for visibility.
  • Static accessors behave like static methods for ordering.
  • Accessors may start with _ without violating public naming rules.
  • Hash-private members (#name) automatically count as private.

License

Apache-2.0 © @rccyx