mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[core/public/deepFreeze] fix recursive type for better array support (#22904)
The `deepFreeze()` function used by `core.injectedMetadata` uses a recursive type definition to indicate that all of the child types within the passed argument become readonly, which works fine for objects but represents arrays as objects instead of using the `ReadonlyArray<>` type. This PR fixes the type definition to use a `RecursiveReadonlyArray<>` type that properly represents arrays with their methods like `push()`, and iterates into the array properly to propagate `ReadOnly<>`, as proven by the tests.
This commit is contained in:
parent
15322e7256
commit
6ce47520ad
4 changed files with 49 additions and 41 deletions
|
@ -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…
Add table
Add a link
Reference in a new issue