From 26477edcf11ee961f01e04937b47caef1f0f420d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 24 Jan 2023 12:24:04 +0100 Subject: [PATCH] style: format with prettier --- .eslintrc | 7 +- .prettierrc | 1 + package.json | 3 +- pnpm-lock.yaml | 8 ++ src/defu.ts | 32 +++-- src/types.ts | 149 ++++++++++++---------- test/defu.test.ts | 310 +++++++++++++++++++++++++--------------------- 7 files changed, 288 insertions(+), 222 deletions(-) create mode 100644 .prettierrc diff --git a/.eslintrc b/.eslintrc index d004ea9..9a6f304 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,8 +1,7 @@ { - "extends": [ - "eslint-config-unjs" - ], + "extends": ["eslint-config-unjs"], "rules": { - "unicorn/prevent-abbreviations": 0 + "unicorn/prevent-abbreviations": 0, + "unicorn/no-null": 0 } } diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/package.json b/package.json index 717f982..5a9e606 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "scripts": { "build": "unbuild", "dev": "vitest", - "lint": "eslint --ext .ts src", + "lint": "eslint --ext .ts src && prettier -c src test", "prepack": "pnpm build", "release": "pnpm test && standard-version && git push --follow-tags && pnpm publish", "test": "pnpm lint && pnpm vitest run", @@ -32,6 +32,7 @@ "eslint": "^8.32.0", "eslint-config-unjs": "^0.1.0", "expect-type": "^0.15.0", + "prettier": "^2.8.3", "standard-version": "^9.5.0", "typescript": "^4.9.4", "unbuild": "^1.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3644a67..d110b35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ specifiers: eslint: ^8.32.0 eslint-config-unjs: ^0.1.0 expect-type: ^0.15.0 + prettier: ^2.8.3 standard-version: ^9.5.0 typescript: ^4.9.4 unbuild: ^1.1.1 @@ -17,6 +18,7 @@ devDependencies: eslint: 8.32.0 eslint-config-unjs: 0.1.0_7uibuqfxkfaozanbtbziikiqje expect-type: 0.15.0 + prettier: 2.8.3 standard-version: 9.5.0 typescript: 4.9.4 unbuild: 1.1.1 @@ -3396,6 +3398,12 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier/2.8.3: + resolution: {integrity: sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /pretty-bytes/6.0.0: resolution: {integrity: sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==} engines: {node: ^14.13.1 || >=16.0.0} diff --git a/src/defu.ts b/src/defu.ts index 45e2fba..61c8264 100644 --- a/src/defu.ts +++ b/src/defu.ts @@ -1,11 +1,16 @@ import type { Merger, DefuFn as DefuFunction, DefuInstance } from "./types"; -function isObject (value: any) { +function isObject(value: any) { return value !== null && typeof value === "object"; } // Base function to apply defaults -function _defu (baseObject: T, defaults: any, namespace: string = ".", merger?: Merger): T { +function _defu( + baseObject: T, + defaults: any, + namespace = ".", + merger?: Merger +): T { if (!isObject(defaults)) { return _defu(baseObject, {}, namespace, merger); } @@ -30,7 +35,12 @@ function _defu (baseObject: T, defaults: any, namespace: string = ".", merger if (Array.isArray(value) && Array.isArray(object[key])) { object[key] = [...value, ...object[key]]; } else if (isObject(value) && isObject(object[key])) { - object[key] = _defu(value, object[key], (namespace ? `${namespace}.` : "") + key.toString(), merger); + object[key] = _defu( + value, + object[key], + (namespace ? `${namespace}.` : "") + key.toString(), + merger + ); } else { object[key] = value; } @@ -40,9 +50,10 @@ function _defu (baseObject: T, defaults: any, namespace: string = ".", merger } // Create defu wrapper with optional merger and multi arg support -export function createDefu (merger?: Merger): DefuFunction { - // eslint-disable-next-line unicorn/no-array-reduce - return (...arguments_) => arguments_.reduce((p, c) => _defu(p, c, "", merger), {} as any); +export function createDefu(merger?: Merger): DefuFunction { + return (...arguments_) => + // eslint-disable-next-line unicorn/no-array-reduce + arguments_.reduce((p, c) => _defu(p, c, "", merger), {} as any); } // Standard version @@ -50,15 +61,18 @@ export const defu = createDefu() as DefuInstance; export default defu; // Custom version with function merge support -export const defuFn = createDefu((object, key, currentValue, _namespace) => { - if (typeof object[key] !== "undefined" && typeof currentValue === "function") { +export const defuFn = createDefu((object, key, currentValue) => { + if ( + typeof object[key] !== "undefined" && + typeof currentValue === "function" + ) { object[key] = currentValue(object[key]); return true; } }); // Custom version with function merge support only for defined arrays -export const defuArrayFn = createDefu((object, key, currentValue, _namespace) => { +export const defuArrayFn = createDefu((object, key, currentValue) => { if (Array.isArray(object[key]) && typeof currentValue === "function") { object[key] = currentValue(object[key]); return true; diff --git a/src/types.ts b/src/types.ts index 1dd4cc5..cfa7cc2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,94 +1,111 @@ -export type Input = Record -export type IgnoredInput = boolean | number | null | any[] | Record | undefined +export type Input = Record; +export type IgnoredInput = + | boolean + | number + | null + | any[] + | Record + | undefined; export type Merger = ( object: T, key: keyof T, value: T[K], namespace: string -) => any +) => any; -type nullish = null | undefined | void +type nullish = null | undefined | void; export type MergeObjects< Destination extends Input, Defaults extends Input -> = Destination extends Defaults ? Destination : Omit & Omit & - { - -readonly [Key in keyof Destination & keyof Defaults]: - Destination[Key] extends nullish - ? Defaults[Key] extends nullish - ? nullish - : Defaults[Key] - : Defaults[Key] extends nullish +> = Destination extends Defaults + ? Destination + : Omit & + Omit & { + -readonly [Key in keyof Destination & + keyof Defaults]: Destination[Key] extends nullish + ? Defaults[Key] extends nullish + ? nullish + : Defaults[Key] + : Defaults[Key] extends nullish ? Destination[Key] - : Merge // eslint-disable-line no-use-before-define - } + : Merge; // eslint-disable-line no-use-before-define + }; -export type Defu> = - D extends [infer F, ...infer Rest] - ? F extends Input - ? Rest extends Array - ? Defu, Rest> - : MergeObjects - : F extends IgnoredInput - ? Rest extends Array - ? Defu - : S - : S +export type Defu< + S extends Input, + D extends Array +> = D extends [infer F, ...infer Rest] + ? F extends Input + ? Rest extends Array + ? Defu, Rest> + : MergeObjects + : F extends IgnoredInput + ? Rest extends Array + ? Defu + : S : S + : S; -export type DefuFn = >( +export type DefuFn = < + Source extends Input, + Defaults extends Array +>( source: Source, ...defaults: Defaults -) => Defu +) => Defu; export interface DefuInstance { - >(source: Source | IgnoredInput, ...defaults: Defaults): Defu - fn: DefuFn - arrayFn: DefuFn - extend(merger?: Merger): DefuFn + >( + source: Source | IgnoredInput, + ...defaults: Defaults + ): Defu; + fn: DefuFn; + arrayFn: DefuFn; + extend(merger?: Merger): DefuFn; } -export type MergeArrays = Destination extends Array +export type MergeArrays = Destination extends Array< + infer DestinationType +> ? Source extends Array ? Array : Source | Array - : Source | Destination + : Source | Destination; -export type Merge< - Destination extends Input, - Defaults extends Input -> = +export type Merge = // Remove explicitly null types Destination extends nullish - ? Defaults extends nullish - ? nullish - : Defaults - : Defaults extends nullish + ? Defaults extends nullish + ? nullish + : Defaults + : Defaults extends nullish ? Destination - // Handle arrays - : Destination extends Array - ? Defaults extends Array - ? MergeArrays - : Destination | Defaults - // Don't attempt to merge Functions, RegExps, Promises - : Destination extends Function - ? Destination | Defaults - : Destination extends RegExp - ? Destination | Defaults - : Destination extends Promise - ? Destination | Defaults - // Don't attempt to merge Functions, RegExps, Promises - : Defaults extends Function - ? Destination | Defaults - : Defaults extends RegExp - ? Destination | Defaults - : Defaults extends Promise - ? Destination | Defaults - // Ensure we only merge Records - : Destination extends Input - ? Defaults extends Input - ? MergeObjects - : Destination | Defaults - : Destination | Defaults + : // Handle arrays + Destination extends Array + ? Defaults extends Array + ? MergeArrays + : Destination | Defaults + : // Don't attempt to merge Functions, RegExps, Promises + // eslint-disable-next-line @typescript-eslint/ban-types + Destination extends Function + ? Destination | Defaults + : Destination extends RegExp + ? Destination | Defaults + : Destination extends Promise + ? Destination | Defaults + : // Don't attempt to merge Functions, RegExps, Promises + // eslint-disable-next-line @typescript-eslint/ban-types + Defaults extends Function + ? Destination | Defaults + : Defaults extends RegExp + ? Destination | Defaults + : Defaults extends Promise + ? Destination | Defaults + : // Ensure we only merge Records + Destination extends Input + ? Defaults extends Input + ? MergeObjects + : Destination | Defaults + : Destination | Defaults; diff --git a/test/defu.test.ts b/test/defu.test.ts index 9525610..de980ba 100644 --- a/test/defu.test.ts +++ b/test/defu.test.ts @@ -1,190 +1,216 @@ -import { expectTypeOf } from 'expect-type' -import { it, describe, expect } from 'vitest' -import { defu, createDefu, defuFn, defuArrayFn } from '../src/defu' +import { expectTypeOf } from "expect-type"; +import { it, describe, expect } from "vitest"; +import { defu, createDefu, defuFn, defuArrayFn } from "../src/defu"; // Part of tests brought from jonschlinkert/defaults-deep (MIT) - -const nonObject = [null, undefined, [], false, true, 123] - -describe('defu', () => { - it('should copy only missing properties defaults', () => { - const result = defu({ a: 'c' }, { a: 'bbb', d: 'c' }) - expect(result).toEqual({ a: 'c', d: 'c' }) - expectTypeOf(result).toEqualTypeOf<{ a: string, d: string }>() - }) - - it('should fill in values that are null', () => { - const result1 = defu({ a: null as null }, { a: 'c', d: 'c' }) - expect(result1).toEqual({ a: 'c', d: 'c' }) - expectTypeOf(result1).toEqualTypeOf<{ a: string, d: string }>() - - const result2 = defu({ a: 'c' }, { a: null as null, d: 'c' }) - expect(result2).toEqual({ a: 'c', d: 'c' }) - expectTypeOf(result2).toEqualTypeOf<{ a: string, d: string }>() - }) - - it('should copy nested values', () => { - const result = defu({ a: { b: 'c' } }, { a: { d: 'e' } }) +const nonObject = [null, undefined, [], false, true, 123]; + +describe("defu", () => { + it("should copy only missing properties defaults", () => { + const result = defu({ a: "c" }, { a: "bbb", d: "c" }); + expect(result).toEqual({ a: "c", d: "c" }); + expectTypeOf(result).toEqualTypeOf<{ a: string; d: string }>(); + }); + + it("should fill in values that are null", () => { + const result1 = defu({ a: null as null }, { a: "c", d: "c" }); + expect(result1).toEqual({ a: "c", d: "c" }); + expectTypeOf(result1).toEqualTypeOf<{ a: string; d: string }>(); + + const result2 = defu({ a: "c" }, { a: null as null, d: "c" }); + expect(result2).toEqual({ a: "c", d: "c" }); + expectTypeOf(result2).toEqualTypeOf<{ a: string; d: string }>(); + }); + + it("should copy nested values", () => { + const result = defu({ a: { b: "c" } }, { a: { d: "e" } }); expect(result).toEqual({ - a: { b: 'c', d: 'e' } - }) - expectTypeOf(result).toEqualTypeOf<{ a: { b: string, d: string } }>() - }) + a: { b: "c", d: "e" }, + }); + expectTypeOf(result).toEqualTypeOf<{ a: { b: string; d: string } }>(); + }); - it('should concat array values by default', () => { - const result = defu({ array: ['a', 'b'] }, { array: ['c', 'd'] }) + it("should concat array values by default", () => { + const result = defu({ array: ["a", "b"] }, { array: ["c", "d"] }); expect(result).toEqual({ - array: ['a', 'b', 'c', 'd'] - }) - expectTypeOf(result).toEqualTypeOf<{ array: string[] }>() - }) - - it('should correctly type differing array values', () => { - const item1 = { name: 'Name', age: 21 } - const item2 = { name: 'Name', age: '42' } - const result = defu({ items: [item1] }, { items: [item2] }) - expect(result).toEqual({ items: [item1, item2] }) - expectTypeOf(result).toEqualTypeOf<{ items: Array<{ name: string, age: number } | { name: string, age: string }> }>() - }) - - it('should correctly merge different object types', () => { - const fn = () => 42 - const re = /test/i - - const result = defu({ a: fn }, { a: re }) - expect(result).toEqual({ a: fn }) - expectTypeOf(result).toEqualTypeOf<{ a:(() => number) | RegExp }>() - }) - - it('should handle non object first param', () => { + array: ["a", "b", "c", "d"], + }); + expectTypeOf(result).toEqualTypeOf<{ array: string[] }>(); + }); + + it("should correctly type differing array values", () => { + const item1 = { name: "Name", age: 21 }; + const item2 = { name: "Name", age: "42" }; + const result = defu({ items: [item1] }, { items: [item2] }); + expect(result).toEqual({ items: [item1, item2] }); + expectTypeOf(result).toEqualTypeOf<{ + items: Array< + { name: string; age: number } | { name: string; age: string } + >; + }>(); + }); + + it("should correctly merge different object types", () => { + const fn = () => 42; + const re = /test/i; + + const result = defu({ a: fn }, { a: re }); + expect(result).toEqual({ a: fn }); + expectTypeOf(result).toEqualTypeOf<{ a: (() => number) | RegExp }>(); + }); + + it("should handle non object first param", () => { for (const val of nonObject) { - expect(defu(val, { d: true })).toEqual({ d: true }) + expect(defu(val, { d: true })).toEqual({ d: true }); } - }) + }); - it('should handle non object second param', () => { + it("should handle non object second param", () => { for (const val of nonObject) { - expect(defu({ d: true }, val)).toEqual({ d: true }) + expect(defu({ d: true }, val)).toEqual({ d: true }); } - }) + }); - it('multi defaults', () => { - const result = defu({ a: 1 }, { b: 2, a: 'x' }, { c: 3, a: 'x', b: 'x' }) + it("multi defaults", () => { + const result = defu({ a: 1 }, { b: 2, a: "x" }, { c: 3, a: "x", b: "x" }); expect(result).toEqual({ a: 1, b: 2, - c: 3 - }) - expectTypeOf(result).toEqualTypeOf<{ a: string | number, b: string | number, c: number }>() - }) - - it('should not override Object prototype', () => { + c: 3, + }); + expectTypeOf(result).toEqualTypeOf<{ + a: string | number; + b: string | number; + c: number; + }>(); + }); + + it("should not override Object prototype", () => { const payload = JSON.parse( '{"constructor": {"prototype": {"isAdmin": true}}}' - ) - defu({}, payload) - defu(payload, {}) - defu(payload, payload) + ); + defu({}, payload); + defu(payload, {}); + defu(payload, payload); // @ts-ignore - expect({}.isAdmin).toBe(undefined) - }) + expect({}.isAdmin).toBe(undefined); + }); - it('should ignore non-object arguments', () => { + it("should ignore non-object arguments", () => { expect(defu(null, { foo: 1 }, false, 123, { bar: 2 })).toEqual({ foo: 1, - bar: 2 - }) - }) - - it('should merge types of more than two objects', () => { - interface SomeConfig { foo: string } - interface SomeOtherConfig { bar: string[] } - interface ThirdConfig { baz: number[] } + bar: 2, + }); + }); + + it("should merge types of more than two objects", () => { + interface SomeConfig { + foo: string; + } + interface SomeOtherConfig { + bar: string[]; + } + interface ThirdConfig { + baz: number[]; + } interface ExpectedMergedType { - foo: string - bar: string[] - baz: number[] + foo: string; + bar: string[]; + baz: number[]; } - expectTypeOf(defu({} as SomeConfig, {} as SomeOtherConfig, {} as ThirdConfig)) - .toEqualTypeOf() - }) + expectTypeOf( + defu({} as SomeConfig, {} as SomeOtherConfig, {} as ThirdConfig) + ).toEqualTypeOf(); + }); - it('should allow partials within merge chain', () => { - interface SomeConfig { foo: string[] } - interface SomeOtherConfig { bar: string[] } + it("should allow partials within merge chain", () => { + interface SomeConfig { + foo: string[]; + } + interface SomeOtherConfig { + bar: string[]; + } interface ExpectedMergedType { - foo: string[] - bar: string[] + foo: string[]; + bar: string[]; } - let options: (SomeConfig & SomeOtherConfig) | undefined + let options: (SomeConfig & SomeOtherConfig) | undefined; expectTypeOf( - defu(options ?? {}, { foo: ['test'] }, { bar: ['test2'] }, {}) - ).toEqualTypeOf() + defu(options ?? {}, { foo: ["test"] }, { bar: ["test2"] }, {}) + ).toEqualTypeOf(); expectTypeOf( - defu({ foo: ['test'] }, {}, { bar: ['test2'] }, {}) - ).toEqualTypeOf() - }) + defu({ foo: ["test"] }, {}, { bar: ["test2"] }, {}) + ).toEqualTypeOf(); + }); - it('custom merger', () => { + it("custom merger", () => { const ext = createDefu((obj, key, val) => { - if (typeof val === 'number') { - ;(obj as any)[key] += val - return true + if (typeof val === "number") { + (obj as any)[key] += val; + return true; } - }) - expect(ext({ cost: 15 }, { cost: 10 })).toEqual({ cost: 25 }) - }) + }); + expect(ext({ cost: 15 }, { cost: 10 })).toEqual({ cost: 25 }); + }); - it('defuFn()', () => { - const num = () => 20 + it("defuFn()", () => { + const num = () => 20; expect( defuFn( { - ignore: val => val.filter(i => i !== 'dist'), + ignore: (val) => val.filter((i) => i !== "dist"), num, - ignored: num + ignored: num, }, { - ignore: ['node_modules', 'dist'], - num: 10 + ignore: ["node_modules", "dist"], + num: 10, } ) ).toEqual({ - ignore: ['node_modules'], + ignore: ["node_modules"], num: 20, - ignored: num - }) - }) - - it('defuArrayFn()', () => { - const num = () => 20 - expect(defuArrayFn({ - arr: () => ['c'], - num - }, { - arr: ['a', 'b'], - num: 10 - })).toEqual({ - arr: ['c'], - num - }) - }) - - it('custom merger with namespace', () => { + ignored: num, + }); + }); + + it("defuArrayFn()", () => { + const num = () => 20; + expect( + defuArrayFn( + { + arr: () => ["c"], + num, + }, + { + arr: ["a", "b"], + num: 10, + } + ) + ).toEqual({ + arr: ["c"], + num, + }); + }); + + it("custom merger with namespace", () => { const ext = createDefu((obj, key, val, namespace) => { // console.log({ obj, key, val, namespace }) - if (key === 'modules') { + if (key === "modules") { // TODO: It is not possible to override types with extend() // @ts-ignore - obj[key] = namespace + ':' + [...val, ...obj[key]].sort().join(',') - return true + obj[key] = namespace + ":" + [...val, ...obj[key]].sort().join(","); + return true; } - }) - - const obj1 = { modules: ['A'], foo: { bar: { modules: ['X'] } } } - const obj2 = { modules: ['B'], foo: { bar: { modules: ['Y'] } } } - expect(ext(obj1, obj2)).toEqual({ modules: ':A,B', foo: { bar: { modules: 'foo.bar:X,Y' } } }) - }) -}) + }); + + const obj1 = { modules: ["A"], foo: { bar: { modules: ["X"] } } }; + const obj2 = { modules: ["B"], foo: { bar: { modules: ["Y"] } } }; + expect(ext(obj1, obj2)).toEqual({ + modules: ":A,B", + foo: { bar: { modules: "foo.bar:X,Y" } }, + }); + }); +});