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

eslint-plugin-prefer-object-params

v1.0.2

Published

ESLint plugin that enforces object-only parameters for functions

Readme

eslint-plugin-prefer-object-params

ESLint rule suite that prefers destructured object parameters over long positional argument lists.

It helps keep function signatures self-describing while staying flexible enough for gradual adoption.

What This Plugin Enforces

Flags functions, class methods, and arrow functions that exceed 2 positional parameters (configurable via maxParams):

// ❌ Error (3+ positional params exceeds maxParams: 2 default)
function updateUser(id, name, email, isAdmin) {}

class UserService {
  create(name, email, role) {}
}

const sendEmail = (to, subject, body) => {};

// ✅ Allowed (up to 2 params by default - common for callbacks)
array.map((item, index) => {});
array.reduce((acc, item) => {});

Accepts parameters declared as object patterns, array patterns, rest parameters, or the special TypeScript this parameter:

// ✅ Allowed
function updateUser({ id, name, email, isAdmin }) {}

class UserService {
  create({ name, email, role }) {}
}

const sendEmail = ({ to, subject, body }) => {};

// ✅ Rest parameters allowed
const log = (...messages) => {};

// ✅ Array destructuring allowed
const processPair = ([first, second]) => {};

// ✅ TypeScript 'this' parameter allowed
function method(this: Context, { id }) {}

Why Prefer Objects?

Self-documenting and order-independent:

// ❌ What does each argument mean?
createUser('John', '[email protected]', true, 'admin');

// ✅ Clear and order doesn't matter
createUser({ name: 'John', email: '[email protected]', isAdmin: true, role: 'admin' });

Adding parameters doesn't break existing call sites:

// ❌ Adding a new parameter breaks all call sites
function createUser(name, email, isAdmin, role) {}

createUser('John', '[email protected]', true); // Missing 'role' parameter

// ✅ New parameters are optional
function createUser({ name, email, isAdmin, role }) {}

createUser({ name: 'John', email: '[email protected]', isAdmin: true }); // Still works

Optional values are omitted entirely:

// ❌ Padding with null/undefined
updateUser(userId, null, null, true);

// ✅ Only specify what you need
updateUser({ userId, isAdmin: true });

IDEs surface available options through autocomplete:

// TypeScript/IDE shows all available options
createUser({
  // IDE autocomplete: name, email, isAdmin, role
});

Installation

Install ESLint if you have not already:

npm install --save-dev eslint
pnpm add -D eslint
bun add -D eslint

Then add the plugin:

npm install --save-dev eslint-plugin-prefer-object-params
pnpm add -D eslint-plugin-prefer-object-params
bun add -D eslint-plugin-prefer-object-params

Usage

ESLint 9+ (flat config)

// eslint.config.js
import preferObjectParams from 'eslint-plugin-prefer-object-params';

export default [
  {
    plugins: {
      'prefer-object-params': preferObjectParams,
    },
    rules: {
      'prefer-object-params/prefer-object-params': 'error',
    },
  },
];

Or enable the bundled configuration:

// eslint.config.js
import preferObjectParams from 'eslint-plugin-prefer-object-params';

export default [
  ...preferObjectParams.configs.recommended,
];

ESLint 8.x (classic config)

// .eslintrc.cjs
module.exports = {
  extends: ['plugin:prefer-object-params/recommended'],
};

Adding to an Existing Codebase

Most teams will add this plugin to an existing codebase with tech debt. Here's a proven strategy for gradual adoption:

Step 1: Start with warnings only

First, understand the scope of existing violations without blocking development:

// eslint.config.js
import preferObjectParams from 'eslint-plugin-prefer-object-params';

export default [
  {
    plugins: {
      'prefer-object-params': preferObjectParams,
    },
    rules: {
      'prefer-object-params/prefer-object-params': ['warn', {
        maxParams: 2,  // Warn when more than 2 params
      }],
    },
  },
];

Run ESLint to see all violations: npx eslint .

Step 2: Block severe violations with errors

Prevent new severe tech debt while allowing existing moderate issues:

