From 5042fd58a58ec15f0a226870912d9e95be96022a Mon Sep 17 00:00:00 2001 From: Marc Fallows Date: Thu, 1 Mar 2018 11:39:04 +1100 Subject: [PATCH 1/4] Make observable map structurally match an ES6 Map --- src/types/observablemap.ts | 22 +++++++++++++++++++++- src/utils/iterable.ts | 12 ++++++++---- src/utils/utils.ts | 6 ++++++ test/base/map.js | 6 ++++++ test/base/typescript-tests.ts | 5 +++++ test/tsconfig.json | 5 ++++- tsconfig.json | 2 +- 7 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/types/observablemap.ts b/src/types/observablemap.ts index 63c80dbc4..2ad8266fa 100644 --- a/src/types/observablemap.ts +++ b/src/types/observablemap.ts @@ -11,7 +11,8 @@ import { deprecated, isES6Map, getMapLikeKeys, - fail + fail, + declareStringTag } from "../utils/utils" import { IInterceptable, @@ -40,9 +41,18 @@ export interface IMap { get(key: K): V | undefined has(key: K): boolean set(key: K, value?: V): this + [Symbol.toStringTag]: "Map" + [Symbol.iterator](): IterableIterator<[K, V]> + entries(): IterableIterator<[K, V]> + keys(): IterableIterator + values(): IterableIterator readonly size: number } +export interface IterableIterator extends Iterator { + [Symbol.iterator](): IterableIterator +} + export interface IKeyValueMap { [key: string]: V } @@ -376,10 +386,20 @@ export class ObservableMap } } +/** + * Include the interface to cover the implementations added to the prototype directly. + */ +export interface ObservableMap { + [Symbol.toStringTag]: "Map" + [Symbol.iterator](): IterableIterator<[string, V]> +} + declareIterator(ObservableMap.prototype, function() { return this.entries() }) +declareStringTag(ObservableMap.prototype, "Map") + export function map(initialValues?: IObservableMapInitialValues): ObservableMap { deprecated("`mobx.map` is deprecated, use `new ObservableMap` or `mobx.observable.map` instead") return observable.map(initialValues) diff --git a/src/utils/iterable.ts b/src/utils/iterable.ts index e131cd368..072b78daa 100644 --- a/src/utils/iterable.ts +++ b/src/utils/iterable.ts @@ -10,11 +10,15 @@ function iteratorSymbol() { export const IS_ITERATING_MARKER = "__$$iterating" +export interface IteratorResult { + done: boolean + value: T +} + export interface Iterator { - next(): { - done: boolean - value?: T - } + next(value?: any): IteratorResult + return?(value?: any): IteratorResult + throw?(e?: any): IteratorResult } export function arrayAsIterator(array: T[]): T[] & Iterator { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 1beafe597..8a1c721a4 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -192,6 +192,12 @@ export function toPrimitive(value) { return value === null ? null : typeof value === "object" ? "" + value : value } +const stringTagSymbol = (typeof Symbol === "function" && Symbol.toStringTag) || "@@toStringTag" + +export function declareStringTag(prototType, tag: string) { + addHiddenFinalProp(prototType, stringTagSymbol, tag) +} + import { globalState } from "../core/globalstate" import { IObservableArray, isObservableArray } from "../types/observablearray" import { isObservableMap, ObservableMap, IKeyValueMap } from "../types/observablemap" diff --git a/test/base/map.js b/test/base/map.js index 213ca63ec..ef840c438 100644 --- a/test/base/map.js +++ b/test/base/map.js @@ -642,3 +642,9 @@ test("#1258 cannot replace maps anymore", () => { const items = mobx.observable.map() items.replace(mobx.observable.map()) }) + +test("map should implement toStringTag", () => { + const m = mobx.observable.map({ a: 1 }) + const name = Object.prototype.toString.call(m) + expect(name).toBe("[object Map]") +}) diff --git a/test/base/typescript-tests.ts b/test/base/typescript-tests.ts index 4fba5ca96..f8f5973ac 100644 --- a/test/base/typescript-tests.ts +++ b/test/base/typescript-tests.ts @@ -1247,3 +1247,8 @@ test("1072 - @observable without initial value and observe before first access", const user = new User() observe(user, "loginCount", () => {}) }) + +test("map should structurally match ES6 Map", () => { + // Including this line strictly for type checking. + const m: Map = mobx.observable.map({ a: 1, b: 2 }) +}) diff --git a/test/tsconfig.json b/test/tsconfig.json index 658dfe651..8205d8813 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -4,6 +4,9 @@ "target": "es5", "noImplicitAny": true, "sourceMap": false, - "experimentalDecorators": true + "experimentalDecorators": true, + "lib": [ + "es2015" + ] } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3d4f2cc45..5118ad8c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "noImplicitAny": false, "moduleResolution": "node", "lib": [ - "es5", + "es2015", "dom" ] }, From ad9346282ab588a0623679be29450aafe9de1ddd Mon Sep 17 00:00:00 2001 From: Marc Fallows Date: Thu, 1 Mar 2018 11:47:21 +1100 Subject: [PATCH 2/4] keep it consistent --- src/utils/utils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 8a1c721a4..502123fae 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -192,10 +192,12 @@ export function toPrimitive(value) { return value === null ? null : typeof value === "object" ? "" + value : value } -const stringTagSymbol = (typeof Symbol === "function" && Symbol.toStringTag) || "@@toStringTag" +export function stringTagSymbol() { + return (typeof Symbol === "function" && Symbol.toStringTag) || "@@toStringTag" +} export function declareStringTag(prototType, tag: string) { - addHiddenFinalProp(prototType, stringTagSymbol, tag) + addHiddenFinalProp(prototType, stringTagSymbol(), tag) } import { globalState } from "../core/globalstate" From 1348c14ab23af6428a7cd831ec1cbedeed69cb68 Mon Sep 17 00:00:00 2001 From: Marc Fallows Date: Thu, 1 Mar 2018 11:51:25 +1100 Subject: [PATCH 3/4] necessary semi --- src/types/observablemap.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/types/observablemap.ts b/src/types/observablemap.ts index 2ad8266fa..6d150a370 100644 --- a/src/types/observablemap.ts +++ b/src/types/observablemap.ts @@ -41,7 +41,8 @@ export interface IMap { get(key: K): V | undefined has(key: K): boolean set(key: K, value?: V): this - [Symbol.toStringTag]: "Map" + // prettier-ignore + [Symbol.toStringTag]: "Map"; // necessary semicolon [Symbol.iterator](): IterableIterator<[K, V]> entries(): IterableIterator<[K, V]> keys(): IterableIterator @@ -390,7 +391,8 @@ export class ObservableMap * Include the interface to cover the implementations added to the prototype directly. */ export interface ObservableMap { - [Symbol.toStringTag]: "Map" + // prettier-ignore + [Symbol.toStringTag]: "Map"; // necessary semicolon [Symbol.iterator](): IterableIterator<[string, V]> } From 23cd7612573fe0db2daae3872fa0b31174a69987 Mon Sep 17 00:00:00 2001 From: Marc Fallows Date: Thu, 1 Mar 2018 12:11:10 +1100 Subject: [PATCH 4/4] bump size --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8d792d4a..f993136cd 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "quick-build": "tsc --pretty", "small-build": "node scripts/build.js", "lint": "tslint -c tslint.json src/*.ts src/types/*.ts src/api/*.ts src/core/*.ts src/utils/*.ts", - "size": "size-limit 20KB lib/mobx.js", + "size": "size-limit 21KB lib/mobx.js", "precommit": "lint-staged" }, "repository": {