diff --git a/src/core/public/injected_metadata/__fixtures__/frozen_object_mutation.ts b/src/core/public/injected_metadata/__fixtures__/frozen_object_mutation/index.ts similarity index 71% rename from src/core/public/injected_metadata/__fixtures__/frozen_object_mutation.ts rename to src/core/public/injected_metadata/__fixtures__/frozen_object_mutation/index.ts index ab9fe4da923ce..0be915a4bde1f 100644 --- a/src/core/public/injected_metadata/__fixtures__/frozen_object_mutation.ts +++ b/src/core/public/injected_metadata/__fixtures__/frozen_object_mutation/index.ts @@ -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); diff --git a/src/core/public/injected_metadata/__fixtures__/frozen_object_mutation.tsconfig.json b/src/core/public/injected_metadata/__fixtures__/frozen_object_mutation/tsconfig.json similarity index 61% rename from src/core/public/injected_metadata/__fixtures__/frozen_object_mutation.tsconfig.json rename to src/core/public/injected_metadata/__fixtures__/frozen_object_mutation/tsconfig.json index aaedce798435d..64fcbf1f74881 100644 --- a/src/core/public/injected_metadata/__fixtures__/frozen_object_mutation.tsconfig.json +++ b/src/core/public/injected_metadata/__fixtures__/frozen_object_mutation/tsconfig.json @@ -6,8 +6,7 @@ "esnext" ] }, - "include": [ - "frozen_object_mutation.ts", - "../deep_freeze.ts" + "files": [ + "index.ts" ] } diff --git a/src/core/public/injected_metadata/deep_freeze.test.ts b/src/core/public/injected_metadata/deep_freeze.test.ts index 9086d697f9ce3..7657af7f1051d 100644 --- a/src/core/public/injected_metadata/deep_freeze.test.ts +++ b/src/core/public/injected_metadata/deep_freeze.test.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' only permits reading. +index.ts(50,8): error TS2339: Property 'push' does not exist on type 'RecursiveReadonlyArray'. +" +`); }); diff --git a/src/core/public/injected_metadata/deep_freeze.ts b/src/core/public/injected_metadata/deep_freeze.ts index 33948fccef950..ba1308690b683 100644 --- a/src/core/public/injected_metadata/deep_freeze.ts +++ b/src/core/public/injected_metadata/deep_freeze.ts @@ -19,9 +19,12 @@ type Freezable = { [k: string]: any } | any[]; -type RecursiveReadOnly = T extends Freezable - ? Readonly<{ [K in keyof T]: RecursiveReadOnly }> - : T; +// if we define this inside RecursiveReadonly TypeScript complains +interface RecursiveReadonlyArray extends ReadonlyArray> {} + +type RecursiveReadonly = T extends any[] + ? RecursiveReadonlyArray + : T extends object ? Readonly<{ [K in keyof T]: RecursiveReadonly }> : T; export function deepFreeze(object: T) { // for any properties that reference an object, makes sure that object is @@ -32,5 +35,5 @@ export function deepFreeze(object: T) { } } - return Object.freeze(object) as RecursiveReadOnly; + return Object.freeze(object) as RecursiveReadonly; }