From 312a5968e874ea997efa24849835242330cef2f0 Mon Sep 17 00:00:00 2001 From: Conrad Buck Date: Sun, 19 Aug 2018 11:17:23 -0700 Subject: [PATCH] Add concrete types; reorganize. --- .babelrc | 4 +- .editorconfig | 2 +- jest_setup.js | 46 ++ package.json | 16 +- src/__test__/collection-indexed.test.js | 64 +++ src/__test__/collection-keyed.test.js | 74 +++ src/__test__/collection-set.test.js | 63 +++ src/__test__/collection.test.js | 135 ++++++ src/__test__/data.js | 39 ++ src/__test__/helpers/make-test-method.js | 90 ++++ src/__test__/seq-factory.test.js | 2 +- src/__test__/sequence-indexed.test.js | 160 ------- src/__test__/sequence-keyed.test.js | 169 ------- src/__test__/sequence-set.test.js | 140 ------ src/__test__/sequence.test.js | 28 ++ src/collection-concrete-mixin.js | 67 +++ src/collection-mixin.js | 117 +++++ src/factories/__test__/flatten.test.js | 7 +- src/factories/__test__/group-by.test.js | 74 +-- src/factories/__test__/to-js.test.js | 9 +- src/factories/__test__/to-native.test.js | 64 --- src/factories/flatten.js | 7 +- src/factories/from.js | 26 ++ src/factories/group-by.js | 42 +- src/factories/index.js | 2 +- src/factories/native-push.js | 13 - src/factories/push.js | 13 + src/factories/reduce.js | 48 ++ src/factories/to-js.js | 5 +- src/factories/to-native.js | 16 - src/index.js | 13 +- src/reflect.js | 6 - src/sequence-indexed.js | 80 ---- src/sequence-keyed.js | 106 ----- src/sequence-set.js | 80 ---- src/sequence.js | 132 ++---- src/static.js | 4 +- src/subtypes/concrete/__test__/list.test.js | 64 +++ src/subtypes/concrete/__test__/map.test.js | 42 ++ src/subtypes/concrete/__test__/set.test.js | 7 + src/subtypes/concrete/list.js | 128 ++++++ src/subtypes/concrete/map.js | 33 ++ src/subtypes/concrete/set.js | 28 ++ src/subtypes/index.js | 3 + src/subtypes/indexed-mixin.js | 41 ++ src/subtypes/keyed-mixin.js | 55 +++ src/subtypes/sequence/indexed.js | 43 ++ src/subtypes/sequence/keyed.js | 39 ++ src/subtypes/sequence/set.js | 39 ++ src/subtypes/set-mixin.js | 36 ++ src/utils/memoize.js | 5 +- src/utils/shape.js | 23 +- yarn.lock | 477 +++++++++++++++++++- 53 files changed, 1991 insertions(+), 1035 deletions(-) create mode 100644 jest_setup.js create mode 100644 src/__test__/collection-indexed.test.js create mode 100644 src/__test__/collection-keyed.test.js create mode 100644 src/__test__/collection-set.test.js create mode 100644 src/__test__/collection.test.js create mode 100644 src/__test__/data.js create mode 100644 src/__test__/helpers/make-test-method.js delete mode 100644 src/__test__/sequence-indexed.test.js delete mode 100644 src/__test__/sequence-keyed.test.js delete mode 100644 src/__test__/sequence-set.test.js create mode 100644 src/__test__/sequence.test.js create mode 100644 src/collection-concrete-mixin.js create mode 100644 src/collection-mixin.js delete mode 100644 src/factories/__test__/to-native.test.js create mode 100644 src/factories/from.js delete mode 100644 src/factories/native-push.js create mode 100644 src/factories/push.js create mode 100644 src/factories/reduce.js delete mode 100644 src/factories/to-native.js delete mode 100644 src/sequence-indexed.js delete mode 100644 src/sequence-keyed.js delete mode 100644 src/sequence-set.js create mode 100644 src/subtypes/concrete/__test__/list.test.js create mode 100644 src/subtypes/concrete/__test__/map.test.js create mode 100644 src/subtypes/concrete/__test__/set.test.js create mode 100644 src/subtypes/concrete/list.js create mode 100644 src/subtypes/concrete/map.js create mode 100644 src/subtypes/concrete/set.js create mode 100644 src/subtypes/index.js create mode 100644 src/subtypes/indexed-mixin.js create mode 100644 src/subtypes/keyed-mixin.js create mode 100644 src/subtypes/sequence/indexed.js create mode 100644 src/subtypes/sequence/keyed.js create mode 100644 src/subtypes/sequence/set.js create mode 100644 src/subtypes/set-mixin.js diff --git a/.babelrc b/.babelrc index 7521eb0..9d2be48 100644 --- a/.babelrc +++ b/.babelrc @@ -1,7 +1,9 @@ { "env": { "test": { - "plugins": ["@babel/plugin-transform-modules-commonjs"] + "plugins": [ + "@babel/plugin-transform-modules-commonjs" + ] } } } diff --git a/.editorconfig b/.editorconfig index 6edc934..a87680e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,7 @@ root = true end_of_line = lf insert_final_newline = true -[{*.{js,mjs,d.ts,json}, .babelrc}] +[*.{js,mjs,d.ts,json,babelrc}] charset = utf-8 indent_style = space indent_size = 2 diff --git a/jest_setup.js b/jest_setup.js new file mode 100644 index 0000000..4f5c86b --- /dev/null +++ b/jest_setup.js @@ -0,0 +1,46 @@ +import { equals } from 'expect/build/jasmine_utils'; + +expect.extend({ + toBeIterable(received, argument) { + let pass = Symbol.iterator in received; + + if (pass && argument && typeof argument.asymmetricMatch === 'function') { + return { pass: argument.asymmetricMatch(received[Symbol.iterator]()), message: () => 'meh' }; + } + + return { + message: () => + this.utils.matcherHint(`${pass ? '.not' : ''}.toBeIterable`) + + '\n\n' + + `expected${ + pass ? ' not' : '' + } to find a [Symbol.iterator] property, but Symbol.iterator was:\n` + + ` ${this.utils.printReceived(received)}`, + pass, + }; + }, + + yields(received, ...args) { + let pass = args.reduce((pass, arg) => { + return pass && arg === received.next().value; + }, true); + const done = received.next(); + pass = pass && done.done; + return { + message: () => `Didn't do the thing.`, + pass, + }; + }, + + yieldsEqual(received, ...args) { + let pass = args.reduce((pass, arg) => { + return pass && equals(arg, received.next().value); + }, true); + const done = received.next(); + pass = pass && done.done; + return { + message: () => `Didn't do the thing.`, + pass, + }; + }, +}); diff --git a/package.json b/package.json index bc4cc21..5a4493b 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "sequins", "version": "0.0.1", - "description": "An object oriented wrapper for iterators, a la Immutable.Seq", "main": "lib/index", "scripts": { - "build": "broccoli build lib --overwrite" + "build": "broccoli build lib --overwrite", + "test": "jest" }, "repository": { "type": "git", @@ -29,19 +29,25 @@ "homepage": "https://github.com/conartist6/sequins#readme", "devDependencies": { "@babel/core": "^7.0.0-beta.54", + "@babel/plugin-transform-classes": "^7.0.0-beta.54", "@babel/plugin-transform-modules-commonjs": "^7.0.0-beta.54", + "@babel/preset-env": "^7.0.0-beta.54", "babel-core": "^7.0.0-beta.41", "babel-jest": "^23.4.0", + "babel-plugin-transform-builtin-extend": "^1.1.2", "broccoli": "^2.0.0-beta.2", "jest": "^23.4.1" }, "dependencies": { - "iter-tools": "^4.1.1", - "memoizee": "^0.4.12" + "invariant": "^2.2.4", + "iter-tools": "^6.0.0", + "memoizee": "^0.4.12", + "stable": "^0.1.8" }, "jest": { "moduleFileExtensions": [ "js" - ] + ], + "setupTestFrameworkScriptFile": "/jest_setup.js" } } diff --git a/src/__test__/collection-indexed.test.js b/src/__test__/collection-indexed.test.js new file mode 100644 index 0000000..28d1c55 --- /dev/null +++ b/src/__test__/collection-indexed.test.js @@ -0,0 +1,64 @@ +import makeTestMethod from './helpers/make-test-method'; +import IndexedSequence from '../subtypes/sequence/indexed'; +import SequinsList from '../subtypes/concrete/list'; +import testData from './data'; + +function makeTests(IndexedConstructor, description) { + describe(description, function() { + let indexed; + + const { keys, values, entries, calls, array } = testData.Indexed; + + const testMethod = makeTestMethod(IndexedConstructor); + + beforeEach(function() { + indexed = new IndexedConstructor(array); + }); + + testMethod('tap') + .callback(() => null, calls) + .run(tapFn => indexed.tap(tapFn)) + .expectCollectionYields(array); + + testMethod('map') + .callback(val => val + 1, calls) + .run(mapFn => indexed.map(mapFn)) + .expectCollectionYields([2, 3, 4]); + + testMethod('flatMap (IndexedSequences)') + .callback(val => new IndexedSequence([val + 1, val + 1.5])) + .expectCalls(calls) + .run(mapFn => indexed.flatMap(mapFn)) + .expectCollectionYields([2, 2.5, 3, 3.5, 4, 4.5]); + + testMethod('flatMap (Arrays)') + .callback(val => [val + 1, val + 1.5]) + .expectCalls(calls) + .run(mapFn => indexed.flatMap(mapFn)) + .expectCollectionYields([2, 2.5, 3, 3.5, 4, 4.5]); + + testMethod('filter') + .callback(val => val > 1, calls) + .run(filterFn => indexed.filter(filterFn)) + .expectCollectionYields([2, 3]); + + testMethod('filterNot') + .callback(val => val > 1, calls) + .run(filterFn => indexed.filterNot(filterFn)) + .expectCollectionYields([1]); + + testMethod('reduce') + .callback((acc, val) => acc + val) + .expectCalls([[1, 2, 1], [3, 3, 2]]) + .run(reducerFn => indexed.reduce(reducerFn)) + .expectReturns(6); + + testMethod('forEach') + .callback(() => true, calls) + .run(eachFn => indexed.forEach(eachFn)) + .expectReturns(3); + }); +} + +makeTests(IndexedSequence, 'IndexedSequence'); +makeTests(SequinsList, 'List'); diff --git a/src/__test__/collection-keyed.test.js b/src/__test__/collection-keyed.test.js new file mode 100644 index 0000000..efc68fc --- /dev/null +++ b/src/__test__/collection-keyed.test.js @@ -0,0 +1,74 @@ +import makeTestMethod from './helpers/make-test-method'; +import { KeyedSeq, Map } from '..'; +import testData from './data'; + +function makeTests(KeyedConstructor, description) { + describe(description, function() { + let keyed; + + const { keys, values, entries, calls, array } = testData.Keyed; + + const testMethod = makeTestMethod(KeyedConstructor); + + beforeEach(function() { + keyed = new KeyedConstructor(entries); + }); + + testMethod('tap') + .callback(() => null, calls) + .run(tapFn => keyed.tap(tapFn)) + .expectCollectionYields(entries); + + testMethod('map') + .callback(val => val + 1, calls) + .run(mapFn => keyed.map(mapFn)) + .expectCollectionYields([[9, 2], [8, 3], [7, 4]]); + + testMethod('mapKeys') + .callback(key => key - 1, entries) + .run(mapFn => keyed.mapKeys(mapFn)) + .expectCollectionYields([[8, 1], [7, 2], [6, 3]]); + + testMethod('mapEntries') + .callback(([key, val]) => [val, key]) + .expectCalls([[[9, 1], 0], [[8, 2], 1], [[7, 3], 2]]) + .run(mapFn => keyed.mapEntries(mapFn)) + .expectCollectionYields([[1, 9], [2, 8], [3, 7]]); + + testMethod('flatMap (KeyedSeqs)') + .callback(val => new KeyedSeq([[val + 1, val + 2]])) + .expectCalls(calls) + .run(mapFn => keyed.flatMap(mapFn)) + .expectCollectionYields([[2, 3], [3, 4], [4, 5]]); + + testMethod('flatMap (Maps)') + .callback(val => new Map([[val + 1, val + 2]])) + .expectCalls(calls) + .run(mapFn => keyed.flatMap(mapFn)) + .expectCollectionYields([[2, 3], [3, 4], [4, 5]]); + + testMethod('filter') + .callback(val => val > 1, calls) + .run(filterFn => keyed.filter(filterFn)) + .expectCollectionYields(entries.slice(1)); + + testMethod('filterNot') + .callback(val => val > 1, calls) + .run(filterFn => keyed.filterNot(filterFn)) + .expectCollectionYields(entries.slice(0, 1)); + + testMethod('reduce') + .callback((acc, val, key) => acc + val) + .expectCalls([[1, 2, 8], [3, 3, 7]]) + .run(reducerFn => keyed.reduce(reducerFn)) + .expectReturns(6); + + testMethod('forEach') + .callback(() => true, calls) + .run(eachFn => keyed.forEach(eachFn)) + .expectReturns(3); + }); +} + +makeTests(KeyedSeq, 'KeyedSeq'); +makeTests(Map, 'Map'); diff --git a/src/__test__/collection-set.test.js b/src/__test__/collection-set.test.js new file mode 100644 index 0000000..0a5ebd3 --- /dev/null +++ b/src/__test__/collection-set.test.js @@ -0,0 +1,63 @@ +import makeTestMethod from './helpers/make-test-method'; +import { SetSeq, Set } from '..'; +import testData from './data'; + +function makeTests(SetConstructor, description) { + describe(description, function() { + let set; + + const { keys, values, entries, calls, array } = testData.Set; + + const testMethod = makeTestMethod(SetConstructor); + + beforeEach(function() { + set = new SetConstructor(array); + }); + + testMethod('tap') + .callback(() => null, calls) + .run(tapFn => set.tap(tapFn)) + .expectCollectionYields(array); + + testMethod('map') + .callback(val => val + 1, calls) + .run(mapFn => set.map(mapFn)) + .expectCollectionYields([2, 3, 4]); + + testMethod('flatMap (SetSeqs)') + .callback(val => new SetSeq([val + 1, val + 1.5])) + .expectCalls(calls) + .run(mapFn => set.flatMap(mapFn)) + .expectCollectionYields([2, 2.5, 3, 3.5, 4, 4.5]); + + testMethod('flatMap (Sets)') + .callback(val => new Set([val + 1, val + 1.5])) + .expectCalls(calls) + .run(mapFn => set.flatMap(mapFn)) + .expectCollectionYields([2, 2.5, 3, 3.5, 4, 4.5]); + + testMethod('filter') + .callback(val => val > 1, calls) + .run(filterFn => set.filter(filterFn)) + .expectCollectionYields([2, 3]); + + testMethod('filterNot') + .callback(val => val > 1, calls) + .run(filterFn => set.filterNot(filterFn)) + .expectCollectionYields([1]); + + testMethod('reduce') + .callback((acc, val, key) => acc + val) + .expectCalls([[1, 2, 2], [3, 3, 3]]) + .run(reducerFn => set.reduce(reducerFn)) + .expectReturns(6); + + testMethod('forEach') + .callback(() => true, calls) + .run(eachFn => set.forEach(eachFn)) + .expectReturns(3); + }); +} + +makeTests(SetSeq, 'SetSeq'); +makeTests(Set, 'Set'); diff --git a/src/__test__/collection.test.js b/src/__test__/collection.test.js new file mode 100644 index 0000000..2797bc6 --- /dev/null +++ b/src/__test__/collection.test.js @@ -0,0 +1,135 @@ +import makeTestMethod from './helpers/make-test-method'; +import { IndexedSeq, KeyedSeq, SetSeq, List, Map, Set } from '..'; +import { Collection } from '../collection-mixin'; +import makeFlatten from '../factories/flatten'; +import testDataByType from './data'; + +function makeTests(type, collectionSubtype, description) { + const CollectionConstructor = Collection[collectionSubtype][type]; + + describe(CollectionConstructor.name, function() { + let collection; + + const { keys, values, entries, calls, array, object, js } = testDataByType[type]; + + const testMethod = makeTestMethod(CollectionConstructor); + + beforeEach(() => { + collection = new CollectionConstructor(array); + }); + + it('has the identity property', function() { + expect(Array.from(array)).toEqual(array); + }); + + it('can flatten', function() { + // The flatten implementation has its own tests + const flatten = makeFlatten(Collection, collectionSubtype, type); + const result = collection.flatten(); + expect(result).toBeInstanceOf(CollectionConstructor); + expect(collection.flatten(true)).toBeIterable(expect.yieldsEqual(flatten(true, collection))); + }); + + // testMethod('mapEntries') + // .callback(([key, val]) => [val, key]) + // .expectCalls([[[9, 1], 0], [[8, 2], 1], [[7, 3], 2]]) + // .run(mapFn => keyed.mapEntries(mapFn)) + // .expectCollectionYields([[1, 9], [2, 8], [3, 7]]); + + it('can toJS', function() { + expect(collection.toJS()).toEqual(js); + }); + + it('can reverse', function() { + expect(Array.from(collection.reverse())).toEqual([...array].reverse()); + }); + + // testMethod('toIndexedSeq'); + // testMethod('toKeyedSeq'); + // testMethod('toSetSeq'); + + // testMethod('keySeq'); + // testMethod('valueSeq'); + // testMethod('entrySeq'); + + // testMethod('toArray'); + // testMethod('toObject'); + // testMethod('toMap'); + // testMethod('toSet'); + + it('can be converted to IndexedSeq', function() { + const indexed = collection.toIndexedSeq(); + expect(Array.from(indexed)).toEqual(values); + expect(indexed).toBeInstanceOf(IndexedSeq); + }); + + it('can be converted to KeyedSeq', function() { + const keyed = collection.toKeyedSeq(); + expect(keyed).toBeInstanceOf(KeyedSeq); + expect(Array.from(keyed)).toEqual(entries); + }); + + it('can be converted to SetSeq', function() { + const set = collection.toSetSeq(); + expect(Array.from(set)).toEqual(values); + expect(set).toBeInstanceOf(SetSeq); + }); + + it('can be converted to Array', function() { + expect(collection.toArray()).toEqual(values); + }); + + it('can be converted to Object', function() { + expect(collection.toObject()).toEqual(object); + }); + + it('can be converted to List', function() { + expect(collection.toList()).toEqual(new List(values)); + }); + + it('can be converted to Map', function() { + expect(collection.toMap()).toEqual(new Map(entries)); + }); + + it('can be converted to Set', function() { + expect(collection.toSet()).toEqual(new Set(values)); + }); + + it('has keys iterator', function() { + expect(Array.from(collection.keys())).toEqual(keys); + }); + + it('has values iterator', function() { + expect(Array.from(collection.values())).toEqual(values); + }); + + it('has entries iterator', function() { + expect(Array.from(collection.entries())).toEqual(entries); + }); + + it('can be converted to a key sequence', function() { + const keySeq = collection.keySeq(); + expect(keySeq).toBeInstanceOf(IndexedSeq); + expect(Array.from(keySeq)).toEqual(keys); + }); + + it('can be converted to a value sequence', function() { + const valueSeq = collection.valueSeq(); + expect(valueSeq).toBeInstanceOf(IndexedSeq); + expect(Array.from(valueSeq)).toEqual(values); + }); + + it('can be converted to an entries sequence', function() { + const entriesSeq = collection.entrySeq(); + expect(entriesSeq).toBeInstanceOf(IndexedSeq); + expect(Array.from(entriesSeq)).toEqual(entries); + }); + }); +} + +makeTests('Indexed', 'Sequence'); +makeTests('Keyed', 'Sequence'); +makeTests('Set', 'Sequence'); +makeTests('Indexed', 'Concrete'); +makeTests('Keyed', 'Concrete'); +makeTests('Set', 'Concrete'); diff --git a/src/__test__/data.js b/src/__test__/data.js new file mode 100644 index 0000000..a8912ad --- /dev/null +++ b/src/__test__/data.js @@ -0,0 +1,39 @@ +const testData = { + Indexed: { + keys: [0, 1, 2], + values: [1, 2, 3], + entries: [[0, 1], [1, 2], [2, 3]], + calls: [[1, 0], [2, 1], [3, 2]], + array: [1, 2, 3], + object: { 0: 1, 1: 2, 2: 3 }, + js: [1, 2, 3], + }, + Keyed: { + keys: [9, 8, 7], + values: [1, 2, 3], + entries: [[9, 1], [8, 2], [7, 3]], + calls: [[1, 9], [2, 8], [3, 7]], + array: [[9, 1], [8, 2], [7, 3]], + object: { 9: 1, 8: 2, 7: 3 }, + js: { 9: 1, 8: 2, 7: 3 }, + }, + Set: { + keys: [1, 2, 3], + values: [1, 2, 3], + entries: [[1, 1], [2, 2], [3, 3]], + calls: [[1, 1], [2, 2], [3, 3]], + array: [1, 2, 3], + object: { 1: 1, 2: 2, 3: 3 }, + js: [1, 2, 3], + }, +}; + +for (const sequenceType of Object.keys(testData)) { + for (const dataset of Object.values(testData[sequenceType])) { + Object.freeze(dataset); + } + Object.freeze(testData[sequenceType]); +} +Object.freeze(testData); + +export default testData; diff --git a/src/__test__/helpers/make-test-method.js b/src/__test__/helpers/make-test-method.js new file mode 100644 index 0000000..5d81793 --- /dev/null +++ b/src/__test__/helpers/make-test-method.js @@ -0,0 +1,90 @@ +class MethodTest { + constructor(ConstructorType, testName) { + this._testName = testName; + this._ConstructorType = ConstructorType; + } + + callback(method, calls) { + this._method = method; + this._methodMock = jest.fn(method); + if (calls) { + this._calls = calls; + } + return this; + } + + expectCalls(calls) { + this._calls = calls; + return this; + } + + run(callback) { + this._runCallback = callback; + + return this; + } + + expectCollectionYields(values) { + this._expectedCollectionValues = values; + return this; + } + + expectReturns(returns) { + this._expectedReturn = returns; + return this; + } + + __exec() { + if (this._runCallback.length > 0 && !('_method' in this)) { + throw new Error('run() called without any callback()'); + } + + const result = this._runCallback(this._methodMock); + this._checkCollectionYields(result); + this._checkReturn(result); + this._checkCalls(); + } + + _checkReturn(result) { + if (this.hasOwnProperty('_expectedReturn')) { + expect(result).toEqual(this._expectedReturn); + } + } + + _checkCollectionYields(result) { + if (this.hasOwnProperty('_expectedCollectionValues')) { + expect(result).toBeInstanceOf(this._ConstructorType); + expect(result).toBeIterable(expect.yieldsEqual(this._expectedCollectionValues)); + } + } + + _checkCalls() { + if (this.hasOwnProperty('_calls')) { + expect(this._methodMock.mock.calls).toEqual(this._calls); + } + } +} + +// Create a DSL on top of Jasmine in order to ensure there is one syntax +// in which test case can be written which will make reasonable assertions +// about a method's behavior both when it is being called on a Concrete type +// and when it is being called on a Sequence. +export default ConstructorType => { + function testMethod(methodName, it = global.it) { + const methodTest = new MethodTest(ConstructorType, methodName); + + it('can ' + methodName, function() { + methodTest.__exec(); + }); + + return methodTest; + } + + testMethod.skip = function(methodName) { + return testMethod(methodName, it.skip); + }; + testMethod.only = function(methodName) { + return testMethod(methodName, it.only); + }; + return testMethod; +}; diff --git a/src/__test__/seq-factory.test.js b/src/__test__/seq-factory.test.js index 77aceae..6be35c9 100644 --- a/src/__test__/seq-factory.test.js +++ b/src/__test__/seq-factory.test.js @@ -1,4 +1,4 @@ -import Seq, { KeyedSeq, IndexedSeq, SetSeq } from '../index'; +import { Seq, KeyedSeq, IndexedSeq, SetSeq } from '../index'; describe('Seq', function() { describe('construction', function() { diff --git a/src/__test__/sequence-indexed.test.js b/src/__test__/sequence-indexed.test.js deleted file mode 100644 index 9ff9c2f..0000000 --- a/src/__test__/sequence-indexed.test.js +++ /dev/null @@ -1,160 +0,0 @@ -import IndexedSeq from '../sequence-indexed'; -import KeyedSeq from '../sequence-keyed'; -import SetSeq from '../sequence-set'; - -describe('Seq.Indexed', function() { - describe('basic functionality', function() { - let array; - let indexed; - let calls; - let keys; - let values; - let entries; - - beforeAll(function() { - keys = [0, 1, 2]; - values = [1, 2, 3]; - entries = [[0, 1], [1, 2], [2, 3]]; - calls = [[1, 0], [2, 1], [3, 2]]; - array = [1, 2, 3]; - }); - - beforeEach(function() { - indexed = new IndexedSeq(array); - }); - - it('has the identity property', function() { - expect(Array.from(indexed)).toEqual(array); - }); - - it('can concat', function() { - expect(Array.from(new IndexedSeq([1, 2]).concat([3, 4]))).toEqual([1, 2, 3, 4]); - }); - - it('can map', function() { - const mapFn = val => val + 1; - const mapMockFn = jest.fn(mapFn); - expect(Array.from(indexed.map(mapMockFn))).toEqual(array.map(mapFn)); - expect(mapMockFn.mock.calls).toEqual(calls); - }); - - it('can flatMap IndexedSequences', function() { - const mapFn = val => new IndexedSeq([val + 1, val + 1.5]); - const mapMockFn = jest.fn(mapFn); - expect(Array.from(indexed.flatMap(mapMockFn))).toEqual([2, 2.5, 3, 3.5, 4, 4.5]); - expect(mapMockFn.mock.calls).toEqual(calls); - }); - - it('can flatMap Arrays', function() { - const mapFn = val => [val + 1, val + 1.5]; - const mapMockFn = jest.fn(mapFn); - expect(Array.from(indexed.flatMap(mapMockFn))).toEqual([2, 2.5, 3, 3.5, 4, 4.5]); - expect(mapMockFn.mock.calls).toEqual(calls); - }); - - it('can tap', function() { - const tapFn = jest.fn(); - Array.from(indexed.tap(tapFn)); - expect(tapFn.mock.calls).toEqual(calls); - }); - - it('can filter', function() { - const filterFn = val => val > 1; - const filterMockFn = jest.fn(filterFn); - expect(Array.from(indexed.filter(filterMockFn))).toEqual(array.filter(filterFn)); - expect(filterMockFn.mock.calls).toEqual(calls); - }); - - it('can filterNot', function() { - const filterFn = val => val > 1; - const filterMockFn = jest.fn(filterFn); - expect(Array.from(indexed.filterNot(filterMockFn))).toEqual( - array.filter(val => !filterFn(val)), - ); - expect(filterMockFn.mock.calls).toEqual(calls); - }); - - it('can reverse', function() { - expect(Array.from(indexed.reverse())).toEqual([...array].reverse()); - }); - - it('can reduce', function() { - const reducer = (acc, val) => acc + val; - const reducerMockFn = jest.fn(reducer); - expect(indexed.reduce(reducerMockFn)).toBe(6); - expect(reducerMockFn.mock.calls).toEqual([[1, 2, 1], [3, 3, 2]]); - }); - - it('can forEach', function() { - const eachFn = jest.fn(); - expect(indexed.forEach(eachFn)).toBe(3); - expect(eachFn.mock.calls).toEqual(calls); - }); - - it('can be converted to IndexedSeq (noop)', function() { - expect(indexed.toIndexedSeq()).toBe(indexed); - }); - - it('can be converted to KeyedSeq', function() { - const keyed = indexed.toKeyedSeq(); - expect(Array.from(keyed)).toEqual(entries); - expect(keyed).toBeInstanceOf(KeyedSeq); - }); - - it('can be converted to SetSeq', function() { - const set = indexed.toSetSeq(); - expect(Array.from(set)).toEqual(values); - expect(set).toBeInstanceOf(SetSeq); - }); - - it('can be converted to Array', function() { - expect(indexed.toArray()).toEqual(array); - }); - - it('can be converted to Object', function() { - expect(indexed.toObject()).toEqual({ 0: 1, 1: 2, 2: 3 }); - }); - - it('can be converted to Map', function() { - expect(indexed.toMap()).toEqual(new Map(entries)); - }); - - it('can be converted to Set', function() { - expect(indexed.toSet()).toEqual(new Set(indexed)); - }); - - it('can be converted to native', function() { - expect(indexed.toNative()).toEqual(indexed.toArray()); - }); - - it('has keys iterator', function() { - expect(Array.from(indexed.keys())).toEqual(keys); - }); - - it('has values iterator', function() { - expect(Array.from(indexed.values())).toEqual(array); - }); - - it('has entries iterator', function() { - expect(Array.from(indexed.entries())).toEqual(entries); - }); - - it('can be converted to a key sequence', function() { - const keySeq = indexed.keySeq(); - expect(keySeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(keySeq)).toEqual(keys); - }); - - it('can be converted to a value sequence', function() { - const valueSeq = indexed.valueSeq(); - expect(valueSeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(valueSeq)).toEqual(values); - }); - - it('can be converted to an entries sequence', function() { - const entriesSeq = indexed.entrySeq(); - expect(entriesSeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(entriesSeq)).toEqual(entries); - }); - }); -}); diff --git a/src/__test__/sequence-keyed.test.js b/src/__test__/sequence-keyed.test.js deleted file mode 100644 index daefb72..0000000 --- a/src/__test__/sequence-keyed.test.js +++ /dev/null @@ -1,169 +0,0 @@ -import IndexedSeq from '../sequence-indexed'; -import KeyedSeq from '../sequence-keyed'; -import SetSeq from '../sequence-set'; - -describe('Seq.Keyed', function() { - describe('basic functionality', function() { - let calls; - let values; - let keys; - let keyed; - let entries; - - beforeEach(function() { - values = [1, 2, 3]; - keys = [9, 8, 7]; - entries = [[9, 1], [8, 2], [7, 3]]; - calls = [[1, 9], [2, 8], [3, 7]]; - keyed = new KeyedSeq(entries); - }); - - it('has the identity property', function() { - expect(Array.from(keyed)).toEqual(entries); - }); - - it('can concat', function() { - const a = [[1, 2], [2, 3]]; - const b = [[3, 4], [4, 5]]; - expect(Array.from(new KeyedSeq(a).concat(b))).toEqual([...a, ...b]); - }); - - it('can map', function() { - const mapFn = val => val + 1; - const mapMockFn = jest.fn(mapFn); - expect(Array.from(keyed.map(mapMockFn))).toEqual([[9, 2], [8, 3], [7, 4]]); - expect(mapMockFn.mock.calls).toEqual(calls); - }); - - it('can mapKeys', function() { - const mapFn = key => key - 1; - const mapMockFn = jest.fn(mapFn); - expect(Array.from(keyed.mapKeys(mapMockFn))).toEqual([[8, 1], [7, 2], [6, 3]]); - expect(mapMockFn.mock.calls).toEqual(entries); - }); - - it('can mapEntries', function() { - const mapFn = ([key, val]) => [val, key]; - const mapMockFn = jest.fn(mapFn); - expect(Array.from(keyed.mapEntries(mapMockFn))).toEqual([[1, 9], [2, 8], [3, 7]]); - expect(mapMockFn.mock.calls).toEqual([[[9, 1], 0], [[8, 2], 1], [[7, 3], 2]]); - }); - - it('can flatMap KeyedSequences', function() { - const mapFn = val => new KeyedSeq([[val + 1, val + 2]]); - const mapMockFn = jest.fn(mapFn); - expect(Array.from(keyed.flatMap(mapMockFn))).toEqual([[2, 3], [3, 4], [4, 5]]); - expect(mapMockFn.mock.calls).toEqual(calls); - }); - - it('can flatMap Maps', function() { - const mapFn = val => new Map([[val + 1, val + 2]]); - const mapMockFn = jest.fn(mapFn); - expect(Array.from(keyed.flatMap(mapMockFn))).toEqual([[2, 3], [3, 4], [4, 5]]); - expect(mapMockFn.mock.calls).toEqual(calls); - }); - - it('can tap', function() { - const tapFn = jest.fn(); - Array.from(keyed.tap(tapFn)); - expect(tapFn.mock.calls).toEqual(calls); - }); - - it('can filter', function() { - const filterFn = val => val > 1; - const filterMockFn = jest.fn(filterFn); - expect(Array.from(keyed.filter(filterMockFn))).toEqual(entries.slice(1)); - expect(filterMockFn.mock.calls).toEqual(calls); - }); - - it('can filterNot', function() { - const filterFn = val => val > 1; - const filterMockFn = jest.fn(filterFn); - expect(Array.from(keyed.filterNot(filterMockFn))).toEqual(entries.slice(0, 1)); - expect(filterMockFn.mock.calls).toEqual(calls); - }); - - it('can reverse', function() { - expect(Array.from(keyed.reverse())).toEqual([...entries].reverse()); - }); - - it('can reduce', function() { - const reducer = (acc, val, key) => acc + val; - const reducerMockFn = jest.fn(reducer); - expect(keyed.reduce(reducerMockFn)).toBe(6); - expect(reducerMockFn.mock.calls).toEqual([[1, 2, 8], [3, 3, 7]]); - }); - - it('can forEach', function() { - const eachFn = jest.fn(); - expect(keyed.forEach(eachFn)).toBe(3); - expect(eachFn.mock.calls).toEqual(calls); - }); - - it('can be converted to IndexedSeq (noop)', function() { - const indexed = keyed.toIndexedSeq(); - expect(Array.from(indexed)).toEqual(values); - expect(indexed).toBeInstanceOf(IndexedSeq); - }); - - it('can be converted to KeyedSeq (noop)', function() { - expect(keyed.toKeyedSeq()).toBe(keyed); - }); - - it('can be converted to SetSeq', function() { - const set = keyed.toSetSeq(); - expect(Array.from(set)).toEqual(values); - expect(set).toBeInstanceOf(SetSeq); - }); - - it('can be converted to Array', function() { - expect(keyed.toArray()).toEqual(values); - }); - - it('can be converted to Object', function() { - expect(keyed.toObject()).toEqual({ 9: 1, 8: 2, 7: 3 }); - }); - - it('can be converted to Map', function() { - expect(keyed.toMap()).toEqual(new Map(keyed)); - }); - - it('can be converted to Set', function() { - expect(keyed.toSet()).toEqual(new Set(values)); - }); - - it('can be converted to native', function() { - expect(keyed.toNative()).toEqual(keyed.toMap()); - }); - - it('has keys iterator', function() { - expect(Array.from(keyed.keys())).toEqual(keys); - }); - - it('has values iterator', function() { - expect(Array.from(keyed.values())).toEqual(values); - }); - - it('has entries iterator', function() { - expect(Array.from(keyed.entries())).toEqual(entries); - }); - - it('can be converted to a key sequence', function() { - const keySeq = keyed.keySeq(); - expect(keySeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(keySeq)).toEqual(keys); - }); - - it('can be converted to a value sequence', function() { - const valueSeq = keyed.valueSeq(); - expect(valueSeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(valueSeq)).toEqual(values); - }); - - it('can be converted to an entries sequence', function() { - const entriesSeq = keyed.entrySeq(); - expect(entriesSeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(entriesSeq)).toEqual(entries); - }); - }); -}); diff --git a/src/__test__/sequence-set.test.js b/src/__test__/sequence-set.test.js deleted file mode 100644 index 0a23557..0000000 --- a/src/__test__/sequence-set.test.js +++ /dev/null @@ -1,140 +0,0 @@ -import IndexedSeq from '../sequence-indexed'; -import KeyedSeq from '../sequence-keyed'; -import SetSeq from '../sequence-set'; - -describe('Seq.Set', function() { - describe('basic functionality', function() { - let array; - let calls; - let set; - let entries; - - beforeAll(function() { - array = [1, 2, 3]; - entries = [[1, 1], [2, 2], [3, 3]]; - calls = entries; - }); - - beforeEach(function() { - set = new SetSeq(array); - }); - - it('has the identity property', function() { - expect(Array.from(set)).toEqual(array); - }); - - it('can concat', function() { - expect(Array.from(new SetSeq([1, 2]).concat([3, 4]))).toEqual([1, 2, 3, 4]); - }); - - it('can map', function() { - const mapFn = val => val + 1; - const mapMockFn = jest.fn(mapFn); - expect(Array.from(set.map(mapMockFn))).toEqual(array.map(mapFn)); - expect(mapMockFn.mock.calls).toEqual(calls); - }); - - it('can flatMap SetSequences', function() { - const mapFn = val => new SetSeq([val + 1, val + 1.5]); - const mapMockFn = jest.fn(mapFn); - expect(Array.from(set.flatMap(mapMockFn))).toEqual([2, 2.5, 3, 3.5, 4, 4.5]); - expect(mapMockFn.mock.calls).toEqual(calls); - }); - - it('can tap', function() { - const tapFn = jest.fn(); - Array.from(set.tap(tapFn)); - expect(tapFn.mock.calls).toEqual(calls); - }); - - it('can filter', function() { - const filterFn = val => val > 1; - const filterMockFn = jest.fn(filterFn); - expect(Array.from(set.filter(filterMockFn))).toEqual(array.filter(filterFn)); - expect(filterMockFn.mock.calls).toEqual(calls); - }); - - it('can reverse', function() { - expect(Array.from(set.reverse())).toEqual([...array].reverse()); - }); - - it('can reduce', function() { - const reducer = (acc, val, key) => acc + val; - const reducerMockFn = jest.fn(reducer); - expect(set.reduce(reducerMockFn)).toBe(6); - expect(reducerMockFn.mock.calls).toEqual([[1, 2, 2], [3, 3, 3]]); - }); - - it('can forEach', function() { - const eachFn = jest.fn(); - expect(set.forEach(eachFn)).toBe(3); - expect(eachFn.mock.calls).toEqual(calls); - }); - - it('can be converted to IndexedSeq', function() { - const indexed = set.toIndexedSeq(); - expect(Array.from(indexed)).toEqual([1, 2, 3]); - expect(indexed).toBeInstanceOf(IndexedSeq); - }); - - it('can be converted to KeyedSeq', function() { - const keyed = set.toKeyedSeq(); - expect(Array.from(keyed)).toEqual(entries); - expect(keyed).toBeInstanceOf(KeyedSeq); - }); - - it('can be converted to SetSeq (noop)', function() { - expect(set.toSetSeq()).toBe(set); - }); - - it('can be converted to Array', function() { - expect(set.toArray()).toEqual(array); - }); - - it('can be converted to Object', function() { - expect(set.toObject()).toEqual({ 1: 1, 2: 2, 3: 3 }); - }); - - it('can be converted to Map', function() { - expect(set.toMap()).toEqual(new Map(entries)); - }); - - it('can be converted to Set', function() { - expect(set.toSet()).toEqual(new Set(array)); - }); - - it('can be converted to native', function() { - expect(set.toNative()).toEqual(set.toSet()); - }); - - it('has keys iterator', function() { - expect(Array.from(set.keys())).toEqual(array); - }); - - it('has values iterator', function() { - expect(Array.from(set.values())).toEqual(array); - }); - - it('has entries iterator', function() { - expect(Array.from(set.entries())).toEqual(entries); - }); - - it('can be converted to a key sequence', function() { - const keySeq = set.keySeq(); - expect(keySeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(keySeq)).toEqual(array); - }); - - it('can be converted to a value sequence', function() { - const valueSeq = set.valueSeq(); - expect(valueSeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(valueSeq)).toEqual(array); - }); - - it('can be converted to an entries sequence', function() { - const entriesSeq = set.entrySeq(); - expect(entriesSeq).toBeInstanceOf(IndexedSeq); - expect(Array.from(entriesSeq)).toEqual(entries); - }); - }); -}); diff --git a/src/__test__/sequence.test.js b/src/__test__/sequence.test.js new file mode 100644 index 0000000..2bbd6af --- /dev/null +++ b/src/__test__/sequence.test.js @@ -0,0 +1,28 @@ +import makeTestMethod from './helpers/make-test-method'; +import IndexedSeq from '../subtypes/sequence/indexed'; +import KeyedSeq from '../subtypes/sequence/keyed'; +import SetSeq from '../subtypes/sequence/set'; +import testDataByType from './data'; + +function makeTests(CollectionConstructor, type, description) { + const testData = testDataByType[type]; + + describe(description, function() { + let collection; + + const testMethod = makeTestMethod(CollectionConstructor); + + beforeEach(() => { + collection = new CollectionConstructor(testData.array); + }); + + // This creates duplicate keys/values, which is expected even for Keyed and Set Seqs + testMethod('concat') + .run(() => collection.concat(testData.array)) + .expectCollectionYields([...testData.array, ...testData.array]); + }); +} + +makeTests(IndexedSeq, 'Indexed', 'IndexedSequence'); +makeTests(KeyedSeq, 'Keyed', 'KeyedSequence'); +makeTests(SetSeq, 'Set', 'SetSequence'); diff --git a/src/collection-concrete-mixin.js b/src/collection-concrete-mixin.js new file mode 100644 index 0000000..f90bd88 --- /dev/null +++ b/src/collection-concrete-mixin.js @@ -0,0 +1,67 @@ +import CollectionMixin, { registerSubtype as registerCollectionSubtype } from './collection-mixin'; +import makeFrom from './factories/from'; + +const Concrete = {}; + +export function registerSubtype(key, type) { + Concrete[key] = type; +} + +const statics = { + from(initial) { + return concreteFrom(initial); + }, + + get Indexed() { + return Concrete.Indexed; + }, + get Keyed() { + return Concrete.Keyed; + }, + get Set() { + return Concrete.Set; + }, +}; + +registerCollectionSubtype('Concrete', Concrete); + +export const ConcreteCollection = statics; + +const concreteFrom = makeFrom(statics); + +export default Base => + class ConcreteCollectionMixin extends CollectionMixin(Base) { + __doCollectionTransform(transform) { + const CollectionConstructor = this.constructor; + const transformed = transform(this); + return transformed instanceof ConcreteCollectionMixin + ? transformed + : new CollectionConstructor(transformed); + } + + __doReductiveTransform(transform) { + return transform(this); + } + + __getStatics() { + return statics; + } + + groupBy(grouper) { + return this.__dynamicMethods.groupBy(this, grouper); + } + + toArray() { + return this.toIndexedSeq().toArray(); + } + toObject() { + return this.toKeyedSeq().toObject(); + } + toMap() { + return this.toKeyedSeq().toMap(); + } + toSet() { + return this.toSetSeq().toSet(); + } + toSeq() {} + }; diff --git a/src/collection-mixin.js b/src/collection-mixin.js new file mode 100644 index 0000000..a912e31 --- /dev/null +++ b/src/collection-mixin.js @@ -0,0 +1,117 @@ +import { keys, concat, slice } from 'iter-tools'; +import * as factories from './factories'; +import reflect from './reflect'; +import invariant from 'invariant'; + +export const Collection = {}; + +export function registerSubtype(key, type) { + Collection[key] = type; +} + +export default Base => { + class CollectionMixin extends Base { + constructor(iterable, reflectionKey) { + super(iterable, reflectionKey); + + invariant( + reflectionKey, + 'new CollectionMixin must be passed a reflectionKey. Received %s', + reflectionKey, + ); + + const collectionSubtype = Base.name === 'Sequence' ? Base.name : 'Concrete'; + + this.__dynamicMethods = {}; + for (const name of keys(factories)) { + this.__dynamicMethods[name] = factories[name](Collection, collectionSubtype, reflectionKey); + } + } + + static get Sequence() { + return Collection.Sequence; + } + + flatten(shallowOrDepth) { + return this.__doCollectionTransform(this.__dynamicMethods.flatten(shallowOrDepth)); + } + + concat(...args) { + return this.__doCollectionTransform(iterable => concat(iterable, ...args)); + } + + slice(start = 0, end = Infinity) { + return this.__doCollectionTransform(iterable => slice({ start, end }, iterable)); + } + + flatMap(mapFn) { + return this.map(mapFn).flatten(true); + } + + // Reductive functions + reduce(...args) { + return this.__doReductiveTransform(iterable => + this.__dynamicMethods.reduce(iterable, ...args), + ); + } + + reduceRight(...args) { + return this.toSeq() + .reverse() + .reduce(...args) + .reverse(); + } + + // Deep conversions + toJS() { + return this.__dynamicMethods.toJS(this); + } + + toIndexedSeq() { + return new Collection.Sequence.Indexed(this); + } + toKeyedSeq() { + return new Collection.Sequence.Keyed(this); + } + toSetSeq() { + return new Collection.Sequence.Set(this); + } + + keySeq() { + return new Collection.Sequence.Indexed(this.keys()); + } + valueSeq() { + return new Collection.Sequence.Indexed(this.values()); + } + entrySeq() { + return new Collection.Sequence.Indexed(this.entries()); + } + + toList() { + return this.toIndexedSeq().toList(); + } + toArray() { + return this.toIndexedSeq().toArray(); + } + toObject() { + return this.toKeyedSeq().toObject(); + } + toMap() { + return this.toKeyedSeq().toMap(); + } + toSet() { + return this.toSetSeq().toSet(); + } + } + + // For native arrays, use native array methods. + for (const method of ['concat', 'slice']) { + if (Base.prototype[method]) { + CollectionMixin.prototype[method] = Base.prototype[method]; + } + } + + Object.defineProperty(CollectionMixin.prototype, '@@__MUTABLE_ITERABLE__@@', { value: true }); + + return CollectionMixin; +}; diff --git a/src/factories/__test__/flatten.test.js b/src/factories/__test__/flatten.test.js index 576086a..f7214cc 100644 --- a/src/factories/__test__/flatten.test.js +++ b/src/factories/__test__/flatten.test.js @@ -1,6 +1,5 @@ -import IndexedSeq from '../../sequence-indexed'; -import KeyedSeq from '../../sequence-keyed'; -import SetSeq from '../../sequence-set'; +import { IndexedSeq, KeyedSeq, SetSeq } from '../..'; +import { Collection } from '../../collection-mixin'; import makeFlatten from '../flatten'; describe('flatten', function() { @@ -16,7 +15,7 @@ describe('flatten', function() { let flatten; beforeAll(function() { - flatten = makeFlatten('Indexed'); + flatten = makeFlatten(Collection, 'Sequence', 'Indexed'); }); beforeEach(function() { diff --git a/src/factories/__test__/group-by.test.js b/src/factories/__test__/group-by.test.js index 44837f4..f3e1279 100644 --- a/src/factories/__test__/group-by.test.js +++ b/src/factories/__test__/group-by.test.js @@ -1,63 +1,29 @@ -import IndexedSeq from '../../sequence-indexed'; -import KeyedSeq from '../../sequence-keyed'; -import SetSeq from '../../sequence-set'; +import { IndexedSeq, KeyedSeq, SetSeq, List, Map, Set } from '../..'; +import { Collection } from '../../collection-mixin'; import makeGroupBy from '../group-by'; -describe('groupBy', function() { - let groupBy; - - describe('Indexed', function() { - beforeAll(function() { - groupBy = makeGroupBy('Indexed'); - }); - - it('works', function() { - const indexed = new IndexedSeq([1, 1, 2, 2]); - const grouped = groupBy(indexed, v => v); - - expect(grouped).toBeInstanceOf(Map); - expect(grouped.get(1)).toBeInstanceOf(IndexedSeq); - expect(grouped.get(2)).toBeInstanceOf(IndexedSeq); - - expect(new KeyedSeq(grouped).toNative()).toEqual(new Map([[1, [1, 1]], [2, [2, 2]]])); - }); - }); - - describe('Keyed', function() { - beforeAll(function() { - groupBy = makeGroupBy('Keyed'); - }); +function makeTests(type, collectionSubtype) { + const CollectionConstructor = Collection[collectionSubtype][type]; + const KeyedCollectionConstructor = Collection[collectionSubtype].Keyed; + describe(CollectionConstructor.name, function() { it('works', function() { - const indexed = new KeyedSeq([[1, 1], [1, 1], [2, 2], [2, 2]]); - const grouped = groupBy(indexed, v => v); - - expect(grouped).toBeInstanceOf(Map); - expect(grouped.get(1)).toBeInstanceOf(KeyedSeq); - expect(grouped.get(2)).toBeInstanceOf(KeyedSeq); - - expect(new KeyedSeq(grouped).toNative()).toEqual( - new Map([[1, new Map([[1, 1], [1, 1]])], [2, new Map([[2, 2], [2, 2]])]]), + const grouped = new CollectionConstructor(new SetSeq([1, 2, 3, 4])).groupBy(v => v % 2); + expect(grouped).toEqual( + new KeyedCollectionConstructor([ + [1, new CollectionConstructor(new SetSeq([1, 3]))], + [0, new CollectionConstructor(new SetSeq([2, 4]))], + ]), ); }); }); +} - describe('Set', function() { - beforeAll(function() { - groupBy = makeGroupBy('Set'); - }); - - it('works', function() { - const indexed = new SetSeq([1, 1, 2, 2]); - const grouped = groupBy(indexed, v => v); - - expect(grouped).toBeInstanceOf(Map); - expect(grouped.get(1)).toBeInstanceOf(SetSeq); - expect(grouped.get(2)).toBeInstanceOf(SetSeq); - - expect(new KeyedSeq(grouped).toNative()).toEqual( - new Map([[1, new Set([1, 1])], [2, new Set([2, 2])]]), - ); - }); - }); +describe('groupBy', function() { + makeTests('Indexed', 'Sequence'); + makeTests('Keyed', 'Sequence'); + makeTests('Set', 'Sequence'); + makeTests('Indexed', 'Concrete'); + makeTests('Keyed', 'Concrete'); + makeTests('Set', 'Concrete'); }); diff --git a/src/factories/__test__/to-js.test.js b/src/factories/__test__/to-js.test.js index f8c0b55..2be6249 100644 --- a/src/factories/__test__/to-js.test.js +++ b/src/factories/__test__/to-js.test.js @@ -1,13 +1,14 @@ import makeToJS from '../to-js'; -import IndexedSeq from '../../sequence-indexed'; -import KeyedSeq from '../../sequence-keyed'; -import SetSeq from '../../sequence-set'; +import IndexedSeq from '../../subtypes/sequence/indexed'; +import KeyedSeq from '../../subtypes/sequence/keyed'; +import SetSeq from '../../subtypes/sequence/set'; +import Sequence from '../../sequence'; describe('toJS', function() { let toJS; beforeAll(function() { - toJS = makeToJS(); + toJS = makeToJS(Sequence); }); it('behaves like toJSON with flat structures', function() { diff --git a/src/factories/__test__/to-native.test.js b/src/factories/__test__/to-native.test.js deleted file mode 100644 index 07288b0..0000000 --- a/src/factories/__test__/to-native.test.js +++ /dev/null @@ -1,64 +0,0 @@ -import makeToNative from '../to-native'; -import IndexedSeq from '../../sequence-indexed'; -import KeyedSeq from '../../sequence-keyed'; -import SetSeq from '../../sequence-set'; - -describe('toNative', function() { - let toNative; - - beforeAll(function() { - toNative = makeToNative(); - }); - - it('works with flat structures', function() { - const indexed = new IndexedSeq([1, 2, 3]); - const keyed = new KeyedSeq([[1, 1], [2, 2], [3, 3]]); - const set = new SetSeq([1, 2, 3]); - expect(toNative(indexed)).toEqual([1, 2, 3]); - expect(toNative(keyed)).toEqual(new Map([[1, 1], [2, 2], [3, 3]])); - expect(toNative(set)).toEqual(new Set([1, 2, 3])); - }); - - it('converts nested sequences', function() { - const seq = new SetSeq([ - 'a', - new KeyedSeq(['fox', 'far']), - new IndexedSeq([]), - new SetSeq([true, new KeyedSeq([[1, 2], [3, 4]])]), - ]); - expect(toNative(seq)).toEqual( - new Set(['a', new Map([['f', 'a']]), [], new Set([true, new Map([[1, 2], [3, 4]])])]), - ); - }); - - it('converts nested native data structures', function() { - const seq = new IndexedSeq([ - new Map([['foo', 'bar']]), - new SetSeq(['socks', 'shoes']), - ['bork', 'bork', 'bork'], - ]); - expect(toNative(seq)).toEqual([ - new Map([['foo', 'bar']]), - new Set(['socks', 'shoes']), - ['bork', 'bork', 'bork'], - ]); - }); - - it('converts structures inside objects', function() { - const seq = new KeyedSeq([ - ['moo', { value: new SetSeq(['moar']) }], - [ - 'i', - { - phrase: new KeyedSeq([['am', 'groot']]), - }, - ], - ]); - expect(toNative(seq)).toEqual( - new Map([ - ['moo', new Map([['value', new Set(['moar'])]])], - ['i', new Map([['phrase', new Map([['am', 'groot']])]])], - ]), - ); - }); -}); diff --git a/src/factories/flatten.js b/src/factories/flatten.js index f5b05e6..d7c2fe8 100644 --- a/src/factories/flatten.js +++ b/src/factories/flatten.js @@ -1,15 +1,14 @@ -import Sequence from '../sequence'; import { memoizeFactory } from '../utils/memoize'; import reflect from '../reflect'; -function makeFlatten(sequenceType) { - const { itemValue, toNative } = reflect[sequenceType]; +function makeFlatten(Collection, collectionSubtype, collectionType) { + const { itemValue, toNative } = reflect[collectionType]; function* flatten(shallowOrDepth, iterable) { const depth = shallowOrDepth === true ? 0 : shallowOrDepth; for (const item of iterable) { - const itemSeq = item == null ? item : Sequence.from(itemValue(item)); + const itemSeq = item == null ? item : Collection.Sequence.from(itemValue(item)); if (itemSeq && (depth === false || depth > 0)) { yield* flatten(depth === false ? depth : depth - 1, toNative(itemSeq)); diff --git a/src/factories/from.js b/src/factories/from.js new file mode 100644 index 0000000..c50049c --- /dev/null +++ b/src/factories/from.js @@ -0,0 +1,26 @@ +import { entries } from 'iter-tools'; +import { isIndexed, isKeyed, isSet } from '../utils/shape'; +import { memoizeFactory } from '../utils/memoize'; + +const emptyArray = []; + +function makeFrom(Collection, collectionSubtype) { + return function from(initial) { + if (initial == null) { + return new Collection[collectionSubtype].Indexed(emptyArray); + } else if (isIndexed(initial)) { + return new Collection[collectionSubtype].Indexed(initial); + } else if (isKeyed(initial)) { + return new Collection[collectionSubtype].Keyed(initial); + } else if (isSet(initial)) { + return new Collection[collectionSubtype].Set(initial); + } else if (typeof initial[Symbol.iterator] === 'function') { + return new Collection[collectionSubtype].Indexed(initial); + } else if (typeof initial === 'object') { + return new Collection[collectionSubtype].Keyed(entries(initial)); + } + return null; + }; +} + +export default memoizeFactory(makeFrom); diff --git a/src/factories/group-by.js b/src/factories/group-by.js index 6bf03b9..7bb4d28 100644 --- a/src/factories/group-by.js +++ b/src/factories/group-by.js @@ -1,28 +1,32 @@ -import Sequence from '../sequence'; import { memoizeFactory } from '../utils/memoize'; import reflect from '../reflect'; -import makeNativePush from './native-push'; +import makePush from './push'; +import makeReduce from './reduce'; -function makeGroupBy(sequenceType) { - const SequenceType = Sequence[sequenceType]; - const { nativeSet, nativeSize, NativeConstructor } = reflect[sequenceType]; +function makeGroupBy(Collection, collectionSubtype, collectionType) { + const TypedCollection = Collection[collectionSubtype][collectionType]; + const ConcreteCollection = Collection.Concrete[collectionType]; + const Map = Collection.Concrete.Keyed; - const nativePush = makeNativePush(sequenceType); + const push = makePush(...arguments); + const reduce = makeReduce(...arguments); - return function groupBy(sequence, grouper) { - const map = sequence.reduce(function(result, value, key) { - key = grouper(value, key); - if (result.has(key)) { - nativePush(result.get(key), key, value); - } else { - const native = new NativeConstructor(); - nativePush(native, key, value); - result.set(key, native); - } - return result; - }, new Map()); + return function groupBy(collection, grouper) { + const map = reduce( + collection, + function(result, value, key) { + const groupKey = grouper(value, key); + if (!result.get(groupKey)) { + const concrete = new ConcreteCollection(); + result.set(groupKey, concrete); + } + push(result.get(groupKey), key, value); + return result; + }, + new Map(), + ); for (const key of map.keys()) { - map.set(key, new SequenceType(map.get(key))); + map.set(key, new TypedCollection(map.get(key))); } return map; }; diff --git a/src/factories/index.js b/src/factories/index.js index 67749a9..1f46d94 100644 --- a/src/factories/index.js +++ b/src/factories/index.js @@ -1,4 +1,4 @@ export { default as flatten } from './flatten'; export { default as groupBy } from './group-by'; +export { default as reduce } from './reduce'; export { default as toJS } from './to-js'; -export { default as toNative } from './to-native'; diff --git a/src/factories/native-push.js b/src/factories/native-push.js deleted file mode 100644 index 5d706e9..0000000 --- a/src/factories/native-push.js +++ /dev/null @@ -1,13 +0,0 @@ -import { memoizeFactory } from '../utils/memoize'; - -function makeNativePush(sequenceType) { - if (sequenceType === 'Indexed') { - return (native, key, value) => native.push(value); - } else if (sequenceType === 'Keyed') { - return (native, key, value) => native.set(key, value); - } else { - return (native, value) => native.add(value); - } -} - -export default memoizeFactory(makeNativePush); diff --git a/src/factories/push.js b/src/factories/push.js new file mode 100644 index 0000000..85c0b16 --- /dev/null +++ b/src/factories/push.js @@ -0,0 +1,13 @@ +import { memoizeFactory } from '../utils/memoize'; + +function makePush(_, collectionSubtype, collectionType) { + if (collectionType === 'Indexed') { + return (collection, _, value) => collection.push(value); + } else if (collectionType === 'Keyed') { + return (collection, key, value) => collection.set(key, value); + } else { + return (collection, _, value) => collection.add(value); + } +} + +export default memoizeFactory(makePush); diff --git a/src/factories/reduce.js b/src/factories/reduce.js new file mode 100644 index 0000000..c32e1c8 --- /dev/null +++ b/src/factories/reduce.js @@ -0,0 +1,48 @@ +import { reduce as reduceIterable } from 'iter-tools'; +import reflect from '../reflect'; + +const reduceByType = { + Indexed: function reduce(iterable, reducer, initial) { + let reduced; + if (arguments.length > 2) { + reduced = reduceIterable(initial, reducer, iterable); + } else { + reduced = reduceIterable(reducer, iterable); + } + return reduced; + }, + Keyed: function reduce(iterable, reducer, initial) { + let invocations = 0; + let hasInitial = arguments.length > 2; + let reduced; + const keyedReducer = (acc, [key, value]) => { + if (invocations++ === 0 && !hasInitial) { + acc = acc[1]; + } + + return reducer(acc, value, key); + }; + if (hasInitial) { + reduced = reduceIterable(initial, keyedReducer, iterable); + } else { + reduced = reduceIterable(keyedReducer, iterable); + } + return reduced; + }, + Set: function reduce(iterable, reducer, initial) { + const setReducer = (acc, item) => reducer(acc, item, item); + let reduced; + if (arguments.length > 2) { + reduced = reduceIterable(initial, setReducer, iterable); + } else { + reduced = reduceIterable(setReducer, iterable); + } + return reduced; + }, +}; + +function makeReduce(Collection, collectionSubtype, collectionType) { + return reduceByType[collectionType]; +} + +export default makeReduce; diff --git a/src/factories/to-js.js b/src/factories/to-js.js index d7ec6ae..570025a 100644 --- a/src/factories/to-js.js +++ b/src/factories/to-js.js @@ -1,12 +1,11 @@ -import Sequence from '../sequence'; import { memoizeFactory } from '../utils/memoize'; import { isDataStructure } from '../utils/shape'; // Implementation borrowed from immutable -function makeToJS() { +function makeToJS(Collection) { return function toJS(value) { return isDataStructure(value) - ? Sequence.from(value) + ? Collection.Sequence.from(value) .map(toJS) .toJSON() : value; diff --git a/src/factories/to-native.js b/src/factories/to-native.js deleted file mode 100644 index fda47f6..0000000 --- a/src/factories/to-native.js +++ /dev/null @@ -1,16 +0,0 @@ -import Sequence from '../sequence'; -import { memoizeFactory } from '../utils/memoize'; -import { isDataStructure, reflectionKey } from '../utils/shape'; -import reflect from '../reflect'; - -function flatNative(value) { - return reflect[reflectionKey(value)].toNative(value); -} - -function makeToNative() { - return function toNative(value) { - return isDataStructure(value) ? flatNative(Sequence.from(value).map(toNative)) : value; - }; -} - -export default memoizeFactory(makeToNative); diff --git a/src/index.js b/src/index.js index f5d9762..caefdb0 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,10 @@ import Seq from './seq-factory'; -export default Seq; - -export { default as IndexedSeq } from './sequence-indexed'; -export { default as KeyedSeq } from './sequence-keyed'; -export { default as SetSeq } from './sequence-set'; +import List from './subtypes/concrete/list'; +import Map from './subtypes/concrete/map'; +import Set from './subtypes/concrete/set'; +import IndexedSeq from './subtypes/sequence/indexed'; +import KeyedSeq from './subtypes/sequence/keyed'; +import SetSeq from './subtypes/sequence/set'; +export { Seq, IndexedSeq, KeyedSeq, SetSeq, List, Map, Set }; +export default { Seq, List, Map, Set }; diff --git a/src/reflect.js b/src/reflect.js index 581b5d2..4a3fe71 100644 --- a/src/reflect.js +++ b/src/reflect.js @@ -2,29 +2,23 @@ export function keyFromInst() {} export default Object.freeze({ Indexed: { - NativeConstructor: Array, nativeSet: (array, key, value) => (array[key] = value), nativeSize: array => array.length, toNative: seq => seq.toArray(), - clone: seq => seq.toIndexedSeq(), itemKey: (item, i) => i, itemValue: item => item, }, Keyed: { - NativeConstructor: Map, nativeSet: (map, key, value) => map.set(key, value), nativeSize: map => map.size, toNative: seq => seq.toMap(), - clone: seq => seq.toKeyedSeq(), itemKey: item => item[0], itemValue: item => item[1], }, Set: { - NativeConstructor: Set, nativeSet: (set, key, value) => set.set(key, value), nativeSize: set => set.size, toNative: seq => seq.toSet(), - clone: seq => seq.toSetSeq(), itemKey: item => item, itemValue: item => item, }, diff --git a/src/sequence-indexed.js b/src/sequence-indexed.js deleted file mode 100644 index 855060e..0000000 --- a/src/sequence-indexed.js +++ /dev/null @@ -1,80 +0,0 @@ -import { map, tap, filter, reduce } from 'iter-tools'; -import { isKeyed } from './utils/shape'; -import forEach from './functions/for-each'; -import Sequence, { registerSubtype } from './sequence'; - -export default class IndexedSeq extends Sequence { - constructor(iterable) { - super(iterable, 'Indexed'); - if (isKeyed(iterable)) { - this.__transforms.push(iterable => iterable.values()); - } - } - - // Sequence methods - map(mapFn) { - this.__transforms.push(map(mapFn)); - return this; - } - - flatMap(mapFn) { - return this.map(mapFn).flatten(true); - } - - tap(tapFn) { - this.__transforms.push(tap(tapFn)); - return this; - } - - filter(filterFn) { - this.__transforms.push(filter(filterFn)); - return this; - } - - filterNot(filterFn) { - this.__transforms.push(filter((item, i) => !filterFn(item, i))); - return this; - } - - // Eager functions - reduce(reducer, initial) { - if (arguments.length === 1) { - return reduce(reducer, this); - } else { - return reduce(initial, reducer, this); - } - } - - forEach(eachFn) { - return forEach(eachFn, this); - } - - // Conversions - toIndexedSeq() { - return this; - } - - toArray() { - return Array.from(this); - } - - toJSON() { - return this.toArray(); - } - - // Iterators - *keys() { - yield* map((_, i) => i, this); - } - - *values() { - yield* this; - } - - *entries() { - yield* map((value, i) => [i, value], this); - } -} - -Object.defineProperty(IndexedSeq.prototype, '@@__MUTABLE_INDEXED__@@', { value: true }); -registerSubtype('Indexed', IndexedSeq); diff --git a/src/sequence-keyed.js b/src/sequence-keyed.js deleted file mode 100644 index 3cc8590..0000000 --- a/src/sequence-keyed.js +++ /dev/null @@ -1,106 +0,0 @@ -import { map, flatMap, tap, filter, reduce } from 'iter-tools'; -import { isSet, isIndexed } from './utils/shape'; -import forEach from './functions/for-each'; -import Sequence, { registerSubtype } from './sequence'; - -export default class KeyedSeq extends Sequence { - constructor(iterable) { - super(iterable, 'Keyed'); - - if (isSet(iterable) || (!Array.isArray(iterable) && isIndexed(iterable))) { - this.__transforms.push(iterable => iterable.entries()); - } - } - - // Sequence methods - map(mapFn) { - this.__transforms.push(map(([key, value]) => [key, mapFn(value, key)])); - return this; - } - - mapKeys(mapFn) { - this.__transforms.push(map(([key, value]) => [mapFn(key, value), value])); - return this; - } - - mapEntries(mapFn) { - this.__transforms.push(map((entry, i) => mapFn(entry, i))); - return this; - } - - flatMap(mapFn) { - return this.map(mapFn).flatten(true); - } - - tap(tapFn) { - this.__transforms.push(tap(([key, value]) => tapFn(value, key))); - return this; - } - - filter(filterFn) { - this.__transforms.push(filter(([key, value]) => filterFn(value, key))); - return this; - } - - filterNot(filterFn) { - this.__transforms.push(filter(([key, value]) => !filterFn(value, key))); - return this; - } - - // Eager functions - reduce(reducer, initial) { - let invocations = 0; - let hasInitial = arguments.length > 1; - const keyedReducer = (acc, [key, value]) => { - if (invocations++ === 0 && !hasInitial) { - acc = acc[1]; - } - - return reducer(acc, value, key); - }; - if (hasInitial) { - return reduce(initial, keyedReducer, this); - } else { - return reduce(keyedReducer, this); - } - } - - forEach(eachFn) { - return forEach(([key, value]) => eachFn(value, key), this); - } - - // Conversions - toKeyedSeq() { - return this; - } - - toMap() { - return new Map(this); - } - - toObject(proto = Object.prototype) { - const obj = Object.create(proto); - for (const [key, value] of this) obj[key] = value; - return obj; - } - - toJSON() { - return this.toObject(); - } - - // Iterators - *keys() { - yield* map(([key, _]) => key, this); - } - - *values() { - yield* map(([_, value]) => value, this); - } - - *entries() { - yield* this; - } -} - -Object.defineProperty(KeyedSeq.prototype, '@@__MUTABLE_KEYED__@@', { value: true }); -registerSubtype('Keyed', KeyedSeq); diff --git a/src/sequence-set.js b/src/sequence-set.js deleted file mode 100644 index dc5437d..0000000 --- a/src/sequence-set.js +++ /dev/null @@ -1,80 +0,0 @@ -import { map, flatMap, tap, filter, reduce } from 'iter-tools'; -import { isKeyed } from './utils/shape'; -import forEach from './functions/for-each'; -import Sequence, { registerSubtype } from './sequence'; - -export default class SetSeq extends Sequence { - constructor(iterable) { - super(iterable, 'Set'); - if (isKeyed(iterable)) { - this.__transforms.push(iterable => iterable.values()); - } - } - - // Sequence methods - map(mapFn) { - this.__transforms.push(map(item => mapFn(item, item))); - return this; - } - - flatMap(mapFn) { - return this.map(mapFn).flatten(true); - } - - tap(tapFn) { - this.__transforms.push(tap(item => tapFn(item, item))); - return this; - } - - filter(filterFn) { - this.__transforms.push(filter(item => filterFn(item, item))); - return this; - } - - filterNot(filterFn) { - this.__transforms.push(filter(item => !filterFn(item, item))); - return this; - } - - // Eager functions - reduce(reducer, initial) { - const setReducer = (acc, item) => reducer(acc, item, item); - if (arguments.length === 1) { - return reduce(setReducer, this); - } else { - return reduce(initial, setReducer, this); - } - } - - forEach(eachFn) { - return forEach(item => eachFn(item, item), this); - } - - // Conversions - toSetSeq() { - return this; - } - - toSet() { - return new Set(this); - } - - toJSON() { - return this.toArray(); - } - - // Iterators - *keys() { - yield* this; - } - - *values() { - yield* this; - } - - *entries() { - yield* map(value => [value, value], this); - } -} - -registerSubtype('Set', SetSeq); diff --git a/src/sequence.js b/src/sequence.js index 54dbe5f..66d390a 100644 --- a/src/sequence.js +++ b/src/sequence.js @@ -1,31 +1,46 @@ -import { compose, concat, map, keys, entries } from 'iter-tools'; -import { isIndexed, isKeyed, isSet } from './utils/shape'; +import { compose, map, concat } from 'iter-tools'; +import { isIndexed, isKeyed, isSet, isConcrete } from './utils/shape'; import { reverseArrayIterator } from './utils/array'; -import * as factories from './factories'; +import CollectionMixin, { + Collection, + registerSubtype as registerCollectionSubtype, +} from './collection-mixin'; +import makeFrom from './factories/from'; -const emptyArray = []; const Seq = {}; export function registerSubtype(key, type) { Seq[key] = type; } -export default class Sequence { +class Sequence { + constructor() { + this.__transforms = []; + } + + __getStatics() { + return Sequence; + } + + __doCollectionTransform(transform) { + this.__transforms.push(transform); + return this; + } + + __doReductiveTransform(transform) { + return transform(this._transform()); + } + + _transform() { + return this.__transforms.length + ? compose(...reverseArrayIterator(this.__transforms))(this.__iterable) + : this.__iterable; + } +} + +export default class AbstractSequence extends CollectionMixin(Sequence) { static from(initial) { - if (initial == null) { - return new Seq.Indexed(emptyArray); - } else if (isIndexed(initial)) { - return new Seq.Indexed(initial); - } else if (isKeyed(initial)) { - return new Seq.Keyed(initial); - } else if (isSet(initial)) { - return new Seq.Set(initial); - } else if (typeof initial[Symbol.iterator] === 'function') { - return new Seq.Indexed(initial); - } else if (typeof initial === 'object') { - return new Seq.Keyed(entries(initial)); - } - return null; + return sequenceFrom(initial); } static get Indexed() { @@ -41,88 +56,35 @@ export default class Sequence { } constructor(iterable, reflectionKey) { + iterable = iterable || []; + super(iterable, reflectionKey); this.__iterable = iterable; - this.__transforms = []; - this._dynamicMethods = {}; - for (const name of keys(factories)) { - this._dynamicMethods[name] = factories[name](reflectionKey); - } } *[Symbol.iterator]() { - yield* this.__transforms.length - ? compose(...reverseArrayIterator(this.__transforms))(this.__iterable) - : this.__iterable; + yield* this._transform(); } cacheResult() { - this.__iterable = Array.from(this); + const transformedIterable = this._transform(); + this.__iterable = isConcrete(transformedIterable) + ? transformedIterable + : Array.from(transformedIterable); this.__transforms.length = 0; return this; } - concat(...args) { - const SequenceConstructor = this.constructor; - this.__transforms.push(iterable => concat(iterable, ...args)); - return this; - } - - flatten(shallowOrDepth) { - this.__transforms.push(this._dynamicMethods.flatten(shallowOrDepth)); - return this; - } - groupBy(grouper) { - this.__iterable = this._dynamicMethods.groupBy(this, grouper); - this.__transforms.length = 0; - return this; + return new Seq.Keyed(this.__dynamicMethods.groupBy(this, grouper)); } reverse() { - this.cacheResult(); - this.__iterable.reverse(); - return this; - } - - toArray() { - return this.toIndexedSeq().toArray(); - } - toObject() { - return this.toKeyedSeq().toObject(); - } - toMap() { - return this.toKeyedSeq().toMap(); - } - toSet() { - return this.toSetSeq().toSet(); - } - - toJS() { - return this._dynamicMethods.toJS(this); - } - toNative() { - return this._dynamicMethods.toNative(this); + return this.__doCollectionTransform(iterable => Array.from(iterable).reverse()); } +} - toIndexedSeq() { - return new Seq.Indexed(this); - } - toKeyedSeq() { - return new Seq.Keyed(this); - } - toSetSeq() { - return new Seq.Set(this); - } +registerCollectionSubtype('Sequence', AbstractSequence); - keySeq() { - return new Seq.Indexed(this.keys()); - } - valueSeq() { - return new Seq.Indexed(this.values()); - } - entrySeq() { - return new Seq.Indexed(this.entries()); - } -} +const sequenceFrom = makeFrom(Collection, 'Sequence'); -Object.defineProperty(Sequence.prototype, '@@__MUTABLE_SEQUENCE__@@', { value: true }); +Object.defineProperty(AbstractSequence.prototype, '@@__MUTABLE_SEQUENCE__@@', { value: true }); diff --git a/src/static.js b/src/static.js index 76614b7..dcb543d 100644 --- a/src/static.js +++ b/src/static.js @@ -1,5 +1,7 @@ import { range, repeat } from 'iter-tools'; -import IndexedSeq from './sequence-indexed'; +import IndexedSeq from './subtypes/sequence/indexed'; + +export { isMutableSeq, isKeyedSeq, isIndexedSeq } from './utils/shape'; export function Repeat(value, times = Infinity) { return new IndexedSeq(repeat(value, times)); diff --git a/src/subtypes/concrete/__test__/list.test.js b/src/subtypes/concrete/__test__/list.test.js new file mode 100644 index 0000000..05025eb --- /dev/null +++ b/src/subtypes/concrete/__test__/list.test.js @@ -0,0 +1,64 @@ +import SequinsList from '../list'; + +describe('SequinsList', function() { + const array = Object.freeze([1, 2, 3]); + let list; + + beforeEach(function() { + list = new SequinsList(array); + }); + + it('has size', function() { + expect(list.size).toBe(3); + }); + + it('can get', function() { + expect(list.get(0)).toBe(1); + expect(list.get(2)).toBe(3); + expect(list.get(3)).toBeUndefined(); + }); + + it('can set', function() { + expect(list.set(1, 22)).toBe(list); + expect(Array.from(list)).toEqual([1, 22, 3]); + }); + + it('can push', function() { + expect(list.push(4)).toBe(list); + expect(Array.from(list)).toEqual([1, 2, 3, 4]); + }); + + it('can pop', function() { + expect(list.pop()).toBe(3); + expect(Array.from(list)).toEqual([1, 2]); + }); + + it('can shift', function() { + expect(list.shift()).toBe(1); + expect(Array.from(list)).toEqual([2, 3]); + }); + + it('can unshift', function() { + expect(list.unshift(0)).toBe(list); + expect(Array.from(list)).toEqual([0, 1, 2, 3]); + }); + + it('can clear', function() { + expect(list.clear()).toBe(list); + expect(Array.from(list)).toEqual([]); + }); + + it('can concat', function() { + list = list.concat([1, 2, 3]); + expect(list).toBeInstanceOf(SequinsList); + expect(Array.from(list)).toEqual([...array, ...array]); + }); + + it('has keys iterator', function() { + expect(Array.from(list.keys())).toEqual([0, 1, 2]); + }); + + it('has values iterator', function() { + expect(Array.from(list.values())).toEqual(array); + }); +}); diff --git a/src/subtypes/concrete/__test__/map.test.js b/src/subtypes/concrete/__test__/map.test.js new file mode 100644 index 0000000..57fe130 --- /dev/null +++ b/src/subtypes/concrete/__test__/map.test.js @@ -0,0 +1,42 @@ +import SequinsMap from '../map'; + +describe('SequinsMap', function() { + let calls; + let values; + let keys; + let keyed; + let entries; + + beforeEach(function() { + values = [1, 2, 3]; + keys = [9, 8, 7]; + entries = [[9, 1], [8, 2], [7, 3]]; + calls = [[1, 9], [2, 8], [3, 7]]; + keyed = new SequinsMap(entries); + }); + + it('is a subtype of native Map', function() { + expect(SequinsMap.prototype).toBeInstanceOf(Map); + }); + + it('can map', function() { + const mapFn = val => val + 1; + const mapMockFn = jest.fn(mapFn); + expect(keyed.map(mapMockFn)).toEqual(new SequinsMap([[9, 2], [8, 3], [7, 4]])); + expect(mapMockFn.mock.calls).toEqual(calls); + }); + + it('can mapKeys', function() { + const mapFn = key => key - 1; + const mapMockFn = jest.fn(mapFn); + expect(keyed.mapKeys(mapMockFn)).toEqual(new SequinsMap([[8, 1], [7, 2], [6, 3]])); + expect(mapMockFn.mock.calls).toEqual(entries); + }); + + it('can mapEntries', function() { + const mapFn = ([key, val]) => [val, key]; + const mapMockFn = jest.fn(mapFn); + expect(keyed.mapEntries(mapMockFn)).toEqual(new SequinsMap([[1, 9], [2, 8], [3, 7]])); + expect(mapMockFn.mock.calls).toEqual([[[9, 1], 0], [[8, 2], 1], [[7, 3], 2]]); + }); +}); diff --git a/src/subtypes/concrete/__test__/set.test.js b/src/subtypes/concrete/__test__/set.test.js new file mode 100644 index 0000000..156bbca --- /dev/null +++ b/src/subtypes/concrete/__test__/set.test.js @@ -0,0 +1,7 @@ +import SequinsSet from '../set'; + +describe('SequinsSet', function() { + it('is a subtype of native Set', function() { + expect(SequinsSet.prototype).toBeInstanceOf(Set); + }); +}); diff --git a/src/subtypes/concrete/list.js b/src/subtypes/concrete/list.js new file mode 100644 index 0000000..faa24d0 --- /dev/null +++ b/src/subtypes/concrete/list.js @@ -0,0 +1,128 @@ +import { range } from 'iter-tools'; +import ConcreteCollectionMixin, { registerSubtype } from '../../collection-concrete-mixin'; +import { IndexedMixin } from '..'; +import { isKeyed } from '../../utils/shape'; + +const aProto = Array.prototype; + +/** + * List is a little extra special in that it does not derive from Array, + * particularly because there is no mechanism to fall back on if es6 extend + * is not supported. See article: + * http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/ + * + * At some point in the future (when IE is well and truly dead) there should + * additionally be an Array type which extends Array. + **/ +export class List { + constructor(iterable) { + this._array = + iterable != null ? Array.from(isKeyed(iterable) ? iterable.values() : iterable) : []; + } + + get size() { + return this._array.length; + } + + __doCollectionTransform(transform) { + const CollectionConstructor = this.constructor; + const arr = new CollectionConstructor(); + arr._array = transform(this._array); + return arr; + } + + __possiblyUseNativeImplementaton(methodName, ...args) { + return typeof aProto[methodName] === 'function' + ? this.__doCollectionTransform(arr => aProto[methodName].apply(arr, args)) + : super[methodName](...args); + } + + get(idx) { + return this._array[idx]; + } + set(idx, value) { + this._array[idx] = value; + return this; + } + push(...values) { + this._array.push(...values); + return this; + } + pop() { + return this._array.pop(); + } + shift() { + return this._array.shift(); + } + unshift(value) { + this._array.unshift(value); + return this; + } + clear() { + this._array = []; + return this; + } + + fill(...args) { + this._array.fill(...args); + return this; + } + map(mapFn) { + return this.__doCollectionTransform(() => this._array.map(mapFn)); + } + filter(filterFn) { + return this.__doCollectionTransform(() => this._array.filter(filterFn)); + } + slice(...args) { + return this.__doCollectionTransform(() => this._array.slice(...args)); + } + concat(...args) { + return this.__doCollectionTransform(() => this._array.concat(...args)); + } + reverse() { + this._array.reverse(); + return this; + } + reduce(...args) { + return this._array.reduce(...args); + } + join(separator) { + return this._array.join(separator); + } + + // Possibly native functions + reduceRight(...args) { + return this.__possiblyUseNativeImplementaton('reduceRight', ...args); + } + + // Conversions + toList() { + return this; + } + toArray() { + return Array.from(this._array); + } + + // Iterators + *[Symbol.iterator]() { + yield* this._array; + } + + keys() { + return range(this._array.length); + } + + values() { + return this[Symbol.iterator](); + } + + *entries() { + for (let i = 0; i < this._array.length; i++) { + yield [i, this._array[i]]; + } + } +} + +export default class SequinsList extends IndexedMixin(ConcreteCollectionMixin(List)) {} + +registerSubtype('Indexed', SequinsList); diff --git a/src/subtypes/concrete/map.js b/src/subtypes/concrete/map.js new file mode 100644 index 0000000..5c88b64 --- /dev/null +++ b/src/subtypes/concrete/map.js @@ -0,0 +1,33 @@ +import ConcreteCollectionMixin, { registerSubtype } from '../../collection-concrete-mixin'; +import { KeyedMixin } from '..'; +import { isSet, isIndexed } from '../../utils/shape'; + +export class TranspiledMap extends Map {} + +export default class SequinsMap extends KeyedMixin(ConcreteCollectionMixin(TranspiledMap)) { + constructor(iterable) { + super( + iterable && !Array.isArray(iterable) && (isSet(iterable) || isIndexed(iterable)) + ? iterable.entries() + : iterable, + ); + } + + reverse() { + const reversedSeq = this.toKeyedSeq() + .reverse() + .cacheResult(); + + this.clear(); + for (const [key, value] of reversedSeq) { + this.set(key, value); + } + return this; + } + + toMap() { + return this; + } +} + +registerSubtype('Keyed', SequinsMap); diff --git a/src/subtypes/concrete/set.js b/src/subtypes/concrete/set.js new file mode 100644 index 0000000..d630e6e --- /dev/null +++ b/src/subtypes/concrete/set.js @@ -0,0 +1,28 @@ +import ConcreteCollectionMixin, { registerSubtype } from '../../collection-concrete-mixin'; +import { SetMixin } from '..'; +import { isKeyed } from '../../utils/shape'; + +export class TranspiledSet extends Set {} + +export default class SequinsSet extends SetMixin(ConcreteCollectionMixin(TranspiledSet)) { + constructor(iterable) { + super(iterable && isKeyed(iterable) ? iterable.values() : iterable); + } + + reverse() { + const reversedSeq = this.toSetSeq() + .reverse() + .cacheResult(); + this.clear(); + for (const value of reversedSeq) { + this.add(value); + } + return this; + } + + toSet() { + return this; + } +} + +registerSubtype('Set', SequinsSet); diff --git a/src/subtypes/index.js b/src/subtypes/index.js new file mode 100644 index 0000000..d63cbf1 --- /dev/null +++ b/src/subtypes/index.js @@ -0,0 +1,3 @@ +export { default as IndexedMixin } from './indexed-mixin'; +export { default as KeyedMixin } from './keyed-mixin'; +export { default as SetMixin } from './set-mixin'; diff --git a/src/subtypes/indexed-mixin.js b/src/subtypes/indexed-mixin.js new file mode 100644 index 0000000..f59031a --- /dev/null +++ b/src/subtypes/indexed-mixin.js @@ -0,0 +1,41 @@ +import { tap, map, filter, reduce } from 'iter-tools'; +import forEach from '../functions/for-each'; + +export default Base => { + class IndexedCollection extends Base { + constructor(iterable) { + super(iterable, 'Indexed'); + } + + // Collection functions + tap(tapFn) { + return this.__doCollectionTransform(tap(tapFn)); + } + + map(mapFn) { + return this.__doCollectionTransform(map(mapFn)); + } + + filter(filterFn) { + return this.__doCollectionTransform(filter(filterFn)); + } + + filterNot(filterFn) { + return this.__doCollectionTransform(filter((item, i) => !filterFn(item, i))); + } + + // Reductive functions + forEach(eachFn) { + return forEach(eachFn, this); + } + + // Conversions + toJSON() { + return this.toArray(); + } + } + + Object.defineProperty(IndexedCollection.prototype, '@@__MUTABLE_INDEXED__@@', { value: true }); + + return IndexedCollection; +}; diff --git a/src/subtypes/keyed-mixin.js b/src/subtypes/keyed-mixin.js new file mode 100644 index 0000000..16fe000 --- /dev/null +++ b/src/subtypes/keyed-mixin.js @@ -0,0 +1,55 @@ +import { tap, map, filter, reduce } from 'iter-tools'; +import forEach from '../functions/for-each'; + +export default Base => { + class KeyedCollection extends Base { + constructor(iterable) { + super(iterable, 'Keyed'); + } + + // Collection functions + tap(tapFn) { + return this.__doCollectionTransform(tap(([key, value]) => tapFn(value, key))); + } + + map(mapFn) { + return this.__doCollectionTransform(map(([key, value]) => [key, mapFn(value, key)])); + } + + mapKeys(mapFn) { + return this.__doCollectionTransform(map(([key, value]) => [mapFn(key, value), value])); + } + + mapEntries(mapFn) { + return this.__doCollectionTransform(map((entry, i) => mapFn(entry, i))); + } + + filter(filterFn) { + return this.__doCollectionTransform(filter(([key, value]) => filterFn(value, key))); + } + + filterNot(filterFn) { + return this.__doCollectionTransform(filter(([key, value]) => !filterFn(value, key))); + } + + // Reductive functions + forEach(eachFn) { + return forEach(([key, value]) => eachFn(value, key), this); + } + + // Conversions + toObject(proto = Object.prototype) { + const obj = Object.create(proto); + for (const [key, value] of this) obj[key] = value; + return obj; + } + + toJSON() { + return this.toObject(); + } + } + + Object.defineProperty(KeyedCollection.prototype, '@@__MUTABLE_KEYED__@@', { value: true }); + + return KeyedCollection; +}; diff --git a/src/subtypes/sequence/indexed.js b/src/subtypes/sequence/indexed.js new file mode 100644 index 0000000..8bf8b77 --- /dev/null +++ b/src/subtypes/sequence/indexed.js @@ -0,0 +1,43 @@ +import { map } from 'iter-tools'; +import { isKeyed } from '../../utils/shape'; +import Sequence, { registerSubtype } from '../../sequence'; +import { ConcreteCollection } from '../../collection-concrete-mixin'; +import { IndexedMixin } from '..'; + +export default class IndexedSeq extends IndexedMixin(Sequence) { + constructor(iterable) { + super(iterable); + if (isKeyed(iterable)) { + this.__transforms.push(iterable => iterable.values()); + } + } + + // Conversions + toIndexedSeq() { + return this; + } + + toList() { + const List = ConcreteCollection.Indexed; + return new List(this); + } + + toArray() { + return Array.from(this); + } + + // Iterators + *keys() { + yield* map((_, i) => i, this); + } + + *values() { + yield* this; + } + + *entries() { + yield* map((value, i) => [i, value], this); + } +} + +registerSubtype('Indexed', IndexedSeq); diff --git a/src/subtypes/sequence/keyed.js b/src/subtypes/sequence/keyed.js new file mode 100644 index 0000000..cdf8020 --- /dev/null +++ b/src/subtypes/sequence/keyed.js @@ -0,0 +1,39 @@ +import { map } from 'iter-tools'; +import { isSet, isIndexed } from '../../utils/shape'; +import Sequence, { registerSubtype } from '../../sequence'; +import { ConcreteCollection } from '../../collection-concrete-mixin'; +import { KeyedMixin } from '..'; + +export default class KeyedSeq extends KeyedMixin(Sequence) { + constructor(iterable) { + super(iterable); + if (isSet(iterable) || (!Array.isArray(iterable) && isIndexed(iterable))) { + this.__transforms.push(iterable => iterable.entries()); + } + } + + // Conversions + toKeyedSeq() { + return this; + } + + toMap() { + const Map = ConcreteCollection.Keyed; + return new Map(this); + } + + // Iterators + *keys() { + yield* map(([key, _]) => key, this); + } + + *values() { + yield* map(([_, value]) => value, this); + } + + *entries() { + yield* this; + } +} + +registerSubtype('Keyed', KeyedSeq); diff --git a/src/subtypes/sequence/set.js b/src/subtypes/sequence/set.js new file mode 100644 index 0000000..b535540 --- /dev/null +++ b/src/subtypes/sequence/set.js @@ -0,0 +1,39 @@ +import { map } from 'iter-tools'; +import { isKeyed } from '../../utils/shape'; +import Sequence, { registerSubtype } from '../../sequence'; +import { ConcreteCollection } from '../../collection-concrete-mixin'; +import { SetMixin } from '..'; + +export default class SetSeq extends SetMixin(Sequence) { + constructor(iterable) { + super(iterable); + if (isKeyed(iterable)) { + this.__transforms.push(iterable => iterable.values()); + } + } + + // Conversions + toSetSeq() { + return this; + } + + toSet() { + const Set = ConcreteCollection.Set; + return new Set(this); + } + + // Iterators + *keys() { + yield* this; + } + + *values() { + yield* this; + } + + *entries() { + yield* map(value => [value, value], this); + } +} + +registerSubtype('Set', SetSeq); diff --git a/src/subtypes/set-mixin.js b/src/subtypes/set-mixin.js new file mode 100644 index 0000000..3ccc4af --- /dev/null +++ b/src/subtypes/set-mixin.js @@ -0,0 +1,36 @@ +import { tap, map, filter, reduce } from 'iter-tools'; +import forEach from '../functions/for-each'; + +export default Base => + class SetCollection extends Base { + constructor(iterable) { + super(iterable, 'Set'); + } + + // Collection functions + tap(tapFn) { + return this.__doCollectionTransform(tap(item => tapFn(item, item))); + } + + map(mapFn) { + return this.__doCollectionTransform(map(item => mapFn(item, item))); + } + + filter(filterFn) { + return this.__doCollectionTransform(filter(item => filterFn(item, item))); + } + + filterNot(filterFn) { + return this.__doCollectionTransform(filter(item => !filterFn(item, item))); + } + + // Reductive functions + forEach(eachFn) { + return forEach(item => eachFn(item, item), this); + } + + // Conversions + toJSON() { + return this.toArray(); + } + }; diff --git a/src/utils/memoize.js b/src/utils/memoize.js index c88382f..d6d9d4d 100644 --- a/src/utils/memoize.js +++ b/src/utils/memoize.js @@ -1,6 +1,7 @@ import memoize from 'memoizee'; export function memoizeFactory(factory) { - const normalizeFn = factory.length > 0 ? reflectionKey => reflectionKey : () => null; - return memoize(factory, normalizeFn); + //const normalizeFn = factory.length > 0 ? reflectionKey => reflectionKey : () => null; + //return memoize(factory, normalizeFn); + return memoize(factory); } diff --git a/src/utils/shape.js b/src/utils/shape.js index bb63cba..e1f0750 100644 --- a/src/utils/shape.js +++ b/src/utils/shape.js @@ -1,4 +1,4 @@ -function isImmutable(shape) { +function isImmutableCollection(shape) { return !!shape['@@__IMMUTABLE_ITERABLE__@@']; } @@ -11,7 +11,7 @@ function isImmutableKeyed(shape) { } function isImmutableSet(shape) { - return isImmutable(shape) && !isImmutableIndexed(shape) && !isImmutableKeyed(shape); + return isImmutableCollection(shape) && !isImmutableIndexed(shape) && !isImmutableKeyed(shape); } function isNativeSet(shape) { @@ -36,7 +36,16 @@ export function isPlainObj(shape) { } export function isDataStructure(shape) { - return isMutableSeq(shape) || isImmutable(shape) || isNative(shape) || isPlainObj(shape); + return ( + isMutableCollection(shape) || + isImmutableCollection(shape) || + isNative(shape) || + isPlainObj(shape) + ); +} + +export function isMutableCollection(shape) { + return !!shape['@@__MUTABLE_ITERABLE__@@']; } export function isMutableSeq(shape) { @@ -71,6 +80,14 @@ export function isSeq(shape) { return !!shape['@@__MUTABLE_SEQUENCE__@@'] || !!shape['@@__IMMUTABLE_SEQ__@@']; } +export function isConcreteCollection(shape) { + return !!shape['@@__MUTABLE_ITERABLE__@@'] && !shape['@@__MUTABLE_SEQUENCE__@@']; +} + +export function isConcrete(shape) { + return isConcreteCollection(shape) || isNative(shape); +} + export function reflectionKey(shape) { if (isIndexed(shape)) { return 'Indexed'; diff --git a/yarn.lock b/yarn.lock index 5a72f1c..f5c2189 100644 --- a/yarn.lock +++ b/yarn.lock @@ -37,6 +37,42 @@ source-map "^0.5.0" trim-right "^1.0.1" +"@babel/helper-annotate-as-pure@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.54.tgz#1626126a3f9fc4ed280ac942372c7d39653d7121" + dependencies: + "@babel/types" "7.0.0-beta.54" + +"@babel/helper-builder-binary-assignment-operator-visitor@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-beta.54.tgz#d0a1967635b9eebcafdba80491917ee4981c12fa" + dependencies: + "@babel/helper-explode-assignable-expression" "7.0.0-beta.54" + "@babel/types" "7.0.0-beta.54" + +"@babel/helper-call-delegate@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.54.tgz#f6b72cfd832fb26eb2a806e18de05f88d3a8f302" + dependencies: + "@babel/helper-hoist-variables" "7.0.0-beta.54" + "@babel/traverse" "7.0.0-beta.54" + "@babel/types" "7.0.0-beta.54" + +"@babel/helper-define-map@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.0.0-beta.54.tgz#2036d7c49365987f091db9702ce2f3b55f677730" + dependencies: + "@babel/helper-function-name" "7.0.0-beta.54" + "@babel/types" "7.0.0-beta.54" + lodash "^4.17.5" + +"@babel/helper-explode-assignable-expression@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-beta.54.tgz#cf067f3330965c2048bf087ea06f62c76d94a792" + dependencies: + "@babel/traverse" "7.0.0-beta.54" + "@babel/types" "7.0.0-beta.54" + "@babel/helper-function-name@7.0.0-beta.54": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.54.tgz#307875507a1eda2482a09a9a4df6a25632ffb34b" @@ -51,6 +87,18 @@ dependencies: "@babel/types" "7.0.0-beta.54" +"@babel/helper-hoist-variables@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.54.tgz#8635be8095135ff73f753ed189e449f68b4f43cb" + dependencies: + "@babel/types" "7.0.0-beta.54" + +"@babel/helper-member-expression-to-functions@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0-beta.54.tgz#bce9ddc484317b13d2615bafe2b524d0d56d99df" + dependencies: + "@babel/types" "7.0.0-beta.54" + "@babel/helper-module-imports@7.0.0-beta.54": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.54.tgz#c2d8e14ff034225bf431356db77ef467b8d35aac" @@ -69,10 +117,41 @@ "@babel/types" "7.0.0-beta.54" lodash "^4.17.5" +"@babel/helper-optimise-call-expression@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.54.tgz#4af8dd4ff90dbd29b3bcf85fff43952e2ae1016e" + dependencies: + "@babel/types" "7.0.0-beta.54" + "@babel/helper-plugin-utils@7.0.0-beta.54": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-beta.54.tgz#61d2a9a0f9a3e31838a458debb9eebd7bdd249b4" +"@babel/helper-regex@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0-beta.54.tgz#8ac562f855f132fc68dfd10b132552555ac870d9" + dependencies: + lodash "^4.17.5" + +"@babel/helper-remap-async-to-generator@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.54.tgz#39a50052aadd74d40c73b7c58eb963b90fac56d3" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-beta.54" + "@babel/helper-wrap-function" "7.0.0-beta.54" + "@babel/template" "7.0.0-beta.54" + "@babel/traverse" "7.0.0-beta.54" + "@babel/types" "7.0.0-beta.54" + +"@babel/helper-replace-supers@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.54.tgz#901f5a1493a410799fd3ab3e0c0d29d18071c89f" + dependencies: + "@babel/helper-member-expression-to-functions" "7.0.0-beta.54" + "@babel/helper-optimise-call-expression" "7.0.0-beta.54" + "@babel/traverse" "7.0.0-beta.54" + "@babel/types" "7.0.0-beta.54" + "@babel/helper-simple-access@7.0.0-beta.54": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.54.tgz#5f760a19589a9b6f07e80a65ef4bcbd4fba8c253" @@ -87,6 +166,15 @@ dependencies: "@babel/types" "7.0.0-beta.54" +"@babel/helper-wrap-function@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.54.tgz#dc1b7a483a3074a3531b36523e03156d910a3a2a" + dependencies: + "@babel/helper-function-name" "7.0.0-beta.54" + "@babel/template" "7.0.0-beta.54" + "@babel/traverse" "7.0.0-beta.54" + "@babel/types" "7.0.0-beta.54" + "@babel/helpers@7.0.0-beta.54": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.0.0-beta.54.tgz#b86a99a80efd81668caef307610b961197446a74" @@ -107,7 +195,154 @@ version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.0.0-beta.54.tgz#c01aa63b57c9c8dce8744796c81d9df121f20db4" -"@babel/plugin-transform-modules-commonjs@^7.0.0-beta.54": +"@babel/plugin-proposal-async-generator-functions@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-beta.54.tgz#19871bd655b5d748b0ae3e9ecebe247be8b7f83b" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/helper-remap-async-to-generator" "7.0.0-beta.54" + "@babel/plugin-syntax-async-generators" "7.0.0-beta.54" + +"@babel/plugin-proposal-object-rest-spread@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-beta.54.tgz#5481269a020dd0d38715a8094fed015d30ef4c2a" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/plugin-syntax-object-rest-spread" "7.0.0-beta.54" + +"@babel/plugin-proposal-optional-catch-binding@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0-beta.54.tgz#931f69298fa0c411b2596616cf5a1d82925b87a9" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/plugin-syntax-optional-catch-binding" "7.0.0-beta.54" + +"@babel/plugin-proposal-unicode-property-regex@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0-beta.54.tgz#1624631faf688bcbde4918712bd0af7186f7d245" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/helper-regex" "7.0.0-beta.54" + regexpu-core "^4.2.0" + +"@babel/plugin-syntax-async-generators@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-beta.54.tgz#ffac8f64927614762897cc9643495fd38097dd41" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-syntax-object-rest-spread@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-beta.54.tgz#e0f445612081ab573e2535adbabc7b710d17940c" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-syntax-optional-catch-binding@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0-beta.54.tgz#2eb8ddde19ddf73a343d087a087159ed44e54809" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-arrow-functions@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-beta.54.tgz#44a977b8e61e4efcc7658bbbe260f204ca1bcf72" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-async-to-generator@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-beta.54.tgz#d035e65c50884937d64dbe68d16498c032f8bbec" + dependencies: + "@babel/helper-module-imports" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/helper-remap-async-to-generator" "7.0.0-beta.54" + +"@babel/plugin-transform-block-scoped-functions@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-beta.54.tgz#938a77fb12f0e11661bdf5386e4aeca47f0c053b" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-block-scoping@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.54.tgz#bcae1c2ffae4cc3b7b3e5455f0a98daecc09a3c6" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + lodash "^4.17.5" + +"@babel/plugin-transform-classes@7.0.0-beta.54", "@babel/plugin-transform-classes@^7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.0.0-beta.54.tgz#b15781d2e499ce25438e73fea2fa5a09858568ff" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-beta.54" + "@babel/helper-define-map" "7.0.0-beta.54" + "@babel/helper-function-name" "7.0.0-beta.54" + "@babel/helper-optimise-call-expression" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/helper-replace-supers" "7.0.0-beta.54" + "@babel/helper-split-export-declaration" "7.0.0-beta.54" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.54.tgz#b28494942b94fb86d01994763d2b5c43bdd986af" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-destructuring@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.54.tgz#81f649a3e4fcb62c2b2ad497f783a800b994472f" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-dotall-regex@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0-beta.54.tgz#2835b7f4141b19fa0648eb96ffe3c4fccd1eca20" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/helper-regex" "7.0.0-beta.54" + regexpu-core "^4.1.3" + +"@babel/plugin-transform-duplicate-keys@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-beta.54.tgz#4b8f4fb349902a800679191f59d0fa53fca49400" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-exponentiation-operator@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-beta.54.tgz#1017096366fb43ebca8ed8d8d0cdd1ebd64febb2" + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-for-of@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.54.tgz#261d2992058a9e09234b9ff67820054ffc55f79c" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-function-name@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.54.tgz#cc722f9973931337def3d1e6c55138581edd371e" + dependencies: + "@babel/helper-function-name" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-literals@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.54.tgz#70f07ecc2f3b7bc9f542a578e82eec18a5504098" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-modules-amd@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-beta.54.tgz#fb50740741420bb485ee1315d2e1133db4e433d2" + dependencies: + "@babel/helper-module-transforms" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-modules-commonjs@7.0.0-beta.54", "@babel/plugin-transform-modules-commonjs@^7.0.0-beta.54": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.54.tgz#07d912a7a24dad2d9bf5d44ce322ddc457a8db37" dependencies: @@ -115,6 +350,132 @@ "@babel/helper-plugin-utils" "7.0.0-beta.54" "@babel/helper-simple-access" "7.0.0-beta.54" +"@babel/plugin-transform-modules-systemjs@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0-beta.54.tgz#0923f012ac252e037467245ff27f8954f4ce6803" + dependencies: + "@babel/helper-hoist-variables" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-modules-umd@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.0.0-beta.54.tgz#3af0e2cf8f533b2984a8ca6da316246850c3aeda" + dependencies: + "@babel/helper-module-transforms" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-new-target@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0-beta.54.tgz#634ee57fa805720195cd31086c973f1fc5c9949b" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-object-super@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-beta.54.tgz#d25fad66eff90de03ee62f8384f0af57bcd065d9" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/helper-replace-supers" "7.0.0-beta.54" + +"@babel/plugin-transform-parameters@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.54.tgz#76306f19b9acac6cf13721af15ecb9f382864ff7" + dependencies: + "@babel/helper-call-delegate" "7.0.0-beta.54" + "@babel/helper-get-function-arity" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-regenerator@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.54.tgz#8b46e192f3bfe096bbbf86e27764e7662e5f9a0f" + dependencies: + regenerator-transform "^0.13.3" + +"@babel/plugin-transform-shorthand-properties@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-beta.54.tgz#50e73c2afc5898b1055510ddf60ee13a6301517f" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-spread@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.54.tgz#4f0852df0f4b1db2426c40facd8fe5f028a3dbc9" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-sticky-regex@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.54.tgz#568f35eb5118ae96fad82eac36374d7923b47883" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/helper-regex" "7.0.0-beta.54" + +"@babel/plugin-transform-template-literals@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.54.tgz#cb1f6303cafb8442a6c6c69a0dfbb60699f327bc" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-typeof-symbol@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-beta.54.tgz#6d068686239c9ebaf534d1c0d8032953f7b521bc" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + +"@babel/plugin-transform-unicode-regex@7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-beta.54.tgz#1dc7e9150b39aaeb19fca1c863e082f6096afc60" + dependencies: + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/helper-regex" "7.0.0-beta.54" + regexpu-core "^4.1.3" + +"@babel/preset-env@^7.0.0-beta.54": + version "7.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.0.0-beta.54.tgz#4b05c4e3aaed64a509098e4e854dfc0e02edf053" + dependencies: + "@babel/helper-module-imports" "7.0.0-beta.54" + "@babel/helper-plugin-utils" "7.0.0-beta.54" + "@babel/plugin-proposal-async-generator-functions" "7.0.0-beta.54" + "@babel/plugin-proposal-object-rest-spread" "7.0.0-beta.54" + "@babel/plugin-proposal-optional-catch-binding" "7.0.0-beta.54" + "@babel/plugin-proposal-unicode-property-regex" "7.0.0-beta.54" + "@babel/plugin-syntax-async-generators" "7.0.0-beta.54" + "@babel/plugin-syntax-object-rest-spread" "7.0.0-beta.54" + "@babel/plugin-syntax-optional-catch-binding" "7.0.0-beta.54" + "@babel/plugin-transform-arrow-functions" "7.0.0-beta.54" + "@babel/plugin-transform-async-to-generator" "7.0.0-beta.54" + "@babel/plugin-transform-block-scoped-functions" "7.0.0-beta.54" + "@babel/plugin-transform-block-scoping" "7.0.0-beta.54" + "@babel/plugin-transform-classes" "7.0.0-beta.54" + "@babel/plugin-transform-computed-properties" "7.0.0-beta.54" + "@babel/plugin-transform-destructuring" "7.0.0-beta.54" + "@babel/plugin-transform-dotall-regex" "7.0.0-beta.54" + "@babel/plugin-transform-duplicate-keys" "7.0.0-beta.54" + "@babel/plugin-transform-exponentiation-operator" "7.0.0-beta.54" + "@babel/plugin-transform-for-of" "7.0.0-beta.54" + "@babel/plugin-transform-function-name" "7.0.0-beta.54" + "@babel/plugin-transform-literals" "7.0.0-beta.54" + "@babel/plugin-transform-modules-amd" "7.0.0-beta.54" + "@babel/plugin-transform-modules-commonjs" "7.0.0-beta.54" + "@babel/plugin-transform-modules-systemjs" "7.0.0-beta.54" + "@babel/plugin-transform-modules-umd" "7.0.0-beta.54" + "@babel/plugin-transform-new-target" "7.0.0-beta.54" + "@babel/plugin-transform-object-super" "7.0.0-beta.54" + "@babel/plugin-transform-parameters" "7.0.0-beta.54" + "@babel/plugin-transform-regenerator" "7.0.0-beta.54" + "@babel/plugin-transform-shorthand-properties" "7.0.0-beta.54" + "@babel/plugin-transform-spread" "7.0.0-beta.54" + "@babel/plugin-transform-sticky-regex" "7.0.0-beta.54" + "@babel/plugin-transform-template-literals" "7.0.0-beta.54" + "@babel/plugin-transform-typeof-symbol" "7.0.0-beta.54" + "@babel/plugin-transform-unicode-regex" "7.0.0-beta.54" + browserslist "^3.0.0" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.3.0" + "@babel/runtime@^7.0.0-beta.49": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.54.tgz#39ebb42723fe7ca4b3e1b00e967e80138d47cadf" @@ -410,6 +771,13 @@ babel-plugin-syntax-object-rest-spread@^6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" +babel-plugin-transform-builtin-extend@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-builtin-extend/-/babel-plugin-transform-builtin-extend-1.1.2.tgz#5e96fecf58b8fa1ed74efcad88475b2af3c9116e" + dependencies: + babel-runtime "^6.2.0" + babel-template "^6.3.0" + babel-preset-jest@^23.2.0: version "23.2.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46" @@ -429,14 +797,14 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: @@ -571,6 +939,13 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" +browserslist@^3.0.0: + version "3.2.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" + dependencies: + caniuse-lite "^1.0.30000844" + electron-to-chromium "^1.3.47" + bser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -611,6 +986,10 @@ camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" +caniuse-lite@^1.0.30000844: + version "1.0.30000865" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz#70026616e8afe6e1442f8bb4e1092987d81a2f25" + capture-exit@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" @@ -868,6 +1247,10 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" +deprecate@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/deprecate/-/deprecate-1.0.0.tgz#661490ed2428916a6c8883d8834e5646f4e4a4a8" + dequeue@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/dequeue/-/dequeue-1.0.5.tgz#10f1cef07e3234b21dcb38f4bfa2d66034ab67c7" @@ -910,6 +1293,10 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +electron-to-chromium@^1.3.47: + version "1.3.52" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz#d2d9f1270ba4a3b967b831c40ef71fb4d9ab5ce0" + encodeurl@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -1514,7 +1901,7 @@ ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -invariant@^2.2.2: +invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -1806,13 +2193,15 @@ istanbul-reports@^1.3.0: dependencies: handlebars "^4.0.3" -iter-tools@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/iter-tools/-/iter-tools-4.1.1.tgz#94c70341f70c568d2e314e62f8bee1ca8505f414" +iter-tools@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/iter-tools/-/iter-tools-6.0.0.tgz#3f1093342108b4668d259a329b50f348dccc6f2b" dependencies: "@babel/runtime" "^7.0.0-beta.49" clone-regexp "^1.0.1" + deprecate "^1.0.0" dequeue "^1.0.5" + little-ds-toolkit "^1.1.0" jest-changed-files@^23.4.0: version "23.4.0" @@ -2102,6 +2491,10 @@ jest@^23.4.1: import-local "^1.0.0" jest-cli "^23.4.1" +js-levenshtein@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.3.tgz#3ef627df48ec8cf24bacf05c0f184ff30ef413c5" + js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -2160,6 +2553,10 @@ jsesc@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -2234,6 +2631,10 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +little-ds-toolkit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/little-ds-toolkit/-/little-ds-toolkit-1.1.0.tgz#ed7d181763c8d223cb049661cf09896b296b591e" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -2794,7 +3195,7 @@ pretty-format@^23.2.0: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -private@^0.1.8: +private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -2895,6 +3296,16 @@ realpath-native@^1.0.0: dependencies: util.promisify "^1.0.0" +regenerate-unicode-properties@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" @@ -2903,6 +3314,12 @@ regenerator-runtime@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.0.tgz#8052ac952d85b10f3425192cd0c53f45cf65c6cb" +regenerator-transform@^0.13.3: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" + dependencies: + private "^0.1.6" + regex-cache@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" @@ -2916,6 +3333,27 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexpu-core@^4.1.3, regexpu-core@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.2.0.tgz#a3744fa03806cffe146dea4421a3e73bdcc47b1d" + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^7.0.0" + regjsgen "^0.4.0" + regjsparser "^0.3.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.0.2" + +regjsgen@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.4.0.tgz#c1eb4c89a209263f8717c782591523913ede2561" + +regjsparser@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.3.0.tgz#3c326da7fcfd69fa0d332575a41c8c0cdf588c96" + dependencies: + jsesc "~0.5.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -3250,6 +3688,10 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + stack-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" @@ -3493,6 +3935,25 @@ underscore.string@^3.2.2, underscore.string@~3.3.4: sprintf-js "^1.0.3" util-deprecate "^1.0.2" +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4" + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" + union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"