// eslint.config.js
export default [
  {
    plugins: {
      'prefer-object-params': preferObjectParams,
    },
    rules: {
      // Error on severe cases (more than 4 params) - blocks CI
      'prefer-object-params/prefer-object-params': ['error', {
        maxParams: 4,
      }],
    },
  },
];

Step 3: Apply stricter rules to new code

Use glob patterns to enforce higher standards in new code while being lenient with legacy:

// eslint.config.js
export default [
  // Default: Error on severe violations (more than 4 params)
  {
    plugins: {
      'prefer-object-params': preferObjectParams,
    },
    rules: {
      'prefer-object-params/prefer-object-params': ['error', {
        maxParams: 4,
      }],
    },
  },

  // Stricter rules for new features
  {
    files: ['src/features/**', 'src/modules/**'],
    rules: {
      'prefer-object-params/prefer-object-params': ['error', {
        maxParams: 2,  // Enforce best practices in new code
      }],
    },
  },

  // Legacy code: warn only (don't block development)
  {
    files: ['src/legacy/**', 'src/old-api/**'],
    rules: {
      'prefer-object-params/prefer-object-params': ['warn', {
        maxParams: 2,
      }],
    },
  },
];

Step 4: Gradually tighten the rules

As you refactor code, lower the threshold:

// After cleaning up functions with 5+ params, tighten to max 3
'prefer-object-params/prefer-object-params': ['error', { maxParams: 3 }]

// Eventually enforce max 2 params everywhere (default)
'prefer-object-params/prefer-object-params': ['error', { maxParams: 2 }]

Tracking Progress

Monitor your progress with ESLint output:

# See warning count over time
npx eslint . --max-warnings 100

# Track specific directories
npx eslint src/legacy --format json > legacy-debt.json

This approach allows teams to:

  • 📊 Visualize tech debt without blocking work
  • 🚫 Prevent new severe violations immediately
  • Enforce best practices in new code from day one
  • 📉 Gradually reduce debt over time

Rule Behaviour

By default the rule:

Reports functions with more than 2 positional parameters (maxParams: 2):

// ❌ Error: 3+ positional params (exceeds max of 2)
function process(x, y, z) {}

const add = (a, b, c) => {};

// ✅ Allowed: up to 2 params (common for callbacks like map, reduce)
function process(x, y) {}
array.map((item, index) => {});

// ✅ Allowed: single positional param (ignoreSingleParam: true)
const hash = (value) => {};

// ✅ Allowed: no params (ignoreNoParams: true)
function init() {}

Allows object destructuring, array destructuring, rest parameters, and this parameters:

// ✅ All allowed
function user({ id, name }) {}

const pair = ([a, b]) => {};

const log = (...args) => {};

function method(this: Context, { id }) {}

Ignores constructors (ignoreConstructors: true):

// ✅ Allowed: constructors are ignored
class User {
  constructor(name, email, role) {}
}

Ignores test files (**/*.test.* and **/*.spec.*):

// ✅ Allowed in test files
describe('UserService', () => {
  it('creates user', () => {
    const user = createUser('John', '[email protected]', true);
  });
});

These defaults make it safe to turn on in a mature codebase while you migrate gradually.

Rule Options

Configure the rule via the second argument:

{
  rules: {
    'prefer-object-params/prefer-object-params': ['error', {
      maxParams: 2,                       // Max positional params allowed (default: 2)
      ignoreFunctions: ['legacyHelper'],  // Function names to skip
      ignoreMethods: ['render'],          // Method names to skip
      ignoreConstructors: true,           // Skip class constructors
      ignoreSingleParam: true,            // Allow single positional param
      ignoreNoParams: true,               // Allow empty parameter lists
      ignoreTestFiles: true,              // Skip *.test.* and *.spec.* files
      ignoreFiles: ['src/generated/**'],  // Additional glob patterns to skip
    }],
  },
}

Option: maxParams

The maximum number of positional parameters allowed before the rule triggers. Default is 2, which allows common 2-parameter patterns like:

