Skip to content

Commit

Permalink
Merge pull request #75 from Maggi64/feature/const-types
Browse files Browse the repository at this point in the history
Stricter Types with HotScript
  • Loading branch information
Maggi64 authored Apr 18, 2023
2 parents 5b3c56a + f9d9763 commit 513a98d
Show file tree
Hide file tree
Showing 12 changed files with 1,029 additions and 2,100 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-bikes-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"moderndash": minor
---

`set` | Path autocomplete & correct return types
5 changes: 5 additions & 0 deletions .changeset/mighty-pianos-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"moderndash": patch
---

`set` | Fix path validation
5 changes: 5 additions & 0 deletions .changeset/polite-lobsters-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"moderndash": minor
---

`FlatKeys` | Correct return types
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
module.exports = {
root: true,
extends: ["dewald"]
};
};
3,026 changes: 950 additions & 2,076 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
"vitest": "0.30.1",
"@vitest/coverage-c8": "0.30.1",
"@vitest/ui": "0.30.1",
"vite": "4.2.1"
"vite": "4.2.1",
"hotscript": "1.0.11"
},
"overrides": {
"tsup": {
"rollup": "3.20.4"
}
}
}
}
7 changes: 6 additions & 1 deletion package/src/number/sum.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

import type { Tuples, Call } from "hotscript";

/**
* Calculates the sum of an array of numbers.
*
Expand All @@ -9,7 +12,9 @@
* @returns The sum of the input array
*/

export function sum(numbers: readonly number[]): number {
export function sum(numbers: number[]): number;
export function sum<TNum extends readonly number[]>(numbers: TNum): Call<Tuples.Sum, TNum>;
export function sum<TNum extends readonly number[]>(numbers: TNum): Call<Tuples.Sum, TNum> | number {
if (numbers.length === 0)
return NaN;
return numbers.reduce((total, current) => total + current, 0);
Expand Down
8 changes: 6 additions & 2 deletions package/src/object/flatKeys.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { PlainObject } from "@type/PlainObject.js";
import type { Call, Objects } from "hotscript";

import { isPlainObject } from "@validate/isPlainObject.js";

type StringIfNever<Type> = [Type] extends [never] ? string : Type;
type Paths<TObj> = StringIfNever<Call<Objects.AllPaths, TObj>>;

/**
* Flattens an object into a single level object.
*
Expand All @@ -15,7 +19,7 @@ import { isPlainObject } from "@validate/isPlainObject.js";
* @returns A new object with flattened keys.
*/

export function flatKeys<TObj extends PlainObject>(obj: TObj): Record<string, unknown> {
export function flatKeys<TObj extends PlainObject>(obj: TObj): Record<Paths<TObj>, unknown> {
const flatObject: Record<string, unknown> = {};

for (const [key, value] of Object.entries(obj)) {
Expand All @@ -39,4 +43,4 @@ function addToResult(prefix: string, value: unknown, flatObject: Record<string,
} else {
flatObject[prefix] = value;
}
}
}
15 changes: 11 additions & 4 deletions package/src/object/set.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type { PlainObject } from "@type/PlainObject.js";
import type { Call, Objects } from "hotscript";

import { isPlainObject } from "@validate/isPlainObject.js";

const validPathRegex = /^(?:[^.[\]]+(?:\[\d+])*(?:\.|\[\d+]))+(?:[^.[\]]+(?:\[\d+])*)+$/;
const validPathRegex = /^[^.[\]]+(?:\.[^.[\]]+)*(?:\[\d+])*(?:\.[^.[\]]+(?:\[\d+])*)*$/;
const pathSplitRegex = /\.|(?=\[)/g;
const matchBracketsRegex = /[[\]]/g;

// eslint-disable-next-line @typescript-eslint/ban-types
type Paths<TObj> = Call<Objects.AllPaths, TObj> | string & {};
type UpdateObj<TObj extends PlainObject, TPath extends string, TVal> = Call<Objects.Update<TPath, TVal>, TObj>;

/**
* Sets the value at path of object. If a portion of path doesn’t exist, it’s created.
*
Expand All @@ -30,14 +35,16 @@ const matchBracketsRegex = /[[\]]/g;
* @param path The path of the property to set.
* @param value The value to set.
* @template TObj The type of the object.
* @template TPath The type of the object path.
* @template TVal The type of the value to set.
* @returns The modified object.
*/

export function set(obj: PlainObject, path: string, value: unknown): PlainObject {
export function set<TObj extends PlainObject, TPath extends Paths<TObj>, TVal>(obj: TObj, path: TPath, value: TVal): UpdateObj<TObj, TPath, TVal> {
if (!validPathRegex.test(path))
throw new Error("Invalid path, look at the examples for the correct format.");

const pathParts = path.split(pathSplitRegex);
const pathParts = (path as string).split(pathSplitRegex);
let currentObj: PlainObject = obj;
for (let index = 0; index < pathParts.length; index++) {
const key = pathParts[index].replace(matchBracketsRegex, "");
Expand All @@ -58,5 +65,5 @@ export function set(obj: PlainObject, path: string, value: unknown): PlainObject
currentObj = currentObj[key] as PlainObject;
}

return obj;
return obj as UpdateObj<TObj, TPath, TVal>;
}
4 changes: 2 additions & 2 deletions package/test/crypto/randomFloat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ test("can return the upper and lower bounds", () => {
expect(results).toContain(max);
});

test("average of 100000 random numbers should be close to the middle", () => {
test("average of 200000 random numbers should be close to the middle", () => {
const min = 0;
const max = 1;
const iterations = 100000;
const iterations = 200000;
let sum = 0;

for (let i = 0; i < iterations; i++) {
Expand Down
22 changes: 16 additions & 6 deletions package/test/object/flatKeys.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import type { PlainObject } from "@type/PlainObject.js";

import { flatKeys } from "@object/flatKeys.js";

test("correct flattened keys", () => {
const obj = { a: 1, b: { c: 2, d: { e: 3 } } };
expect(flatKeys(obj)).toEqual({ a: 1, "b.c": 2, "b.d.e": 3 });
});

test("correct flattened keys with arrays", () => {
const obj = { a: 1, b: { c: 2, d: [{ e: 3 }, { e: 4, f: { g: 5 } }] } };
expect(flatKeys(obj)).toEqual({ a: 1, "b.c": 2, "b.d[0].e": 3, "b.d[1].e": 4, "b.d[1].f.g": 5 });
test("correct flattened keys", () => {
const obj = { a: 1, b: { c: 2, d: { e: 3 } } };
const flat = flatKeys(obj);
const flatGeneric = flatKeys(obj as PlainObject);

expectTypeOf(flat).toEqualTypeOf<Record<"a" | "b" | "b.c" | "b.d" | "b.d.e", unknown>>();
expectTypeOf(flatGeneric).toEqualTypeOf<Record<string, unknown>>();

expect(flatKeys(obj)).toEqual({ a: 1, "b.c": 2, "b.d.e": 3 });
});

test("nested arrays", () => {
const obj = { a: [[1, 2], [3, 4]] };
expect(flatKeys(obj)).toEqual({ "a[0][0]": 1, "a[0][1]": 2, "a[1][0]": 3, "a[1][1]": 4 });
test("correct flattened keys with arrays", () => {
const obj = { a: 1, b: { c: 2, d: [{ e: 3 }, { e: 4, f: { g: 5 } }] } };
const flat = flatKeys(obj);
expectTypeOf(flat).toEqualTypeOf<Record<"a" | "b" | "b.c" | "b.d" | `b.d[${number}]` | `b.d[${number}].e` | `b.d[${number}].f` | `b.d[${number}].f.g`, unknown>>();
expect(flat).toEqual({ a: 1, "b.c": 2, "b.d[0].e": 3, "b.d[1].e": 4, "b.d[1].f.g": 5 });
});

test("simple array", () => {
Expand Down
20 changes: 14 additions & 6 deletions package/test/object/set.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ import { set } from "@object/set.js";

test("set a value", () => {
const obj = { a: { b: 2 } };
set(obj, "a.c", 1);
const updatedObj = set(obj, "a.c", 1);

expectTypeOf(updatedObj).toEqualTypeOf<{ a: { b: number; c: number } }>();
expect(obj).toEqual({ a: { b: 2, c: 1 } });

const updatedObj2 = set(obj, "a.c.d", 1);
expectTypeOf(updatedObj2).toEqualTypeOf<{ a: { b: number; c: { d: number } } }>();
expect(obj).toEqual({ a: { b: 2, c: { d: 1 } } });
});

test("set a value with array path", () => {
Expand All @@ -18,11 +24,13 @@ test("set a value with array path", () => {
expect(obj).toEqual({ a: [{ c: 3 }] });
});

test("recognise number key", () => {
const obj = { a: 1 };
set(obj, "a.e0[0]", 1);
expect(obj).toEqual({ a: { e0: [1] } });
});
// TODO Waiting for hotscript fix
// test("recognize number key", () => {
// const obj = { a: 1 };
// const updatedObj = set(obj, "a[0]", 4);
// expectTypeOf(updatedObj).toEqualTypeOf<{ a: number[] }>();
// expect(obj).toEqual({ a: [4] });
// });

test("throw error on incorrect path format", () => {
const obj = { a: { b: 2 } };
Expand Down

0 comments on commit 513a98d

Please sign in to comment.