From 72c3d745518c38e487b85a157c96dcfc13e0f587 Mon Sep 17 00:00:00 2001 From: Voltra Date: Sun, 24 Mar 2024 20:53:48 +0000 Subject: [PATCH 1/4] Initial implementation of asKeysSequence and asValuesSequence --- src/Sequence.ts | 172 ++++++++++++++++++++++++++++-------------------- 1 file changed, 102 insertions(+), 70 deletions(-) diff --git a/src/Sequence.ts b/src/Sequence.ts index 76eb3c3..b197d7a 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -1,73 +1,73 @@ -import {All} from "./all"; -import {Any} from "./any"; -import {AsIterable} from "./asIterable"; -import {Associate} from "./associate"; -import {AssociateBy} from "./associateBy"; -import {Average} from "./average"; -import {Chunk} from "./chunk"; -import {Contains} from "./contains"; -import {Count} from "./count"; -import {Distinct} from "./distinct"; -import {DistinctBy} from "./distinctBy"; -import {Drop} from "./drop"; -import {DropWhile} from "./dropWhile"; -import {ElementAt} from "./elementAt"; -import {ElementAtOrElse} from "./elementAtOrElse"; -import {ElementAtOrNull} from "./elementAtOrNull"; -import {Filter} from "./filter"; -import {FilterIndexed} from "./filterIndexed"; -import {FilterNot} from "./filterNot"; -import {FilterNotNull} from "./filterNotNull"; -import {First} from "./first"; -import {FirstOrNull} from "./firstOrNull"; -import {FlatMap} from "./flatMap"; -import {Flatten} from "./flatten"; -import {Fold} from "./fold"; -import {FoldIndexed} from "./foldIndexed"; -import {ForEach} from "./forEach"; -import {ForEachIndexed} from "./forEachIndexed"; -import {GroupBy} from "./groupBy"; -import {IndexOf} from "./indexOf"; -import {IndexOfFirst} from "./indexOfFirst"; -import {IndexOfLast} from "./indexOfLast"; -import {JoinToString} from "./joinToString"; -import {Last} from "./last"; -import {LastOrNull} from "./lastOrNull"; -import {Map} from "./map"; -import {MapIndexed} from "./mapIndexed"; -import {MapNotNull} from "./mapNotNull"; -import {Max} from "./max"; -import {MaxBy} from "./maxBy"; -import {MaxWith} from "./maxWith"; -import {Merge} from "./merge"; -import {Min} from "./min"; -import {MinBy} from "./minBy"; -import {Minus} from "./minus"; -import {MinWith} from "./minWith"; -import {None} from "./none"; -import {OnEach} from "./onEach"; -import {Partition} from "./partition"; -import {Plus} from "./plus"; -import {Reduce} from "./reduce"; -import {ReduceIndexed} from "./reduceIndexed"; -import {Reverse} from "./reverse"; -import {Single} from "./single"; -import {SingleOrNull} from "./singleOrNull"; -import {Sorted} from "./sorted"; -import {SortedBy} from "./sortedBy"; -import {SortedByDescending} from "./sortedByDescending"; -import {SortedDescending} from "./sortedDescending"; -import {SortedWith} from "./sortedWith"; -import {Sum} from "./sum"; -import {SumBy} from "./sumBy"; -import {Take} from "./take"; -import {TakeWhile} from "./takeWhile"; -import {ToArray} from "./toArray"; -import {ToMap} from "./toMap"; -import {ToSet} from "./toSet"; -import {Unzip} from "./unzip"; -import {WithIndex} from "./withIndex"; -import {Zip} from "./zip"; +import { All } from "./all"; +import { Any } from "./any"; +import { AsIterable } from "./asIterable"; +import { Associate } from "./associate"; +import { AssociateBy } from "./associateBy"; +import { Average } from "./average"; +import { Chunk } from "./chunk"; +import { Contains } from "./contains"; +import { Count } from "./count"; +import { Distinct } from "./distinct"; +import { DistinctBy } from "./distinctBy"; +import { Drop } from "./drop"; +import { DropWhile } from "./dropWhile"; +import { ElementAt } from "./elementAt"; +import { ElementAtOrElse } from "./elementAtOrElse"; +import { ElementAtOrNull } from "./elementAtOrNull"; +import { Filter } from "./filter"; +import { FilterIndexed } from "./filterIndexed"; +import { FilterNot } from "./filterNot"; +import { FilterNotNull } from "./filterNotNull"; +import { First } from "./first"; +import { FirstOrNull } from "./firstOrNull"; +import { FlatMap } from "./flatMap"; +import { Flatten } from "./flatten"; +import { Fold } from "./fold"; +import { FoldIndexed } from "./foldIndexed"; +import { ForEach } from "./forEach"; +import { ForEachIndexed } from "./forEachIndexed"; +import { GroupBy } from "./groupBy"; +import { IndexOf } from "./indexOf"; +import { IndexOfFirst } from "./indexOfFirst"; +import { IndexOfLast } from "./indexOfLast"; +import { JoinToString } from "./joinToString"; +import { Last } from "./last"; +import { LastOrNull } from "./lastOrNull"; +import { Map } from "./map"; +import { MapIndexed } from "./mapIndexed"; +import { MapNotNull } from "./mapNotNull"; +import { Max } from "./max"; +import { MaxBy } from "./maxBy"; +import { MaxWith } from "./maxWith"; +import { Merge } from "./merge"; +import { Min } from "./min"; +import { MinBy } from "./minBy"; +import { Minus } from "./minus"; +import { MinWith } from "./minWith"; +import { None } from "./none"; +import { OnEach } from "./onEach"; +import { Partition } from "./partition"; +import { Plus } from "./plus"; +import { Reduce } from "./reduce"; +import { ReduceIndexed } from "./reduceIndexed"; +import { Reverse } from "./reverse"; +import { Single } from "./single"; +import { SingleOrNull } from "./singleOrNull"; +import { Sorted } from "./sorted"; +import { SortedBy } from "./sortedBy"; +import { SortedByDescending } from "./sortedByDescending"; +import { SortedDescending } from "./sortedDescending"; +import { SortedWith } from "./sortedWith"; +import { Sum } from "./sum"; +import { SumBy } from "./sumBy"; +import { Take } from "./take"; +import { TakeWhile } from "./takeWhile"; +import { ToArray } from "./toArray"; +import { ToMap } from "./toMap"; +import { ToSet } from "./toSet"; +import { Unzip } from "./unzip"; +import { WithIndex } from "./withIndex"; +import { Zip } from "./zip"; import GeneratorIterator from "./GeneratorIterator"; import GeneratorSeedIterator from "./GeneratorSeedIterator"; @@ -174,3 +174,35 @@ export function range(start: number, endInclusive: number, step: number = 1): Se } }); } + +export function asKeysSequence(keyedIterable: { + keys(): IterableIterator; +}): Sequence; +export function asKeysSequence(obj: Record): Sequence; +export function asKeysSequence(a: any): Sequence { + const gen = typeof a.keys === "function" ? a.keys.bind(a) : function* () { + for (const key in a) { + if (a.hasOwnProperty(key)) { + yield key as K; + } + } + }; + + return asSequence(gen()); +} + +export function asValuesSequence(keyedIterable: { + values(): IterableIterator; +}): Sequence; +export function asValuesSequence(obj: Record): Sequence +export function asValuesSequence(a: any): Sequence { + const gen = typeof a.values === "function" ? a.values.bind(a) : function* () { + for (const key in a) { + if (a.hasOwnProperty(key)) { + yield a[key]; + } + } + }; + + return asSequence(gen()); +} From d6b98c11aa745956b086db9a95b74ccfede1a06d Mon Sep 17 00:00:00 2001 From: Voltra Date: Mon, 25 Mar 2024 10:42:30 +0000 Subject: [PATCH 2/4] Loosen contract on iterable methods --- src/Sequence.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Sequence.ts b/src/Sequence.ts index b197d7a..3d6e9fd 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -176,10 +176,18 @@ export function range(start: number, endInclusive: number, step: number = 1): Se } export function asKeysSequence(keyedIterable: { - keys(): IterableIterator; + keys(): Iterable; }): Sequence; export function asKeysSequence(obj: Record): Sequence; export function asKeysSequence(a: any): Sequence { + if (typeof a !== "object") { + throw new TypeError("Cannot create keys sequence for non-object input"); + } + + if (a === null) { + throw new TypeError("Cannot create keys sequence for input: null"); + } + const gen = typeof a.keys === "function" ? a.keys.bind(a) : function* () { for (const key in a) { if (a.hasOwnProperty(key)) { @@ -192,10 +200,18 @@ export function asKeysSequence(a: any): Sequence { } export function asValuesSequence(keyedIterable: { - values(): IterableIterator; + values(): Iterable; }): Sequence; export function asValuesSequence(obj: Record): Sequence export function asValuesSequence(a: any): Sequence { + if (typeof a !== "object") { + throw new TypeError("Cannot create values sequence for non-object input"); + } + + if (a === null) { + throw new TypeError("Cannot create values sequence for input: null"); + } + const gen = typeof a.values === "function" ? a.values.bind(a) : function* () { for (const key in a) { if (a.hasOwnProperty(key)) { From 80efae35f6324f77c23a5dcbf54e3be7188f18aa Mon Sep 17 00:00:00 2001 From: Voltra Date: Mon, 25 Mar 2024 10:45:24 +0000 Subject: [PATCH 3/4] Add unit tests for asKeysSequence and asValuesSequence --- test/asKeysSequence.test.ts | 79 +++++++++++++++++++++++++++++++++++ test/asValuesSequence.test.ts | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 test/asKeysSequence.test.ts create mode 100644 test/asValuesSequence.test.ts diff --git a/test/asKeysSequence.test.ts b/test/asKeysSequence.test.ts new file mode 100644 index 0000000..244525c --- /dev/null +++ b/test/asKeysSequence.test.ts @@ -0,0 +1,79 @@ +import { asKeysSequence } from "../src/Sequence"; + +describe("asKeysSequence", () => { + it("should create sequence from object keys", () => { + const array = asKeysSequence({ "a": 1, "b": 2, "c": 3 } as const) + .toArray(); + + expect(array.length).toBe(3); + expect(array[0]).toBe("a"); + expect(array[1]).toBe("b"); + expect(array[2]).toBe("c"); + }); + + it("should create sequence from Map keys", () => { + const array = asKeysSequence(new Map([ + ["a", 1], + ["b", 2], + ["c", 3], + ] as const)) + .toArray(); + + expect(array.length).toBe(3); + expect(array[0]).toBe("a"); + expect(array[1]).toBe("b"); + expect(array[2]).toBe("c"); + }); + + it("should create sequence from Set values", () => { + const array = asKeysSequence(new Set([ + "a", + "b", + "c", + ] as const)) + .toArray(); + + expect(array.length).toBe(3); + expect(array[0]).toBe("a"); + expect(array[1]).toBe("b"); + expect(array[2]).toBe("c"); + }); + + it("should create sequence from array indexes", () => { + const array = asKeysSequence([ + "a", + "b", + "c", + ] as const) + .toArray(); + + expect(array.length).toBe(3); + expect(array[0]).toBe(0); + expect(array[1]).toBe(1); + expect(array[2]).toBe(2); + }); + + it("should throw understandable error message if input is null", () => { + expect( + () => asKeysSequence(null as any) + ).toThrowError("Cannot create keys sequence for input: null"); + }); + + it.each([ + { input: undefined }, + { input: false }, + { input: true }, + { input: 0 }, + { input: 1 }, + { input: 42 }, + { input: 23.42 }, + { input: NaN }, + { input: Infinity }, + { input: () => 42 }, + { input: function* () { yield 42 } }, + ])("should throw understandable error message if input is non-object: %p", ({ input }) => { + expect( + () => asKeysSequence(input as any) + ).toThrowError("Cannot create keys sequence for non-object input"); + }); +}); diff --git a/test/asValuesSequence.test.ts b/test/asValuesSequence.test.ts new file mode 100644 index 0000000..e47072a --- /dev/null +++ b/test/asValuesSequence.test.ts @@ -0,0 +1,79 @@ +import { asValuesSequence } from "../src/Sequence"; + +describe("asValuesSequence", () => { + it("should create sequence from object values", () => { + const array = asValuesSequence({ "a": 1, "b": 2, "c": 3 } as const) + .toArray(); + + expect(array.length).toBe(3); + expect(array[0]).toBe(1); + expect(array[1]).toBe(2); + expect(array[2]).toBe(3); + }); + + it("should create sequence from Map values", () => { + const array = asValuesSequence(new Map([ + ["a", 1], + ["b", 2], + ["c", 3], + ] as const)) + .toArray(); + + expect(array.length).toBe(3); + expect(array[0]).toBe(1); + expect(array[1]).toBe(2); + expect(array[2]).toBe(3); + }); + + it("should create sequence from Set values", () => { + const array = asValuesSequence(new Set([ + "a", + "b", + "c", + ] as const)) + .toArray(); + + expect(array.length).toBe(3); + expect(array[0]).toBe("a"); + expect(array[1]).toBe("b"); + expect(array[2]).toBe("c"); + }); + + it("should create sequence from array values", () => { + const array = asValuesSequence([ + "a", + "b", + "c", + ] as const) + .toArray(); + + expect(array.length).toBe(3); + expect(array[0]).toBe("a"); + expect(array[1]).toBe("b"); + expect(array[2]).toBe("c"); + }); + + it("should throw understandable error message if input is null", () => { + expect( + () => asValuesSequence(null as any) + ).toThrowError("Cannot create values sequence for input: null"); + }); + + it.each([ + { input: undefined }, + { input: false }, + { input: true }, + { input: 0 }, + { input: 1 }, + { input: 42 }, + { input: 23.42 }, + { input: NaN }, + { input: Infinity }, + { input: () => 42 }, + { input: function* () { yield 42 } }, + ])("should throw understandable error message if input is non-object: %p", ({ input }) => { + expect( + () => asValuesSequence(input as any) + ).toThrowError("Cannot create values sequence for non-object input"); + }); +}); From 3ca13363a0c6b799afb879992ef72e48d95cec02 Mon Sep 17 00:00:00 2001 From: Voltra Date: Mon, 25 Mar 2024 10:47:45 +0000 Subject: [PATCH 4/4] Lint --- package.json | 4 ++-- src/Sequence.ts | 2 +- test/asKeysSequence.test.ts | 8 ++++---- test/asValuesSequence.test.ts | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index a251016..f459392 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "watch": "jest --watch --notify", "coverage": "rimraf coverage && jest --coverage", "travis": "yarn lint && yarn test", - "lint": "node_modules/.bin/tslint -c tslint.json 'src/**/*.ts' 'test/**/*.ts'", + "lint": "node_modules/.bin/tslint --fix -c tslint.json 'src/**/*.ts' 'test/**/*.ts'", "docs": "rimraf docs && typedoc --name Sequency --readme APIDOC.md -out docs --hideGenerator src/Sequence.ts", "docs-publish": "yarn docs && touch docs/.nojekyll && gh-pages -d docs -t", "bundle": "webpack --mode production && size-limit", @@ -83,4 +83,4 @@ "map", "set" ] -} +} \ No newline at end of file diff --git a/src/Sequence.ts b/src/Sequence.ts index 3d6e9fd..fba9f46 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -202,7 +202,7 @@ export function asKeysSequence(a: any): Sequence { export function asValuesSequence(keyedIterable: { values(): Iterable; }): Sequence; -export function asValuesSequence(obj: Record): Sequence +export function asValuesSequence(obj: Record): Sequence; export function asValuesSequence(a: any): Sequence { if (typeof a !== "object") { throw new TypeError("Cannot create values sequence for non-object input"); diff --git a/test/asKeysSequence.test.ts b/test/asKeysSequence.test.ts index 244525c..0efaa9e 100644 --- a/test/asKeysSequence.test.ts +++ b/test/asKeysSequence.test.ts @@ -15,7 +15,7 @@ describe("asKeysSequence", () => { const array = asKeysSequence(new Map([ ["a", 1], ["b", 2], - ["c", 3], + ["c", 3] ] as const)) .toArray(); @@ -29,7 +29,7 @@ describe("asKeysSequence", () => { const array = asKeysSequence(new Set([ "a", "b", - "c", + "c" ] as const)) .toArray(); @@ -43,7 +43,7 @@ describe("asKeysSequence", () => { const array = asKeysSequence([ "a", "b", - "c", + "c" ] as const) .toArray(); @@ -70,7 +70,7 @@ describe("asKeysSequence", () => { { input: NaN }, { input: Infinity }, { input: () => 42 }, - { input: function* () { yield 42 } }, + { input: function* () { yield 42; } } ])("should throw understandable error message if input is non-object: %p", ({ input }) => { expect( () => asKeysSequence(input as any) diff --git a/test/asValuesSequence.test.ts b/test/asValuesSequence.test.ts index e47072a..0b09e5c 100644 --- a/test/asValuesSequence.test.ts +++ b/test/asValuesSequence.test.ts @@ -15,7 +15,7 @@ describe("asValuesSequence", () => { const array = asValuesSequence(new Map([ ["a", 1], ["b", 2], - ["c", 3], + ["c", 3] ] as const)) .toArray(); @@ -29,7 +29,7 @@ describe("asValuesSequence", () => { const array = asValuesSequence(new Set([ "a", "b", - "c", + "c" ] as const)) .toArray(); @@ -43,7 +43,7 @@ describe("asValuesSequence", () => { const array = asValuesSequence([ "a", "b", - "c", + "c" ] as const) .toArray(); @@ -70,7 +70,7 @@ describe("asValuesSequence", () => { { input: NaN }, { input: Infinity }, { input: () => 42 }, - { input: function* () { yield 42 } }, + { input: function* () { yield 42; } } ])("should throw understandable error message if input is non-object: %p", ({ input }) => { expect( () => asValuesSequence(input as any)