[@types/seamless-immutable] Overrides<T> doesn't change some parameters to Immutable<T> or Immutable<T[]> (#34884)

* Added forEach and changed signatures for existing T[] functions

T becomes Immutable<T>, T[] becomes Immutable<T[]>.

* Corrected whitespace

* More whitespace..

* asMutable, concat, isImmutable signatures

Expanded asMutable to have 3x options with the same signature, allowing
correct typed result when possible.

* Further improvements to concat

* Further addition of Immutable<T> to concat

* Fixing up errors from automated tests around complex Array types

* Travis CI objected to default generic parameters being included - fixed

* Edit as review feedback (TDeep) and corrected array indexer

Changed TDepthBoolean to TDeep as suggested.
Removed mutable indexer and added immutable accessor for ImmutableArray
- note that adding this directly into

* Reverting indexer changes that broke tslinting..

* Reverting indexer changes that broke tslinting...

* Re-adding indexer tweaks

Linter seems happy when not running Indexer through Readonly<T>. While I
was at it, I removed the Readonly<T> from Additions and Overrides given
that they are specified immediately below and are completely readonly..

* Corrected default asMutable, added some tests

Responded to PR feedback about asMutable with no parameters.
Added a few test cases for asMutable to ensure that cases have correct
result type.
Fixed an incorrect previous test.

* Removing 'number' seems necessary as the result type differs

* Updated tests to use $ExpectsError (or not) to check immutability of results

* Update seamless-immutable-tests.ts

Added additional test.
Cleaned up some comments.
This commit is contained in:
Stuart Murray
2019-05-08 04:11:52 +10:00
committed by Jesse Trinity
parent 4ef82c966a
commit 2eb83051a5
2 changed files with 73 additions and 17 deletions

View File

