@typescript-eslint/prefer-readonly-parameter-types

Require the types of function parameters to be read-only

needs types

Also called typescript-eslint/prefer-readonly-parameter-types, typescript/prefer-readonly-parameter-types

A function that sorts, pushes to, or assigns into its argument changes the caller's data. The caller can't see that from the call. If the parameter has a read-only type, TypeScript rejects those changes in the function body, and the signature promises the caller that the value comes back unchanged.

The check is deep, so a readonly array of objects that can be changed is still reported. Strings, numbers, and other primitives are always fine, and so are functions.

Configure#

Off by default. This rule needs types, so turning it on makes bun lint type check your code first.

bunfig.toml
[lint.rules]
"@typescript-eslint/prefer-readonly-parameter-types" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"@typescript-eslint/prefer-readonly-parameter-types" = ["error", {
  allow = [], # list of strings or objects
  checkParameterProperties = true,
  ignoreInferredTypes = false,
  treatMethodsAsReadonly = false,
}]

Examples#

example.ts3 problems
function total(prices: number[]) {}

function rename(user: { name: string }) {}

function names(users: readonly { name: string }[]) {}
example.tsNo problems
function total(prices: readonly number[]) {}

function rename(user: Readonly<{ name: string }>) {}

function names(users: readonly { readonly name: string }[]) {}

function repeat(text: string, times: number, done: () => void) {}

Options#

OptionDefaultWhat it does
allow[]Types that are accepted as they are.
checkParameterPropertiestrueCheck constructor parameters that declare a field, such as private items: string[].
ignoreInferredTypesfalseSkip parameters that have no type annotation.
treatMethodsAsReadonlyfalseAccept methods that aren't marked readonly.

Each allow entry is a type name, or an object that also says where the type comes from: { "from": "file", "name": "Draft" }, { "from": "lib", "name": "Date" }, or { "from": "package", "name": "Draft", "package": "some-package" }.

A Date is reported by default:

example.ts1 problem
function format(date: Date) {}

With allow: [{ "from": "lib", "name": "Date" }] it's accepted:

example.tswith { "allow": [{ "from": "lib", "name": "Date" }] }No problems
function format(date: Date) {}

A callback's parameter often gets its type from the function you pass the callback to, which you might not control. Here row is a string[], so it's reported:

example.ts1 problem
const rows = [["a", "b"], ["c"]];
rows.forEach(row => console.log(row.length));

With ignoreInferredTypes: true it's skipped:

example.tswith { "ignoreInferredTypes": true }No problems
const rows = [["a", "b"], ["c"]];
rows.forEach(row => console.log(row.length));

A method written as save(): void can be assigned to, so it counts as something that can change:

example.ts1 problem
interface Store {
  save(): void;
}

function flush(store: Store) {}

With treatMethodsAsReadonly: true it doesn't:

example.tswith { "treatMethodsAsReadonly": true }No problems
interface Store {
  save(): void;
}

function flush(store: Store) {}

When not to use it#

This rule is strict. Most library and DOM types aren't read-only, and every function that takes one is reported until you list the type under allow.