Skip to content

Commit

Permalink
[6.x] [core/public/deepFreeze] fix recursive type for better array su…
Browse files Browse the repository at this point in the history
…pport (#22904) (#22932)

Backports the following commits to 6.x:
 - [core/public/deepFreeze] fix recursive type for better array support  (#22904)
  • Loading branch information
Spencer authored Sep 11, 2018
1 parent 526c48f commit cfd2a8f
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,34 @@
* under the License.
*/

import { deepFreeze } from '../deep_freeze';
import { deepFreeze } from '../../deep_freeze';

const obj = deepFreeze({
foo: {
bar: {
baz: 1,
deepFreeze(
{
foo: {
bar: {
baz: 1,
},
},
},
});
}
).foo.bar.baz = 2;

delete obj.foo;
obj.foo = 1;
obj.foo.bar.baz = 2;
obj.foo.bar.box = false;
deepFreeze(
{
foo: [
{
bar: 1,
},
],
}
).foo[0].bar = 2;

deepFreeze(
{
foo: [1],
}
).foo[0] = 2;

deepFreeze({
foo: [1],
}).foo.push(2);
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
"esnext"
]
},
"include": [
"frozen_object_mutation.ts",
"../deep_freeze.ts"
"files": [
"index.ts"
]
}
35 changes: 12 additions & 23 deletions src/core/public/injected_metadata/deep_freeze.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,28 +75,17 @@ it('prevents reassigning items in a frozen array', () => {
});

it('types return values to prevent mutations in typescript', async () => {
const result = await execa.stdout(
'tsc',
[
'--noEmit',
'--project',
resolve(__dirname, '__fixtures__/frozen_object_mutation.tsconfig.json'),
],
{
cwd: resolve(__dirname, '__fixtures__'),
reject: false,
}
);
await expect(
execa.stdout('tsc', ['--noEmit'], {
cwd: resolve(__dirname, '__fixtures__/frozen_object_mutation'),
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Command failed: tsc --noEmit
const errorCodeRe = /\serror\s(TS\d{4}):/g;
const errorCodes = [];
while (true) {
const match = errorCodeRe.exec(result);
if (!match) {
break;
}
errorCodes.push(match[1]);
}

expect(errorCodes).toEqual(['TS2704', 'TS2540', 'TS2540', 'TS2339']);
index.ts(30,11): error TS2540: Cannot assign to 'baz' because it is a constant or a read-only property.
index.ts(40,10): error TS2540: Cannot assign to 'bar' because it is a constant or a read-only property.
index.ts(42,1): error TS2542: Index signature in type 'RecursiveReadonlyArray<number>' only permits reading.
index.ts(50,8): error TS2339: Property 'push' does not exist on type 'RecursiveReadonlyArray<number>'.
"
`);
});
11 changes: 7 additions & 4 deletions src/core/public/injected_metadata/deep_freeze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@

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

type RecursiveReadOnly<T> = T extends Freezable
? Readonly<{ [K in keyof T]: RecursiveReadOnly<T[K]> }>
: T;
// if we define this inside RecursiveReadonly TypeScript complains
interface RecursiveReadonlyArray<T> extends ReadonlyArray<RecursiveReadonly<T>> {}

type RecursiveReadonly<T> = T extends any[]
? RecursiveReadonlyArray<T[number]>
: T extends object ? Readonly<{ [K in keyof T]: RecursiveReadonly<T[K]> }> : T;

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

return Object.freeze(object) as RecursiveReadOnly<T>;
return Object.freeze(object) as RecursiveReadonly<T>;
}

0 comments on commit cfd2a8f

Please sign in to comment.