@@ -32,8 +32,8 @@ declare namespace SeamlessImmutable {
prototype?: any;
}
interface AsMutableOptions {
deep: boolean;
interface AsMutableOptions<TDeep extends boolean = boolean> {
deep: TDeep;
}
interface ImmutableObjectMixin<T> {
@@ -63,7 +63,9 @@ declare namespace SeamlessImmutable {
getIn(propertyPath: string[]): Immutable<any>;
getIn<TValue>(propertyPath: string[], defaultValue: TValue): Immutable<TValue>;
asMutable(opts?: AsMutableOptions): T;
asMutable(opts?: AsMutableOptions<false>): { [K in keyof T]: Immutable<T[K]> };
asMutable(opts: AsMutableOptions<true>): T;
asMutable(opts: AsMutableOptions): T | { [K in keyof T]: Immutable<T[K]> };
merge(part: DeepPartial<T>, config?: MergeConfig): Immutable<T>;
@@ -91,29 +93,38 @@ declare namespace SeamlessImmutable {
type ImmutableObject<T> = ImmutableObjectMixin<T> & { readonly [P in keyof T]: Immutable<T[P]> };
/** An ImmutableArray provides read-only access to the array elements, and provides functions (such as `map()`) that return immutable data structures. */
type ImmutableArray<T> = Readonly<ImmutableArray.Remaining<T> & ImmutableArray.Additions<T> & ImmutableArray.Overrides<T>>;
type ImmutableArray<T> = Readonly<ImmutableArray.Remaining<T>> & ImmutableArray.Additions<T> & ImmutableArray.Overrides<T> & ImmutableArray.ReadOnlyIndexer<T>;
namespace ImmutableArray {
/** New methods added by seamless-immutable. */
interface Additions<T> {
asMutable(opts?: AsMutableOptions): T[];
asMutable(opts?: AsMutableOptions<false>): Array<Immutable<T>>;
asMutable(opts: AsMutableOptions<true>): T[];
asMutable(opts: AsMutableOptions): T[] | Array<Immutable<T>>;
asObject<U extends object = {}, K extends keyof U = keyof U>(toKeyValue: (item: T) => [K, U[K]]): Immutable<U>;
flatMap<TTarget>(mapFunction: (item: T) => TTarget): Immutable<TTarget extends any[] ? TTarget : TTarget[]>;
}
/** Custom implementation of the array functions, which return Immutable. */
interface Overrides<T> {
map<TTarget>(mapFuction: (item: T) => TTarget): Immutable<TTarget[]>;
filter(filterFunction: (item: T) => boolean): Immutable<T[]>;
forEach(callbackfn: (value: Immutable<T>, index: number, array: Immutable<T[]>) => void, thisArg?: any): void;
map<TTarget>(mapFuction: (item: Immutable<T>) => TTarget): Immutable<TTarget[]>;
filter(filterFunction: (item: Immutable<T>) => boolean): Immutable<T[]>;
slice(start?: number, end?: number): Immutable<T[]>;
concat(...arr: Array<T|T[]|Immutable<T|T[]>>): Immutable<T[]>;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): Immutable<T>;
reduce<TTarget>(callbackfn: (previousValue: TTarget, currentValue: T, currentIndex: number, array: T[]) => TTarget, initialValue?: TTarget): Immutable<TTarget>;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): Immutable<T>;
reduceRight<TTarget>(callbackfn: (previousValue: TTarget, currentValue: T, currentIndex: number, array: T[]) => TTarget, initialValue?: TTarget): Immutable<TTarget>;
concat(...arr: Array<T|T[]|Immutable<T>|Array<Immutable<T>>|Immutable<T[]>>): Immutable<T[]>;
reduce(callbackfn: (previousValue: Immutable<T>, currentValue: Immutable<T>, currentIndex: number, array: Immutable<T[]>) => T): Immutable<T>;
reduce<TTarget>(callbackfn: (previousValue: TTarget, currentValue: Immutable<T>, currentIndex: number, array: Immutable<T[]>) => TTarget, initialValue?: TTarget): Immutable<TTarget>;
reduceRight(callbackfn: (previousValue: Immutable<T>, currentValue: Immutable<T>, currentIndex: number, array: Immutable<T[]>) => T): Immutable<T>;
reduceRight<TTarget>(callbackfn: (previousValue: TTarget, currentValue: Immutable<T>, currentIndex: number, array: Immutable<T[]>) => TTarget, initialValue?: TTarget): Immutable<TTarget>;
}
/** Merging this into Overrides breaks stuff, so this is split out */
interface ReadOnlyIndexer<T> {
readonly [key: number]: Immutable<T>;
}
/** These methods are banned by seamless-immutable. */
type MutatingArrayMethods = Extract<keyof any[], 'push' | 'pop' | 'sort' | 'splice' | 'shift' | 'unshift' | 'reverse'>;
type MutatingArrayMethods = Extract<keyof any[], 'push' | 'pop' | 'sort' | 'splice' | 'shift' | 'unshift' | 'reverse' | number>;
/** NOTE: These methods mutate data, but seamless-immutable does not ban them. We will ban them in our type definitions. */
type AdditionalMutatingArrayMethods = Extract<keyof any[], 'copyWithin' | 'fill'>;
@@ -160,7 +171,7 @@ declare namespace SeamlessImmutable {
function from<T>(obj: T, options?: Options): Immutable<T>;
function isImmutable(target: any): boolean;
function isImmutable<T>(target: T | Immutable<T>): target is Immutable<T>;
function ImmutableError(message: string): Error;
function replace<T, S>(obj: Immutable<T>, valueObj: S, options?: ReplaceConfig): Immutable<S>;

View File

@@ -15,6 +15,13 @@ interface ExtendedUser extends User {
address: Address;
}
// A result type for asMutable({ deep: false } | undefined)
interface NonDeepMutableExtendedUser {
firstName: string;
lastName: string;
address: Immutable.Immutable<Address>;
}
//
// Constructors
// ---------------------------------------------------------------
@@ -89,6 +96,27 @@ interface ExtendedUser extends User {
{
const array: Immutable.Immutable<User[]> = Immutable.from([ { firstName: 'Angry', lastName: 'Monkey' } ]);
const immutableElement: Immutable.Immutable<User> = array[0];
// $ExpectError
immutableElement.firstName = 'Krusty';
const asMutableDefault: Array<Immutable.Immutable<User>> = array.asMutable();
// Can't mutate beyond 1 level deep (in this case only the array itself is mutable, not the items)
// $ExpectError
asMutableDefault[0].firstName = 'Krusty';
const asMutableNotDeep: Array<Immutable.Immutable<User>> = array.asMutable({ deep: false });
// Can't mutate beyond 1 level deep (in this case only the array itself is mutable, not the items)
// $ExpectError
asMutableNotDeep[0].firstName = 'Krusty';
// Can mutate at any depth
const asMutableDeep: User[] = array.asMutable({ deep: true });
asMutableDeep[0].firstName = 'Krusty';
const opaqueMutableParam = { deep: true };
const asMutableOpaque: Array<User | Immutable.Immutable<User>> = array.asMutable(opaqueMutableParam);
// keys. Call the mutable array's 'keys' to ensure compatability
const mutableKeys = array.asMutable().keys();
const keys: typeof mutableKeys = array.keys();
@@ -119,7 +147,7 @@ interface ExtendedUser extends User {
slice4.asMutable();
// concat. Call the mutable array's 'concat' with the same args to ensure compatability. Make sure the output array is immutable.
array.asMutable().concat({ firstName: 'Happy', lastName: 'Cat' });
array.asMutable().concat(Immutable({ firstName: 'Happy', lastName: 'Cat' }));
const concat: Immutable.Immutable<User[]> = array
.concat({ firstName: 'Happy', lastName: 'Cat' }, { firstName: 'Silly', lastName: 'Cat' })
.concat([{firstName: 'saucy', lastName: 'Cat'}], {firstName: 'Fussy', lastName: 'Cat'})
@@ -204,8 +232,25 @@ interface ExtendedUser extends User {
const updatedUser12: Immutable.Immutable<ExtendedUser> = immutableUserEx.setIn([ data.propertyId, 'line1' ], 'Small house');
// asMutable
const mutableUser21: User = immutableUser.asMutable();
const mutableUser22: User = immutableUser.asMutable({ deep: true });
// Can't mutate beyond 1 level deep
const mutableUser21 = immutableUserEx.asMutable();
mutableUser21.firstName = 'Krusty';
// $ExpectError
mutableUser21.address.line1 = 'Example';
// Can't mutate beyond 1 level deep
const mutableUser22 = immutableUserEx.asMutable({ deep: false });
mutableUser22.firstName = 'Krusty';
// $ExpectError
mutableUser22.address.line1 = 'Example';
// Can mutate at any depth
const mutableUser23: ExtendedUser = immutableUserEx.asMutable({ deep: true });
mutableUser23.firstName = 'Krusty';
mutableUser23.address.line1 = 'Example';
const opaqueMutableParam = { deep: true }; // treats as { deep: boolean }
const mutableUser24: ExtendedUser | NonDeepMutableExtendedUser = immutableUserEx.asMutable(opaqueMutableParam);
// merge: merged part is strongly checked as a deeply partial object
const mergedUser: Immutable.Immutable<User> = immutableUserEx.merge({ address: { line1: 'Small house' }, firstName: 'Jack' });