Created
October 9, 2017 22:30
-
-
Save bassjacob/b23e7bde65291ed927d46d12ccc1512f to your computer and use it in GitHub Desktop.
immutable 3.x.x flowtype binding
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This file provides type definitions for use with the Flow type checker. | |
* | |
* An important caveat when using these definitions is that the types for | |
* `Iterable.Keyed`, `Iterable.Indexed`, `Seq.Keyed`, and so on are stubs. | |
* When referring to those types, you can get the proper definitions by | |
* importing the types `KeyedIterable`, `IndexedIterable`, `KeyedSeq`, etc. | |
* For example, | |
* | |
* import { Seq } from 'immutable' | |
* import type { IndexedIterable, IndexedSeq } from 'immutable' | |
* | |
* const someSeq: IndexedSeq<number> = Seq.Indexed.of(1, 2, 3) | |
* | |
* function takesASeq<T, TS: IndexedIterable<T>>(iter: TS): TS { | |
* return iter.butLast() | |
* } | |
* | |
* takesASeq(someSeq) | |
* | |
* @flow | |
*/ | |
/* | |
* Alias for ECMAScript `Iterable` type, declared in | |
* https://github.com/facebook/flow/blob/master/lib/core.js | |
* | |
* Note that Immutable values implement the `ESIterable` interface. | |
*/ | |
type ESIterable<T> = $Iterable<T,void,void>; | |
declare module "immutable" { | |
declare export class Iterable<K, V> extends _Iterable<K, V, typeof KeyedIterable, typeof IndexedIterable, typeof SetIterable> {} | |
declare export class _Iterable<K, V, KI, II, SI> { | |
static Keyed: KI; | |
static Indexed: II; | |
static Set: SI; | |
static isIterable(maybeIterable: any): boolean; | |
static isKeyed(maybeKeyed: any): boolean; | |
static isIndexed(maybeIndexed: any): boolean; | |
static isAssociative(maybeAssociative: any): boolean; | |
static isOrdered(maybeOrdered: any): boolean; | |
equals(other: Iterable<K,V>): boolean; | |
hashCode(): number; | |
get(key: K): V; | |
get<V_>(key: K, notSetValue: V_): V|V_; | |
has(key: K): boolean; | |
includes(value: V): boolean; | |
contains(value: V): boolean; | |
first(): V; | |
last(): V; | |
getIn<T>(searchKeyPath: ESIterable<any>, notSetValue: T): T; | |
getIn<T>(searchKeyPath: ESIterable<any>): T; | |
hasIn(searchKeyPath: ESIterable<any>): boolean; | |
toJS(): any; | |
toArray(): V[]; | |
toObject(): { [key: string]: V }; | |
toMap(): Map<K,V>; | |
toOrderedMap(): Map<K,V>; | |
toSet(): Set<V>; | |
toOrderedSet(): Set<V>; | |
toList(): List<V>; | |
toStack(): Stack<V>; | |
toSeq(): Seq<K,V>; | |
toKeyedSeq(): KeyedSeq<K,V>; | |
toIndexedSeq(): IndexedSeq<V>; | |
toSetSeq(): SetSeq<V>; | |
keys(): Iterator<K>; | |
values(): Iterator<V>; | |
entries(): Iterator<[K,V]>; | |
keySeq(): IndexedSeq<K>; | |
valueSeq(): IndexedSeq<V>; | |
entrySeq(): IndexedSeq<[K,V]>; | |
reverse(): this; | |
sort(comparator?: (valueA: V, valueB: V) => number): this; | |
sortBy<C>( | |
comparatorValueMapper: (value: V, key: K, iter: this) => C, | |
comparator?: (valueA: C, valueB: C) => number | |
): this; | |
groupBy<G>( | |
grouper: (value: V, key: K, iter: this) => G, | |
context?: any | |
): KeyedSeq<G, this>; | |
forEach( | |
sideEffect: (value: V, key: K, iter: this) => any, | |
context?: any | |
): number; | |
slice(begin?: number, end?: number): this; | |
rest(): this; | |
butLast(): this; | |
skip(amount: number): this; | |
skipLast(amount: number): this; | |
skipWhile(predicate: (value: V, key: K, iter: this) => mixed, context?: any): this; | |
skipUntil(predicate: (value: V, key: K, iter: this) => mixed, context?: any): this; | |
take(amount: number): this; | |
takeLast(amount: number): this; | |
takeWhile(predicate: (value: V, key: K, iter: this) => mixed, context?: any): this; | |
takeUntil(predicate: (value: V, key: K, iter: this) => mixed, context?: any): this; | |
flatten(depth?: number): /*this*/Iterable<any,any>; | |
flatten(shallow?: boolean): /*this*/Iterable<any,any>; | |
filter( | |
predicate: (value: V, key: K, iter: this) => mixed, | |
context?: any | |
): this; | |
filterNot( | |
predicate: (value: V, key: K, iter: this) => mixed, | |
context?: any | |
): this; | |
reduce<R>( | |
reducer: (reduction: R, value: V, key: K, iter: this) => R, | |
initialReduction?: R, | |
context?: any, | |
): R; | |
reduceRight<R>( | |
reducer: (reduction: R, value: V, key: K, iter: this) => R, | |
initialReduction?: R, | |
context?: any, | |
): R; | |
every(predicate: (value: V, key: K, iter: this) => mixed, context?: any): boolean; | |
some(predicate: (value: V, key: K, iter: this) => mixed, context?: any): boolean; | |
join(separator?: string): string; | |
isEmpty(): boolean; | |
count(predicate?: (value: V, key: K, iter: this) => mixed, context?: any): number; | |
countBy<G>(grouper: (value: V, key: K, iter: this) => G, context?: any): Map<G,number>; | |
find( | |
predicate: (value: V, key: K, iter: this) => mixed, | |
context?: any, | |
): ?V; | |
find<V_>( | |
predicate: (value: V, key: K, iter: this) => mixed, | |
context: any, | |
notSetValue: V_ | |
): V|V_; | |
findLast( | |
predicate: (value: V, key: K, iter: this) => mixed, | |
context?: any, | |
): ?V; | |
findLast<V_>( | |
predicate: (value: V, key: K, iter: this) => mixed, | |
context: any, | |
notSetValue: V_ | |
): V|V_; | |
findEntry(predicate: (value: V, key: K, iter: this) => mixed): ?[K,V]; | |
findLastEntry(predicate: (value: V, key: K, iter: this) => mixed): ?[K,V]; | |
findKey(predicate: (value: V, key: K, iter: this) => mixed, context?: any): ?K; | |
findLastKey(predicate: (value: V, key: K, iter: this) => mixed, context?: any): ?K; | |
keyOf(searchValue: V): ?K; | |
lastKeyOf(searchValue: V): ?K; | |
max(comparator?: (valueA: V, valueB: V) => number): V; | |
maxBy<C>( | |
comparatorValueMapper: (value: V, key: K, iter: this) => C, | |
comparator?: (valueA: C, valueB: C) => number | |
): V; | |
min(comparator?: (valueA: V, valueB: V) => number): V; | |
minBy<C>( | |
comparatorValueMapper: (value: V, key: K, iter: this) => C, | |
comparator?: (valueA: C, valueB: C) => number | |
): V; | |
isSubset(iter: Iterable<any, V>): boolean; | |
isSubset(iter: ESIterable<V>): boolean; | |
isSuperset(iter: Iterable<any, V>): boolean; | |
isSuperset(iter: ESIterable<V>): boolean; | |
} | |
declare export class KeyedIterable<K,V> extends Iterable<K,V> { | |
static <K,V>(iter?: ESIterable<[K,V]>): KeyedIterable<K,V>; | |
static <K,V>(obj?: { [key: K]: V }): KeyedIterable<K,V>; | |
@@iterator(): Iterator<[K,V]>; | |
toSeq(): KeyedSeq<K,V>; | |
flip(): /*this*/KeyedIterable<V,K>; | |
mapKeys<K_>( | |
mapper: (key: K, value: V, iter: this) => K_, | |
context?: any | |
): /*this*/KeyedIterable<K_,V>; | |
mapEntries<K_,V_>( | |
mapper: (entry: [K,V], index: number, iter: this) => [K_,V_], | |
context?: any | |
): /*this*/KeyedIterable<K_,V_>; | |
concat(...iters: ESIterable<[K,V]>[]): this; | |
map<V_>( | |
mapper: (value: V, key: K, iter: this) => V_, | |
context?: any | |
): /*this*/KeyedIterable<K,V_>; | |
flatMap<K_, V_>( | |
mapper: (value: V, key: K, iter: this) => ESIterable<[K_,V_]>, | |
context?: any | |
): /*this*/KeyedIterable<K_,V_>; | |
flatten(depth?: number): /*this*/KeyedIterable<any,any>; | |
flatten(shallow?: boolean): /*this*/KeyedIterable<any,any>; | |
} | |
declare export class IndexedIterable<T> extends Iterable<number,T> { | |
static <T>(iter?: ESIterable<T>): IndexedIterable<T>; | |
@@iterator(): Iterator<T>; | |
toSeq(): IndexedSeq<T>; | |
fromEntrySeq<K,V>(): KeyedSeq<K,V>; | |
interpose(separator: T): this; | |
interleave(...iterables: ESIterable<T>[]): this; | |
splice( | |
index: number, | |
removeNum: number, | |
...values: T[] | |
): this; | |
zip<A>( | |
a: ESIterable<A>, | |
$?: null | |
): IndexedIterable<[T,A]>; | |
zip<A,B>( | |
a: ESIterable<A>, | |
b: ESIterable<B>, | |
$?: null | |
): IndexedIterable<[T,A,B]>; | |
zip<A,B,C>( | |
a: ESIterable<A>, | |
b: ESIterable<B>, | |
c: ESIterable<C>, | |
$?: null | |
): IndexedIterable<[T,A,B,C]>; | |
zip<A,B,C,D>( | |
a: ESIterable<A>, | |
b: ESIterable<B>, | |
c: ESIterable<C>, | |
d: ESIterable<D>, | |
$?: null | |
): IndexedIterable<[T,A,B,C,D]>; | |
zip<A,B,C,D,E>( | |
a: ESIterable<A>, | |
b: ESIterable<B>, | |
c: ESIterable<C>, | |
d: ESIterable<D>, | |
e: ESIterable<E>, | |
$?: null | |
): IndexedIterable<[T,A,B,C,D,E]>; | |
zipWith<A,R>( | |
zipper: (value: T, a: A) => R, | |
a: ESIterable<A>, | |
$?: null | |
): IndexedIterable<R>; | |
zipWith<A,B,R>( | |
zipper: (value: T, a: A, b: B) => R, | |
a: ESIterable<A>, | |
b: ESIterable<B>, | |
$?: null | |
): IndexedIterable<R>; | |
zipWith<A,B,C,R>( | |
zipper: (value: T, a: A, b: B, c: C) => R, | |
a: ESIterable<A>, | |
b: ESIterable<B>, | |
c: ESIterable<C>, | |
$?: null | |
): IndexedIterable<R>; | |
zipWith<A,B,C,D,R>( | |
zipper: (value: T, a: A, b: B, c: C, d: D) => R, | |
a: ESIterable<A>, | |
b: ESIterable<B>, | |
c: ESIterable<C>, | |
d: ESIterable<D>, | |
$?: null | |
): IndexedIterable<R>; | |
zipWith<A,B,C,D,E,R>( | |
zipper: (value: T, a: A, b: B, c: C, d: D, e: E) => R, | |
a: ESIterable<A>, | |
b: ESIterable<B>, | |
c: ESIterable<C>, | |
d: ESIterable<D>, | |
e: ESIterable<E>, | |
$?: null | |
): IndexedIterable<R>; | |
indexOf(searchValue: T): number; | |
lastIndexOf(searchValue: T): number; | |
findIndex( | |
predicate: (value: T, index: number, iter: this) => mixed, | |
context?: any | |
): number; | |
findLastIndex( | |
predicate: (value: T, index: number, iter: this) => mixed, | |
context?: any | |
): number; | |
concat(...iters: ESIterable<T>[]): this; | |
map<U>( | |
mapper: (value: T, index: number, iter: this) => U, | |
context?: any | |
): /*this*/IndexedIterable<U>; | |
flatMap<U>( | |
mapper: (value: T, index: number, iter: this) => ESIterable<U>, | |
context?: any | |
): /*this*/IndexedIterable<U>; | |
flatten(depth?: number): /*this*/IndexedIterable<any>; | |
flatten(shallow?: boolean): /*this*/IndexedIterable<any>; | |
} | |
declare export class SetIterable<T> extends Iterable<T,T> { | |
static <T>(iter?: ESIterable<T>): SetIterable<T>; | |
@@iterator(): Iterator<T>; | |
toSeq(): SetSeq<T>; | |
concat(...iters: ESIterable<T>[]): this; | |
// `map` and `flatMap` cannot be defined further up the hiearchy, because the | |
// implementation for `KeyedIterable` allows the value type to change without | |
// constraining the key type. That does not work for `SetIterable` - the value | |
// and key types *must* match. | |
map<U>( | |
mapper: (value: T, value: T, iter: this) => U, | |
context?: any | |
): /*this*/SetIterable<U>; | |
flatMap<U>( | |
mapper: (value: T, value: T, iter: this) => ESIterable<U>, | |
context?: any | |
): /*this*/SetIterable<U>; | |
flatten(depth?: number): /*this*/SetIterable<any>; | |
flatten(shallow?: boolean): /*this*/SetIterable<any>; | |
} | |
declare export class Collection<K,V> extends _Iterable<K,V, typeof KeyedCollection, typeof IndexedCollection, typeof SetCollection> { | |
size: number; | |
} | |
declare export class KeyedCollection<K,V> extends Collection<K,V> mixins KeyedIterable<K,V> { | |
toSeq(): KeyedSeq<K,V>; | |
} | |
declare export class IndexedCollection<T> extends Collection<number,T> mixins IndexedIterable<T> { | |
toSeq(): IndexedSeq<T>; | |
} | |
declare export class SetCollection<T> extends Collection<T,T> mixins SetIterable<T> { | |
toSeq(): SetSeq<T>; | |
} | |
declare export class Seq<K,V> extends _Iterable<K,V, typeof KeyedSeq, typeof IndexedSeq, typeof SetSeq> { | |
static <K,V>(iter: KeyedSeq<K,V>): KeyedSeq<K,V>; | |
static <T> (iter: SetSeq<T>): SetSeq<K,V>; | |
static <T> (iter?: ESIterable<T>): IndexedSeq<T>; | |
static <K,V>(iter: { [key: K]: V }): KeyedSeq<K,V>; | |
static isSeq(maybeSeq: any): boolean; | |
size: ?number; | |
cacheResult(): this; | |
toSeq(): this; | |
} | |
declare export class KeyedSeq<K,V> extends Seq<K,V> mixins KeyedIterable<K,V> { | |
static <K,V>(iter?: ESIterable<[K,V]>): KeyedSeq<K,V>; | |
static <K,V>(iter?: { [key: K]: V }): KeyedSeq<K,V>; | |
} | |
declare export class IndexedSeq<T> extends Seq<number,T> mixins IndexedIterable<T> { | |
static <T>(iter?: ESIterable<T>): IndexedSeq<T>; | |
static of<T>(...values: T[]): IndexedSeq<T>; | |
} | |
declare export class SetSeq<T> extends Seq<T,T> mixins SetIterable<T> { | |
static <T>(iter?: ESIterable<T>): IndexedSeq<T>; | |
static of<T>(...values: T[]): SetSeq<T>; | |
} | |
declare export class List<T> extends IndexedCollection<T> { | |
static (iterable?: ESIterable<T>): List<T>; | |
static isList(maybeList: any): boolean; | |
static of<T>(...values: T[]): List<T>; | |
set<U>(index: number, value: U): List<T|U>; | |
delete(index: number): this; | |
remove(index: number): this; | |
insert<U>(index: number, value: U): List<T|U>; | |
clear(): this; | |
push<U>(...values: U[]): List<T|U>; | |
pop(): this; | |
unshift<U>(...values: U[]): List<T|U>; | |
shift(): this; | |
update<U>(updater: (value: this) => List<U>): List<U>; | |
update<U>(index: number, updater: (value: T) => U): List<T|U>; | |
update<U>(index: number, notSetValue: U, updater: (value: T) => U): List<T|U>; | |
merge<U>(...iterables: ESIterable<U>[]): List<T|U>; | |
mergeWith<U,V>( | |
merger: (previous: T, next: U, key: number) => V, | |
...iterables: ESIterable<U>[] | |
): List<T|U|V>; | |
mergeDeep<U>(...iterables: ESIterable<U>[]): List<T|U>; | |
mergeDeepWith<U,V>( | |
merger: (previous: T, next: U, key: number) => V, | |
...iterables: ESIterable<U>[] | |
): List<T|U|V>; | |
setSize(size: number): List<?T>; | |
setIn(keyPath: ESIterable<any>, value: any): List<T>; | |
deleteIn(keyPath: ESIterable<any>, value: any): this; | |
removeIn(keyPath: ESIterable<any>, value: any): this; | |
updateIn(keyPath: ESIterable<any>, notSetValue: any, value: any): List<T>; | |
updateIn(keyPath: ESIterable<any>, value: any): List<T>; | |
mergeIn(keyPath: ESIterable<any>, ...iterables: ESIterable<any>[]): List<T>; | |
mergeDeepIn(keyPath: ESIterable<any>, ...iterables: ESIterable<any>[]): List<T>; | |
withMutations(mutator: (mutable: this) => any): this; | |
asMutable(): this; | |
asImmutable(): this; | |
// Overrides that specialize return types | |
map<M>( | |
mapper: (value: T, index: number, iter: this) => M, | |
context?: any | |
): List<M>; | |
flatMap<M>( | |
mapper: (value: T, index: number, iter: this) => ESIterable<M>, | |
context?: any | |
): List<M>; | |
flatten(depth?: number): /*this*/List<any>; | |
flatten(shallow?: boolean): /*this*/List<any>; | |
} | |
declare export class Map<K,V> extends KeyedCollection<K,V> { | |
static <K, V>(): Map<K, V>; | |
static <V>(obj?: {[key: string]: V}): Map<string, V>; | |
static <K, V>(iterable?: ESIterable<[K,V]>): Map<K, V>; | |
static isMap(maybeMap: any): boolean; | |
set<K_, V_>(key: K_, value: V_): Map<K|K_, V|V_>; | |
delete(key: K): this; | |
remove(key: K): this; | |
clear(): this; | |
update<K_,V_>(updater: (value: this) => Map<K_,V_>): Map<K_,V_>; | |
update<V_>(key: K, updater: (value: V) => V_): Map<K,V|V_>; | |
update<V_>(key: K, notSetValue: V_, updater: (value: V) => V_): Map<K,V|V_>; | |
merge<K_,V_>( | |
...iterables: (ESIterable<[K_,V_]> | { [key: K_]: V_ })[] | |
): Map<K|K_,V|V_>; | |
mergeWith<K_,W,X>( | |
merger: (previous: V, next: W, key: number) => X, | |
...iterables: ESIterable<W>[] | |
): Map<K,V|W|X>; | |
mergeDeep<K_,V_>( | |
...iterables: (ESIterable<[K_,V_]> | { [key: K_]: V_ })[] | |
): Map<K|K_,V|V_>; | |
mergeDeepWith<K_,W,X>( | |
merger: (previous: V, next: W, key: number) => X, | |
...iterables: ESIterable<W>[] | |
): Map<K,V|W|X>; | |
setIn(keyPath: ESIterable<any>, value: any): Map<K,V>; | |
deleteIn(keyPath: ESIterable<any>, value: any): this; | |
removeIn(keyPath: ESIterable<any>, value: any): this; | |
updateIn(keyPath: ESIterable<any>, notSetValue: any, value: any): Map<K,V>; | |
updateIn(keyPath: ESIterable<any>, value: any): Map<K,V>; | |
mergeIn(keyPath: ESIterable<any>, ...iterables: ESIterable<any>[]): Map<K,V>; | |
mergeDeepIn(keyPath: ESIterable<any>, ...iterables: ESIterable<any>[]): Map<K,V>; | |
withMutations(mutator: (mutable: this) => any): this; | |
asMutable(): this; | |
asImmutable(): this; | |
// Overrides that specialize return types | |
map<V_>( | |
mapper: (value: V, key: K, iter: this) => V_, | |
context?: any | |
): Map<K,V_>; | |
flatMap<K_,V_>( | |
mapper: (value: V, key: K, iter: this) => ESIterable<[K_,V_]>, | |
context?: any | |
): Map<K_,V_>; | |
flip(): Map<V,K>; | |
mapKeys<K_>( | |
mapper: (key: K, value: V, iter: this) => K_, | |
context?: any | |
): Map<K_,V>; | |
flatten(depth?: number): /*this*/Map<any,any>; | |
flatten(shallow?: boolean): /*this*/Map<any,any>; | |
} | |
// OrderedMaps have nothing that Maps do not have. We do not need to override constructor & other statics | |
declare export class OrderedMap<K,V> extends Map<K,V> { | |
static isOrderedMap(maybeOrderedMap: any): bool; | |
} | |
declare export class Set<T> extends SetCollection<T> { | |
static <T>(iterable?: ESIterable<T>): Set<T>; | |
static isSet(maybeSet: any): boolean; | |
static of<T>(...values: T[]): Set<T>; | |
static fromKeys<T>(iter: ESIterable<[T,any]>): Set<T>; | |
static fromKeys(iter: { [key: string]: any }): Set<string>; | |
add<U>(value: U): Set<T|U>; | |
delete(value: T): this; | |
remove(value: T): this; | |
clear(): this; | |
union<U>(...iterables: ESIterable<U>[]): Set<T|U>; | |
merge<U>(...iterables: ESIterable<U>[]): Set<T|U>; | |
intersect<U>(...iterables: ESIterable<U>[]): Set<T&U>; | |
subtract<U>(...iterables: ESIterable<U>[]): Set<T>; | |
withMutations(mutator: (mutable: this) => any): this; | |
asMutable(): this; | |
asImmutable(): this; | |
// Overrides that specialize return types | |
map<M>( | |
mapper: (value: T, value: T, iter: this) => M, | |
context?: any | |
): Set<M>; | |
flatMap<M>( | |
mapper: (value: T, value: T, iter: this) => ESIterable<M>, | |
context?: any | |
): Set<M>; | |
flatten(depth?: number): /*this*/Set<any>; | |
flatten(shallow?: boolean): /*this*/Set<any>; | |
} | |
// OrderedSets have nothing that Sets do not have. We do not need to override constructor & other statics | |
declare export class OrderedSet<T> extends Set<T> { | |
static isOrderedSet(maybeOrderedSet: any): bool; | |
} | |
declare export class Stack<T> extends IndexedCollection<T> { | |
static <T>(iterable?: ESIterable<T>): Stack<T>; | |
static isStack(maybeStack: any): boolean; | |
static of<T>(...values: T[]): Stack<T>; | |
peek(): T; | |
clear(): this; | |
unshift<U>(...values: U[]): Stack<T|U>; | |
unshiftAll<U>(iter: ESIterable<U>): Stack<T|U>; | |
shift(): this; | |
push<U>(...values: U[]): Stack<T|U>; | |
pushAll<U>(iter: ESIterable<U>): Stack<T|U>; | |
pop(): this; | |
withMutations(mutator: (mutable: this) => any): this; | |
asMutable(): this; | |
asImmutable(): this; | |
// Overrides that specialize return types | |
map<U>( | |
mapper: (value: T, index: number, iter: this) => U, | |
context?: any | |
): Stack<U>; | |
flatMap<U>( | |
mapper: (value: T, index: number, iter: this) => ESIterable<U>, | |
context?: any | |
): Stack<U>; | |
flatten(depth?: number): /*this*/Stack<any>; | |
flatten(shallow?: boolean): /*this*/Stack<any>; | |
} | |
declare export function Range(start?: number, end?: number, step?: number): IndexedSeq<number>; | |
declare export function Repeat<T>(value: T, times?: number): IndexedSeq<T>; | |
//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 export class Record<T: Object> { | |
static <T: Object>(spec: T, name?: string): /*T & Record<T>*/any; | |
get<A>(key: $Keys<T>): A; | |
set<A>(key: $Keys<T>, value: A): /*T & Record<T>*/this; | |
remove(key: $Keys<T>): /*T & Record<T>*/this; | |
} | |
declare export function fromJS(json: any, reviver?: (k: any, v: Iterable<any,any>) => any): any; | |
declare export function is(first: any, second: any): boolean; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment