ntnyq/prefer-object-method-syntax
Prefer method syntax for inline functions in object literals.
- 🔧 The
--fixoption 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:
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:
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.
const dispose = () => {}
const object = {
dispose,
method() {},
async asyncMethod() {},
*iterator() {},
get value() {
return this.internalValue
},
set value(value) {
this.internalValue = value
},
}const object = {
method: function () {},
asyncMethod: async function () {},
iterator: function* () {},
dispose: () => {},
calculate: value => value * 2,
}Named function expressions are also reported, but are not automatically fixed because method syntax cannot preserve their inner name binding:
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:
const object = {
__proto__: function () {},
}🔧 Options
export type Options = [
{
allowArrowFunctions?: boolean | 'singleLineOnly'
allowedPropertyNames?: string[]
avoidQuotes?: boolean
fix?: boolean
},
]Defaults:
allowArrowFunctions:falseallowedPropertyNames:[]avoidQuotes:falsefix:false
allowArrowFunctions
This option controls which arrow functions are allowed as object property values:
falsereports all arrow functions;trueallows all arrow functions;'singleLineOnly'allows single-line arrow functions but still reports multi-line arrow functions.
Function expressions are still reported for every option value.
// options: [{ allowArrowFunctions: true }]
const object = {
allowed: () => {},
reported: function () {},
}With 'singleLineOnly', multi-line arrow functions can be automatically fixed when fix is also enabled and the conversion is safe:
// options: [{ allowArrowFunctions: 'singleLineOnly', fix: true }]
const name = computed({
get: () => model.value.name,
read: () =>
// Keep this expression comment.
model.value.name,
set: value => {
model.value = { ...model.value, name: value }
},
})With --fix, the multi-line set property becomes:
const name = computed({
get: () => model.value.name,
read() {
// Keep this expression comment.
return model.value.name
},
set(value) {
model.value = { ...model.value, name: value }
},
})allowedPropertyNames
Static property names in this list are ignored for both function expressions and arrow functions.
// 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:
const object = {
[methodName]: () => {},
}avoidQuotes
When true, string literal property keys are ignored for both function expressions and arrow functions. No diagnostics, fixes, or suggestions are produced for these properties. This includes computed string literals such as ['Program:exit'], matching the core object-shorthand option.
Enable this option when using object-shorthand with avoidQuotes: true. Otherwise, the core rule can turn a quoted method back into a function expression after this rule fixes it, leaving a preferMethodSyntax error.
export default [
{
rules: {
'object-shorthand': ['error', 'always', { avoidQuotes: true }],
'ntnyq/prefer-object-method-syntax': [
'error',
{
allowArrowFunctions: 'singleLineOnly',
avoidQuotes: true,
fix: true,
},
],
},
},
]For example, an ESLint listener can keep its quoted selector and arrow:
// options: [{ allowArrowFunctions: 'singleLineOnly', avoidQuotes: true, fix: true }]
const listeners = {
'Program > YAMLPair': node => {
report(node)
},
}Identifier keys, numeric keys, and dynamic computed keys are still checked. The default is false, preserving checks and safe conversions for quoted keys.
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;
- multi-line expression-bodied arrow functions that do not depend on lexical bindings.
The fixer introduces an explicit return when converting a multi-line expression-bodied arrow. Comments before the returned expression stay above the generated return statement.
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
prototypeproperty; - named function expressions, because their inner name binding would be removed;
- single-line expression-bodied arrows;
- arrows that use lexical
this,arguments,super, ornew.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:
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:
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