Skip to content

Commit a20ac4e

Browse files
authored
Merge pull request #1103 from reduxjs/chore/readonly-array-params
add `readonly` to array-type function parameters where mutation is not intended
2 parents 07b0a7c + 202ae85 commit a20ac4e

15 files changed

+111
-65
lines changed

.eslintrc.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@ module.exports = {
1919
],
2020
},
2121
overrides: [
22+
// {
23+
// // only add after https://github.com/typescript-eslint/typescript-eslint/pull/3463 is merged
24+
// files: ['src/**/*.ts'],
25+
// excludedFiles: [
26+
// '**/tests/*.ts',
27+
// '**/tests/**/*.ts',
28+
// '**/tests/*.tsx',
29+
// '**/tests/**/*.tsx',
30+
// ],
31+
// parserOptions: {
32+
// project: './tsconfig.json',
33+
// },
34+
// rules: {
35+
// '@typescript-eslint/prefer-readonly-parameter-types': [
36+
// 'warn',
37+
// { arraysAndTuplesOnly: true },
38+
// ],
39+
// },
40+
// },
2241
{
2342
files: ['src/tests/*.ts', 'src/**/tests/*.ts', 'src/**/tests/*.tsx'],
2443
rules: {

src/configureStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production'
3030
* @public
3131
*/
3232
export type ConfigureEnhancersCallback = (
33-
defaultEnhancers: StoreEnhancer[]
33+
defaultEnhancers: readonly StoreEnhancer[]
3434
) => StoreEnhancer[]
3535

3636
/**

src/createReducer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export type ActionMatcherDescription<S, A extends AnyAction> = {
2424
reducer: CaseReducer<S, NoInfer<A>>
2525
}
2626

27+
export type ReadonlyActionMatcherDescriptionCollection<S> = ReadonlyArray<
28+
ActionMatcherDescription<S, any>
29+
>
30+
2731
export type ActionMatcherDescriptionCollection<S> = Array<
2832
ActionMatcherDescription<S, any>
2933
>
@@ -190,7 +194,7 @@ export function createReducer<S>(
190194
mapOrBuilderCallback:
191195
| CaseReducers<S, any>
192196
| ((builder: ActionReducerMapBuilder<S>) => void),
193-
actionMatchers: ActionMatcherDescriptionCollection<S> = [],
197+
actionMatchers: ReadonlyActionMatcherDescriptionCollection<S> = [],
194198
defaultCaseReducer?: CaseReducer<S>
195199
): Reducer<S> {
196200
// We deliberately enable Immer's ES5 support, on the grounds that

src/entities/models.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ export interface EntityStateAdapter<T> {
6565

6666
addMany<S extends EntityState<T>>(
6767
state: PreventAny<S, T>,
68-
entities: T[] | Record<EntityId, T>
68+
entities: readonly T[] | Record<EntityId, T>
6969
): S
7070
addMany<S extends EntityState<T>>(
7171
state: PreventAny<S, T>,
72-
entities: PayloadAction<T[] | Record<EntityId, T>>
72+
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
7373
): S
7474

7575
setOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
@@ -79,19 +79,19 @@ export interface EntityStateAdapter<T> {
7979
): S
8080
setMany<S extends EntityState<T>>(
8181
state: PreventAny<S, T>,
82-
entities: T[] | Record<EntityId, T>
82+
entities: readonly T[] | Record<EntityId, T>
8383
): S
8484
setMany<S extends EntityState<T>>(
8585
state: PreventAny<S, T>,
86-
entities: PayloadAction<T[] | Record<EntityId, T>>
86+
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
8787
): S
8888
setAll<S extends EntityState<T>>(
8989
state: PreventAny<S, T>,
90-
entities: T[] | Record<EntityId, T>
90+
entities: readonly T[] | Record<EntityId, T>
9191
): S
9292
setAll<S extends EntityState<T>>(
9393
state: PreventAny<S, T>,
94-
entities: PayloadAction<T[] | Record<EntityId, T>>
94+
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
9595
): S
9696

9797
removeOne<S extends EntityState<T>>(state: PreventAny<S, T>, key: EntityId): S
@@ -102,11 +102,11 @@ export interface EntityStateAdapter<T> {
102102

103103
removeMany<S extends EntityState<T>>(
104104
state: PreventAny<S, T>,
105-
keys: EntityId[]
105+
keys: readonly EntityId[]
106106
): S
107107
removeMany<S extends EntityState<T>>(
108108
state: PreventAny<S, T>,
109-
keys: PayloadAction<EntityId[]>
109+
keys: PayloadAction<readonly EntityId[]>
110110
): S
111111

112112
removeAll<S extends EntityState<T>>(state: PreventAny<S, T>): S
@@ -122,11 +122,11 @@ export interface EntityStateAdapter<T> {
122122

123123
updateMany<S extends EntityState<T>>(
124124
state: PreventAny<S, T>,
125-
updates: Update<T>[]
125+
updates: ReadonlyArray<Update<T>>
126126
): S
127127
updateMany<S extends EntityState<T>>(
128128
state: PreventAny<S, T>,
129-
updates: PayloadAction<Update<T>[]>
129+
updates: PayloadAction<ReadonlyArray<Update<T>>>
130130
): S
131131

132132
upsertOne<S extends EntityState<T>>(state: PreventAny<S, T>, entity: T): S
@@ -137,11 +137,11 @@ export interface EntityStateAdapter<T> {
137137

138138
upsertMany<S extends EntityState<T>>(
139139
state: PreventAny<S, T>,
140-
entities: T[] | Record<EntityId, T>
140+
entities: readonly T[] | Record<EntityId, T>
141141
): S
142142
upsertMany<S extends EntityState<T>>(
143143
state: PreventAny<S, T>,
144-
entities: PayloadAction<T[] | Record<EntityId, T>>
144+
entities: PayloadAction<readonly T[] | Record<EntityId, T>>
145145
): S
146146
}
147147

src/entities/sorted_state_adapter.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function createSortedStateAdapter<T>(
2929
}
3030

3131
function addManyMutably(
32-
newEntities: T[] | Record<EntityId, T>,
32+
newEntities: readonly T[] | Record<EntityId, T>,
3333
state: R
3434
): void {
3535
newEntities = ensureEntitiesArray(newEntities)
@@ -48,7 +48,7 @@ export function createSortedStateAdapter<T>(
4848
}
4949

5050
function setManyMutably(
51-
newEntities: T[] | Record<EntityId, T>,
51+
newEntities: readonly T[] | Record<EntityId, T>,
5252
state: R
5353
): void {
5454
newEntities = ensureEntitiesArray(newEntities)
@@ -58,7 +58,7 @@ export function createSortedStateAdapter<T>(
5858
}
5959

6060
function setAllMutably(
61-
newEntities: T[] | Record<EntityId, T>,
61+
newEntities: readonly T[] | Record<EntityId, T>,
6262
state: R
6363
): void {
6464
newEntities = ensureEntitiesArray(newEntities)
@@ -72,6 +72,7 @@ export function createSortedStateAdapter<T>(
7272
return updateManyMutably([update], state)
7373
}
7474

75+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
7576
function takeUpdatedModel(models: T[], update: Update<T>, state: R): boolean {
7677
if (!(update.id in state.entities)) {
7778
return false
@@ -88,7 +89,10 @@ export function createSortedStateAdapter<T>(
8889
return newKey !== update.id
8990
}
9091

91-
function updateManyMutably(updates: Update<T>[], state: R): void {
92+
function updateManyMutably(
93+
updates: ReadonlyArray<Update<T>>,
94+
state: R
95+
): void {
9296
const models: T[] = []
9397

9498
updates.forEach((update) => takeUpdatedModel(models, update, state))
@@ -103,7 +107,7 @@ export function createSortedStateAdapter<T>(
103107
}
104108

105109
function upsertManyMutably(
106-
newEntities: T[] | Record<EntityId, T>,
110+
newEntities: readonly T[] | Record<EntityId, T>,
107111
state: R
108112
): void {
109113
const [added, updated] = splitAddedUpdatedEntities<T>(
@@ -116,7 +120,7 @@ export function createSortedStateAdapter<T>(
116120
addManyMutably(added, state)
117121
}
118122

119-
function areArraysEqual(a: unknown[], b: unknown[]) {
123+
function areArraysEqual(a: readonly unknown[], b: readonly unknown[]) {
120124
if (a.length !== b.length) {
121125
return false
122126
}
@@ -130,7 +134,7 @@ export function createSortedStateAdapter<T>(
130134
return true
131135
}
132136

133-
function merge(models: T[], state: R): void {
137+
function merge(models: readonly T[], state: R): void {
134138
// Insert/overwrite all new/updated
135139
models.forEach((model) => {
136140
state.entities[selectId(model)] = model

src/entities/state_selectors.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { createDraftSafeSelector } from '../createDraftSafeSelector'
2-
import type { EntityState, EntitySelectors, Dictionary, EntityId } from './models'
2+
import type {
3+
EntityState,
4+
EntitySelectors,
5+
Dictionary,
6+
EntityId,
7+
} from './models'
38

49
export function createSelectorsFactory<T>() {
510
function getSelectors(): EntitySelectors<T, EntityState<T>>
@@ -16,7 +21,7 @@ export function createSelectorsFactory<T>() {
1621
const selectAll = createDraftSafeSelector(
1722
selectIds,
1823
selectEntities,
19-
(ids: T[], entities: Dictionary<T>): any =>
24+
(ids: readonly T[], entities: Dictionary<T>): any =>
2025
ids.map((id: any) => (entities as any)[id])
2126
)
2227

src/entities/unsorted_state_adapter.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function createUnsortedStateAdapter<T>(
3232
}
3333

3434
function addManyMutably(
35-
newEntities: T[] | Record<EntityId, T>,
35+
newEntities: readonly T[] | Record<EntityId, T>,
3636
state: R
3737
): void {
3838
newEntities = ensureEntitiesArray(newEntities)
@@ -51,7 +51,7 @@ export function createUnsortedStateAdapter<T>(
5151
}
5252

5353
function setManyMutably(
54-
newEntities: T[] | Record<EntityId, T>,
54+
newEntities: readonly T[] | Record<EntityId, T>,
5555
state: R
5656
): void {
5757
newEntities = ensureEntitiesArray(newEntities)
@@ -61,7 +61,7 @@ export function createUnsortedStateAdapter<T>(
6161
}
6262

6363
function setAllMutably(
64-
newEntities: T[] | Record<EntityId, T>,
64+
newEntities: readonly T[] | Record<EntityId, T>,
6565
state: R
6666
): void {
6767
newEntities = ensureEntitiesArray(newEntities)
@@ -76,7 +76,7 @@ export function createUnsortedStateAdapter<T>(
7676
return removeManyMutably([key], state)
7777
}
7878

79-
function removeManyMutably(keys: EntityId[], state: R): void {
79+
function removeManyMutably(keys: readonly EntityId[], state: R): void {
8080
let didMutate = false
8181

8282
keys.forEach((key) => {
@@ -122,7 +122,10 @@ export function createUnsortedStateAdapter<T>(
122122
return updateManyMutably([update], state)
123123
}
124124

125-
function updateManyMutably(updates: Update<T>[], state: R): void {
125+
function updateManyMutably(
126+
updates: ReadonlyArray<Update<T>>,
127+
state: R
128+
): void {
126129
const newKeys: { [id: string]: EntityId } = {}
127130

128131
const updatesPerEntity: { [id: string]: Update<T> } = {}
@@ -165,7 +168,7 @@ export function createUnsortedStateAdapter<T>(
165168
}
166169

167170
function upsertManyMutably(
168-
newEntities: T[] | Record<EntityId, T>,
171+
newEntities: readonly T[] | Record<EntityId, T>,
169172
state: R
170173
): void {
171174
const [added, updated] = splitAddedUpdatedEntities<T>(

src/entities/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export function selectIdValue<T>(entity: T, selectId: IdSelector<T>) {
1818
}
1919

2020
export function ensureEntitiesArray<T>(
21-
entities: T[] | Record<EntityId, T>
22-
): T[] {
21+
entities: readonly T[] | Record<EntityId, T>
22+
): readonly T[] {
2323
if (!Array.isArray(entities)) {
2424
entities = Object.values(entities)
2525
}
@@ -28,7 +28,7 @@ export function ensureEntitiesArray<T>(
2828
}
2929

3030
export function splitAddedUpdatedEntities<T>(
31-
newEntities: T[] | Record<EntityId, T>,
31+
newEntities: readonly T[] | Record<EntityId, T>,
3232
selectId: IdSelector<T>,
3333
state: EntityState<T>
3434
): [T[], Update<T>[]] {

src/immutableStateInvariantMiddleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ function trackProperties(
121121
return tracked as TrackedProperty
122122
}
123123

124-
type IgnorePaths = string[]
124+
type IgnorePaths = readonly string[]
125125

126126
function detectMutations(
127127
isImmutable: IsImmutableFunc,

src/matchers.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ export function isAllOf<Matchers extends [Matcher<any>, ...Matcher<any>[]]>(
6969
*
7070
* @internal
7171
*/
72-
export function hasExpectedRequestMetadata(action: any, validStatus: string[]) {
72+
export function hasExpectedRequestMetadata(
73+
action: any,
74+
validStatus: readonly string[]
75+
) {
7376
if (!action || !action.meta) return false
7477

7578
const hasValidRequestId = typeof action.meta.requestId === 'string'

0 commit comments

Comments
 (0)