Skip to content

ntnyq/prefer-object-method-syntax

Prefer method syntax for inline functions in object literals.

  • 🔧 The --fix option on the command line can automatically fix some of the problems reported by this rule.
  • 💡 Some problems reported by this rule are manually fixable by editor suggestions.

Why this rule?

The ESLint core object-shorthand rule already prefers method syntax for anonymous function expressions:

ts
const object = {
  method: function () {},
}

However, its primary responsibility is broader: it also handles property shorthand, long-form syntax, and consistency across an object. Arrow functions are allowed by default. The avoidExplicitReturnArrows option only checks a subset of block-bodied arrow functions and does not report arrows that use lexical bindings such as this, arguments, super, or new.target.

The core rule also skips named function expressions and fixes anonymous ordinary function expressions even though converting one to a method removes its constructibility and changes its prototype behavior.

This rule focuses only on inline functions used as object property values. It:

  • checks function expressions and, by default, arrow functions;
  • reports expression-bodied and block-bodied arrows;
  • still reports arrows that require lexical bindings, while withholding unsafe fixes;
  • reports named function expressions without removing their inner binding;
  • supports exact property-name exceptions;
  • keeps automatic fixing opt-in and limited to the safe subset.

To avoid duplicate reports, configure the core rule to handle only property shorthand:

ts
export default [
  {
    rules: {
      'object-shorthand': ['error', 'properties'],
      'ntnyq/prefer-object-method-syntax': 'error',
    },
  },
]

📖 Rule Details

This rule reports function expressions and disallowed arrow functions used directly as object property values.

ts
const dispose = () => {}

const object = {
  dispose,

  method() {},

  async asyncMethod() {},

  *iterator() {},

  get value() {
    return this.internalValue
  },

  set value(value) {
    this.internalValue = value
  },
}
correct
ts
const object = {
  method: function () {},
  asyncMethod: async function () {},
  iterator: function* () {},
  dispose: () => {},
  calculate: value => value * 2,
}
incorrect

Named function expressions are also reported, but are not automatically fixed because method syntax cannot preserve their inner name binding:

ts
const object = {
  recursive: function inner() {
    return inner()
  },
}

The non-computed __proto__ form is ignored because it changes the created object's prototype rather than defining a normal property:

ts
const object = {
  __proto__: function () {},
}

🔧 Options

ts
export type Options = [
  {
    allowArrowFunctions?: boolean
    allowedPropertyNames?: string[]
    fix?: boolean
  },
]

Defaults:

  • allowArrowFunctions: false
  • allowedPropertyNames: []
  • fix: false

allowArrowFunctions

When true, arrow functions are allowed as object property values. Function expressions are still reported.

ts
// options: [{ allowArrowFunctions: true }]
const object = {
  allowed: () => {},
  reported: function () {},
}

allowedPropertyNames

Static property names in this list are ignored for both function expressions and arrow functions.

ts
// options: [{ allowedPropertyNames: ['computed', 'get', 'set'] }]
const object = {
  computed: () => value,
  get: function () {},
  ['set']: value => update(value),
}

Identifier, string, number, and statically computed property names are supported. A dynamic computed property cannot match this option:

ts
const object = {
  [methodName]: () => {},
}

fix

When true, the rule automatically fixes only conversions that preserve the relevant function semantics.

The rule can automatically convert:

  • anonymous async, generator, and async-generator function expressions;
  • block-bodied arrow functions that do not depend on lexical bindings.
ts
const before = {
  load: async function () {},
  iterate: function* () {},
  dispose: () => {
    cleanup()
  },
}

const after = {
  async load() {},
  *iterate() {},
  dispose() {
    cleanup()
  },
}

The rule does not automatically fix:

  • ordinary function expressions, because methods are not constructible and do not have the same prototype property;
  • named function expressions, because their inner name binding would be removed;
  • expression-bodied arrows, because conversion requires introducing an explicit return;
  • arrows that use lexical this, arguments, super, or new.target;
  • arrows containing direct eval(...), which can access lexical bindings dynamically;
  • conversions that would remove or relocate comments.

For example, this arrow must retain the surrounding class instance as its this value:

ts
class Controller {
  #document = document

  create() {
    return {
      dispose: () => {
        this.#document.removeEventListener('click', handleClick)
      },
    }
  }
}

A semantics-preserving manual refactor keeps the arrow in the same lexical scope:

ts
class Controller {
  #document = document

  create() {
    const dispose = () => {
      this.#document.removeEventListener('click', handleClick)
    }

    return {
      dispose,
    }
  }
}

Editor suggestions may be available for conversions that require manual review. In particular, the suggestion for an ordinary function expression warns that the resulting method will no longer be constructible.

🚀 Version

This rule was introduced in eslint-plugin-ntnyq v0.15.0

🔍 Implementation