class-methods-use-this

Require class methods to use this

A method that never touches this doesn't need an instance, but callers still have to create one to call it. Make it static, or move it out of the class as a plain function.

The rule checks methods, getters, setters, and fields that hold a function. Constructors and static members aren't checked. Using super counts as using this.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"class-methods-use-this" = "error" # or "warn"

Every option, set to its default. The ones that are commented out are unset by default.

bunfig.toml
[lint.rules]
"class-methods-use-this" = ["error", {
  exceptMethods = [], # list of strings
  enforceForClassFields = true,
  ignoreOverrideMethods = false,
  # ignoreClassesWithImplements = <"all" or "public-fields">,
}]

Examples#

example.js1 problem
class Cart {
  formatPrice(cents) {
    return "$" + (cents / 100).toFixed(2);
  }
}
example.jsNo problems
class Cart {
  static formatPrice(cents) {
    return "$" + (cents / 100).toFixed(2);
  }

  total() {
    return Cart.formatPrice(this.items.reduce((sum, item) => sum + item.cents, 0));
  }
}

Options#

OptionDefaultWhat it does
exceptMethods[]Method names to skip. Write a private method with its #.
enforceForClassFieldstrueAlso check fields that hold a function, like format = () => {}.
ignoreOverrideMethodsfalseDon't check members marked override in TypeScript.
ignoreClassesWithImplementsnot set"all" skips every member of a class that has an implements clause. "public-fields" skips only its public members.

A framework may call a method on the instance whether it uses this or not:

example.jswith { "exceptMethods": ["render", "#label"] }No problems
class Spinner extends Component {
  render() {
    return "Loading";
  }

  #label() {
    return "Spinner";
  }
}
example.jswith { "enforceForClassFields": false }No problems
class Cart {
  formatPrice = cents => "$" + (cents / 100).toFixed(2);
}
example.tswith { "ignoreOverrideMethods": true }No problems
class Guest extends User {
  override displayName() {
    return "Guest";
  }
}

With "public-fields", the interface may ask for name(), but it can't ask for the private prefix():

example.tswith { "ignoreClassesWithImplements": "public-fields" }1 problem
class Guest implements Named {
  name() {
    return "Guest";
  }

  private prefix() {
    return "Mx.";
  }
}