eslint-plugin-prefer-object-params
v1.0.2
Published
ESLint plugin that enforces object-only parameters for functions
Maintainers
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 worksOptional 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 eslintThen 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-paramsUsage
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.jsonThis 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