// ✅ Allowed with maxParams: 2 (default)
array.map((item, index) => {});
array.reduce((acc, item) => {});
addEventListener((event, data) => {});

// ❌ Error - exceeds maxParams: 2
function createUser(name, email, role) {}

Set maxParams: 1 to enforce object params for all multi-parameter functions:

{
  rules: {
    'prefer-object-params/prefer-object-params': ['error', {
      maxParams: 1, // Flag functions with 2+ params
    }],
  },
}

Or increase to maxParams: 4 for more lenient enforcement:

{
  rules: {
    'prefer-object-params/prefer-object-params': ['error', {
      maxParams: 4, // Only flag functions with 5+ params
    }],
  },
}

Use these switches to opt specific APIs back out while enforcing object parameters everywhere else.

Examples

Function Declarations

// ❌ Error (3+ params)
function createUser(name, email, isAdmin = false) {}

function updateUser(id, name, email, role) {}

// ✅ Allowed (2 params)
function authenticate(username, password) {}

// ✅ Allowed (object destructuring)
function createUser({ name, email, isAdmin = false }) {}

function updateUser({ id, name, email, role }) {}

Arrow Functions

// ❌ Error (3+ params)
const sendEmail = (to, subject, body) => {};

const calculate = (x, y, operation) => {};

// ✅ Allowed (2 params - common pattern)
const add = (a, b) => a + b;

// ✅ Allowed (object destructuring)
const sendEmail = ({ to, subject, body }) => {};

const calculate = ({ x, y, operation }) => {};

Class Methods

// ❌ Error (3+ params)
class UserService {

  create(name, email, role) {}

  update(id, name, email, status) {}
}

// ✅ Allowed (2 params)
class UserService {

  authenticate(username, password) {}
}

// ✅ Allowed (object destructuring)
class UserService {

  create({ name, email, role }) {}

  update({ id, name, email, status }) {}
}

Special Cases

// ✅ Rest parameters allowed
const log = (...messages) => {};

const sum = (...numbers) => {};

// ✅ Array destructuring allowed
const processPair = ([first, second]) => {};

const swap = ([a, b]) => [b, a];

// ✅ Single positional parameter allowed (ignoreSingleParam: true)
const hash = (value) => {};

const parse = (str) => {};

// ✅ TypeScript 'this' parameter allowed
function method(this: Context, { id }) {}

Migration Tips

Using ESLint disable comments

You can disable the rule for specific lines, functions, or entire files using ESLint comments:

// Disable for a single line
function legacyFunction(a, b, c) {} // eslint-disable-line prefer-object-params/prefer-object-params

// Disable for the next line
// eslint-disable-next-line prefer-object-params/prefer-object-params
function oldAPI(id, name, email) {}

// Disable for a block of code
/* eslint-disable prefer-object-params/prefer-object-params */
function legacy1(a, b, c) {}
function legacy2(x, y, z) {}
/* eslint-enable prefer-object-params/prefer-object-params */

// Disable for entire file (put at top of file)
/* eslint-disable prefer-object-params/prefer-object-params */

Use comments sparingly - they create invisible tech debt. Prefer configuration options when possible:

// Better: use ignoreFunctions config
rules: {
  'prefer-object-params/prefer-object-params': ['error', {
    ignoreFunctions: ['legacyHelper', 'oldAPI'],
  }],
}

Using ignore lists for gradual cleanup

Add specific functions or methods to the ignore list while you refactor:

// eslint.config.js
rules: {
  'prefer-object-params/prefer-object-params': ['error', {
    ignoreFunctions: ['legacyHelper', 'oldAPI'],  // Remove as you refactor
    ignoreMethods: ['render', 'toString'],
  }],
}

Example refactoring

// Before
function createUser(name, email, role) {
  return { name, email, role };
}

createUser('John', '[email protected]', 'admin');

// After
function createUser({ name, email, role }) {
  return { name, email, role };
}

createUser({ name: 'John', email: '[email protected]', role: 'admin' });

Contributing

Issues, discussions, and pull requests are welcome. Please include failing cases or suggestions for new options when reporting bugs.

License

MIT © Jag Reehal