Backports the following commits to 6.x: - [core/public/deepFreeze] fix recursive type for better array support (#22904)
This commit is contained in:
parent
526c48f2cd
commit
cfd2a8fee8
|
@ -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);
|
|
@ -6,8 +6,7 @@
|
|||
"esnext"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"frozen_object_mutation.ts",
|
||||
"../deep_freeze.ts"
|
||||
"files": [
|
||||
"index.ts"
|
||||
]
|
||||
}
|
|
@ -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>'.
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
@ -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>;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue