diff --git a/dist/immutable-nonambient.d.ts b/dist/immutable-nonambient.d.ts index 9177cf0e3d..5068a221b5 100644 --- a/dist/immutable-nonambient.d.ts +++ b/dist/immutable-nonambient.d.ts @@ -1537,7 +1537,7 @@ * * @alias toJSON */ - toJS(): any; + toJS(): Object; /** * Shallowly converts this Record to equivalent JS. diff --git a/dist/immutable.d.ts b/dist/immutable.d.ts index 16df13c534..14db658802 100644 --- a/dist/immutable.d.ts +++ b/dist/immutable.d.ts @@ -1537,7 +1537,7 @@ declare module Immutable { * * @alias toJSON */ - toJS(): any; + toJS(): Object; /** * Shallowly converts this Record to equivalent JS. diff --git a/dist/immutable.js.flow b/dist/immutable.js.flow index 10cfa88e7e..2f15b02cfb 100644 --- a/dist/immutable.js.flow +++ b/dist/immutable.js.flow @@ -1033,16 +1033,50 @@ declare class Stack<+T> extends IndexedCollection { declare function Range(start?: number, end?: number, step?: number): IndexedSeq; declare function Repeat(value: T, times?: number): IndexedSeq; -// TODO: Once flow can extend normal Objects we can change this back to actually reflect Record behavior. -// For now fallback to any to not break existing code -declare class Record { - static (spec: T, name?: string): /*T & Record*/any; +declare class Record { + static (spec: Values, name?: string): RecordClass; + constructor(spec: Values, name?: string): RecordClass; - static getDescriptiveName(record: Record): string; + static getDescriptiveName(record: RecordInstance<*>): string; +} + +declare interface RecordClass { + (values: $Shape | ESIterable<[string, any]>): RecordInstance & T; + new (values: $Shape | ESIterable<[string, any]>): RecordInstance & T; +} + +declare class RecordInstance { + size: number; + + has(key: string): boolean; + get>(key: K): /*T[K]*/any; + + equals(other: any): boolean; + hashCode(): number; + + set>(key: K, value: /*T[K]*/any): this; + update>(key: K, updater: (value: /*T[K]*/any) => /*T[K]*/any): this; + merge(...iterables: Array<$Shape | ESIterable<[string, any]>>): this; + mergeDeep(...iterables: Array<$Shape | ESIterable<[string, any]>>): this; + + delete(key: $Keys): this; + remove(key: $Keys): this; + clear(): this; + + setIn(keyPath: ESIterable, value: any): this; + updateIn(keyPath: ESIterable, updater: (value: any) => any): this; + mergeIn(keyPath: ESIterable, ...iterables: Array): this; + mergeDeepIn(keyPath: ESIterable, ...iterables: Array): this; + deleteIn(keyPath: ESIterable): this; + removeIn(keyPath: ESIterable): this; + + toJS(): T; + toObject(): T; + withMutations(mutator: (mutable: this) => this | void): this; + asMutable(): this; + asImmutable(): this; - get(key: $Keys): A; - set(key: $Keys, value: A): /*T & Record*/this; - remove(key: $Keys): /*T & Record*/this; + @@iterator(): Iterator<[$Keys, any]>; } declare function fromJS(json: mixed, reviver?: (k: any, v: Iterable) => any): any; diff --git a/type-definitions/Immutable.d.ts b/type-definitions/Immutable.d.ts index 16df13c534..14db658802 100644 --- a/type-definitions/Immutable.d.ts +++ b/type-definitions/Immutable.d.ts @@ -1537,7 +1537,7 @@ declare module Immutable { * * @alias toJSON */ - toJS(): any; + toJS(): Object; /** * Shallowly converts this Record to equivalent JS. diff --git a/type-definitions/immutable.js.flow b/type-definitions/immutable.js.flow index 10cfa88e7e..2f15b02cfb 100644 --- a/type-definitions/immutable.js.flow +++ b/type-definitions/immutable.js.flow @@ -1033,16 +1033,50 @@ declare class Stack<+T> extends IndexedCollection { declare function Range(start?: number, end?: number, step?: number): IndexedSeq; declare function Repeat(value: T, times?: number): IndexedSeq; -// TODO: Once flow can extend normal Objects we can change this back to actually reflect Record behavior. -// For now fallback to any to not break existing code -declare class Record { - static (spec: T, name?: string): /*T & Record*/any; +declare class Record { + static (spec: Values, name?: string): RecordClass; + constructor(spec: Values, name?: string): RecordClass; - static getDescriptiveName(record: Record): string; + static getDescriptiveName(record: RecordInstance<*>): string; +} + +declare interface RecordClass { + (values: $Shape | ESIterable<[string, any]>): RecordInstance & T; + new (values: $Shape | ESIterable<[string, any]>): RecordInstance & T; +} + +declare class RecordInstance { + size: number; + + has(key: string): boolean; + get>(key: K): /*T[K]*/any; + + equals(other: any): boolean; + hashCode(): number; + + set>(key: K, value: /*T[K]*/any): this; + update>(key: K, updater: (value: /*T[K]*/any) => /*T[K]*/any): this; + merge(...iterables: Array<$Shape | ESIterable<[string, any]>>): this; + mergeDeep(...iterables: Array<$Shape | ESIterable<[string, any]>>): this; + + delete(key: $Keys): this; + remove(key: $Keys): this; + clear(): this; + + setIn(keyPath: ESIterable, value: any): this; + updateIn(keyPath: ESIterable, updater: (value: any) => any): this; + mergeIn(keyPath: ESIterable, ...iterables: Array): this; + mergeDeepIn(keyPath: ESIterable, ...iterables: Array): this; + deleteIn(keyPath: ESIterable): this; + removeIn(keyPath: ESIterable): this; + + toJS(): T; + toObject(): T; + withMutations(mutator: (mutable: this) => this | void): this; + asMutable(): this; + asImmutable(): this; - get(key: $Keys): A; - set(key: $Keys, value: A): /*T & Record*/this; - remove(key: $Keys): /*T & Record*/this; + @@iterator(): Iterator<[$Keys, any]>; } declare function fromJS(json: mixed, reviver?: (k: any, v: Iterable) => any): any; diff --git a/type-definitions/tests/immutable-flow.js b/type-definitions/tests/immutable-flow.js index ed085bff52..465abaadbd 100644 --- a/type-definitions/tests/immutable-flow.js +++ b/type-definitions/tests/immutable-flow.js @@ -765,6 +765,3 @@ numberStack = Stack(['a']).flatten() { const stringSequence: IndexedSeq = Repeat(0, 1) } // $ExpectError { const stringSequence: IndexedSeq = Range(0, 0, 0) } - -/* Record */ -// TODO diff --git a/type-definitions/tests/record.js b/type-definitions/tests/record.js new file mode 100644 index 0000000000..725832c758 --- /dev/null +++ b/type-definitions/tests/record.js @@ -0,0 +1,60 @@ +/* + * @flow + */ + +// Some tests look like they are repeated in order to avoid false positives. +// Flow might not complain about an instance of (what it thinks is) T to be assigned to T + +import { Record } from '../../'; + +const Point2 = Record({x:0, y:0}); +const Point3 = Record({x:0, y:0, z:0}); +const GeoPoint = Record({lat:(null: ?number), lon:(null: ?number)}); + +let origin2 = Point2({}); +let origin3 = Point3({}); +let geo = GeoPoint({lat:34}); +// $ExpectError +const mistake = Point2({x:'string'}); +origin3 = GeoPoint({lat:34}) +geo = Point3({}); + +const px = origin2.get('x'); +const px2 = origin2.x; +// $ExpectError +const pz = origin2.get('z'); +// $ExpectError +const pz2 = origin2.z; + +origin2.set('x', 4); +// Note: this should be an error, but Flow does not yet support index types. +origin2.set('x', 'string'); +// $ExpectError +origin2.set('z', 3); + +const name: string = Record.getDescriptiveName(origin2); +// $ExpectError +const name: string = Record.getDescriptiveName({}); + +// Note: need to cast through any when extending Records as if they ere classes +class ABClass extends (Record({a:1, b:2}): any) { + setA(a: number) { + return this.set('a', a); + } + + setB(b: number) { + return this.set('b', b); + } +} + +var t1 = new ABClass({a: 1}); +var t2 = t1.setA(3); +var t3 = t2.setB(10); +// Note: flow does not check extended Record classes yet +var t4 = t2.setC(10); + +var t1a = t1.a; +// Note: flow does not check extended Record classes yet +var t1a: string = t1.a; +// Note: flow does not check extended Record classes yet +var t1c = t1.c;