Skip to content

Commit cfd2a8f

Browse files
author
Spencer
authored
[6.x] [core/public/deepFreeze] fix recursive type for better array support (#22904) (#22932)
Backports the following commits to 6.x: - [core/public/deepFreeze] fix recursive type for better array support (#22904)
1 parent 526c48f commit cfd2a8f

4 files changed

Lines changed: 49 additions & 41 deletions

File tree

src/core/public/injected_metadata/__fixtures__/frozen_object_mutation.ts renamed to src/core/public/injected_metadata/__fixtures__/frozen_object_mutation/index.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,34 @@
1717
* under the License.
1818
*/
1919

20-
import { deepFreeze } from '../deep_freeze';
20+
import { deepFreeze } from '../../deep_freeze';
2121

22-
const obj = deepFreeze({
23-
foo: {
24-
bar: {
25-
baz: 1,
22+
deepFreeze(
23+
{
24+
foo: {
25+
bar: {
26+
baz: 1,
27+
},
2628
},
27-
},
28-
});
29+
}
30+
).foo.bar.baz = 2;
2931

30-
delete obj.foo;
31-
obj.foo = 1;
32-
obj.foo.bar.baz = 2;
33-
obj.foo.bar.box = false;
32+
deepFreeze(
33+
{
34+
foo: [
35+
{
36+
bar: 1,
37+
},
38+
],
39+
}
40+
).foo[0].bar = 2;
41+
42+
deepFreeze(
43+
{
44+
foo: [1],
45+
}
46+
).foo[0] = 2;
47+
48+
deepFreeze({
49+
foo: [1],
50+
}).foo.push(2);

src/core/public/injected_metadata/__fixtures__/frozen_object_mutation.tsconfig.json renamed to src/core/public/injected_metadata/__fixtures__/frozen_object_mutation/tsconfig.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
"esnext"
77
]
88
},
9-
"include": [
10-
"frozen_object_mutation.ts",
11-
"../deep_freeze.ts"
9+
"files": [
10+
"index.ts"
1211
]
1312
}

src/core/public/injected_metadata/deep_freeze.test.ts

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -75,28 +75,17 @@ it('prevents reassigning items in a frozen array', () => {
7575
});
7676

7777
it('types return values to prevent mutations in typescript', async () => {
78-
const result = await execa.stdout(
79-
'tsc',
80-
[
81-
'--noEmit',
82-
'--project',
83-
resolve(__dirname, '__fixtures__/frozen_object_mutation.tsconfig.json'),
84-
],
85-
{
86-
cwd: resolve(__dirname, '__fixtures__'),
87-
reject: false,
88-
}
89-
);
78+
await expect(
79+
execa.stdout('tsc', ['--noEmit'], {
80+
cwd: resolve(__dirname, '__fixtures__/frozen_object_mutation'),
81+
})
82+
).rejects.toThrowErrorMatchingInlineSnapshot(`
83+
"Command failed: tsc --noEmit
9084
91-
const errorCodeRe = /\serror\s(TS\d{4}):/g;
92-
const errorCodes = [];
93-
while (true) {
94-
const match = errorCodeRe.exec(result);
95-
if (!match) {
96-
break;
97-
}
98-
errorCodes.push(match[1]);
99-
}
100-
101-
expect(errorCodes).toEqual(['TS2704', 'TS2540', 'TS2540', 'TS2339']);
85+
index.ts(30,11): error TS2540: Cannot assign to 'baz' because it is a constant or a read-only property.
86+
index.ts(40,10): error TS2540: Cannot assign to 'bar' because it is a constant or a read-only property.
87+
index.ts(42,1): error TS2542: Index signature in type 'RecursiveReadonlyArray<number>' only permits reading.
88+
index.ts(50,8): error TS2339: Property 'push' does not exist on type 'RecursiveReadonlyArray<number>'.
89+
"
90+
`);
10291
});

src/core/public/injected_metadata/deep_freeze.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919

2020
type Freezable = { [k: string]: any } | any[];
2121

22-
type RecursiveReadOnly<T> = T extends Freezable
23-
? Readonly<{ [K in keyof T]: RecursiveReadOnly<T[K]> }>
24-
: T;
22+
// if we define this inside RecursiveReadonly TypeScript complains
23+
interface RecursiveReadonlyArray<T> extends ReadonlyArray<RecursiveReadonly<T>> {}
24+
25+
type RecursiveReadonly<T> = T extends any[]
26+
? RecursiveReadonlyArray<T[number]>
27+
: T extends object ? Readonly<{ [K in keyof T]: RecursiveReadonly<T[K]> }> : T;
2528

2629
export function deepFreeze<T extends Freezable>(object: T) {
2730
// for any properties that reference an object, makes sure that object is
@@ -32,5 +35,5 @@ export function deepFreeze<T extends Freezable>(object: T) {
3235
}
3336
}
3437

35-
return Object.freeze(object) as RecursiveReadOnly<T>;
38+
return Object.freeze(object) as RecursiveReadonly<T>;
3639
}

0 commit comments

Comments
 (0)