diff --git a/.changeset/clever-points-tap.md b/.changeset/clever-points-tap.md new file mode 100644 index 000000000..bb0088d1d --- /dev/null +++ b/.changeset/clever-points-tap.md @@ -0,0 +1,5 @@ +--- +'@modern-kit/types': minor +--- + +feat(types): Merge, Invert, NegativeNumber 타입 추가 - @ssi02014 diff --git a/packages/types/src/ExtendOmittedProperties/ExtendOmittedProperties.spec.ts b/packages/types/src/ExtendOmittedProperties/ExtendOmittedProperties.spec.ts deleted file mode 100644 index 3c02c90f9..000000000 --- a/packages/types/src/ExtendOmittedProperties/ExtendOmittedProperties.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, it, expectTypeOf } from 'vitest'; -import { ExtendOmittedProperties } from '.'; - -describe('ExcludeNullish', () => { - it('첫 번째 제네릭 타입의 프로퍼티를 Omit으로 제외 후 제외 한 프로퍼티를 확장합니다.', () => { - type TestType = { foo: string; bar: number }; - - const test: ExtendOmittedProperties = { - foo: 'foo', - bar: 'bar', - }; - - // toEqualTypeOf 이슈로 우회 테스트 - expectTypeOf(test as { foo: string; bar: string }).toEqualTypeOf<{ - foo: string; - bar: string; - }>(); - }); -}); diff --git a/packages/types/src/ExtendOmittedProperties/index.ts b/packages/types/src/ExtendOmittedProperties/index.ts deleted file mode 100644 index 5c54f032e..000000000 --- a/packages/types/src/ExtendOmittedProperties/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ExtendOmittedProperties< - T extends Record, - E extends Record -> = Omit & E; diff --git a/packages/types/src/ExtractMapType/ExtractMapType.spec.ts b/packages/types/src/ExtractMapType/ExtractMapType.spec.ts index b6e86670d..d9500b41a 100644 --- a/packages/types/src/ExtractMapType/ExtractMapType.spec.ts +++ b/packages/types/src/ExtractMapType/ExtractMapType.spec.ts @@ -2,7 +2,7 @@ import { describe, it, expectTypeOf } from 'vitest'; import { ExtractMapType } from '.'; describe('ExtractMapType', () => { - it('should infer the generic type of a Map', () => { + it('Map의 제네릭 타입을 올바르게 추론해야 합니다.', () => { const key = 'foo' as 'foo' | 'bar'; const value = 1 as 1 | 2 | 3; diff --git a/packages/types/src/ExtractNestedArrayType/ExtractNestedArrayType.spec.ts b/packages/types/src/ExtractNestedArrayType/ExtractNestedArrayType.spec.ts index 6526fe02f..031d6d3a0 100644 --- a/packages/types/src/ExtractNestedArrayType/ExtractNestedArrayType.spec.ts +++ b/packages/types/src/ExtractNestedArrayType/ExtractNestedArrayType.spec.ts @@ -2,7 +2,7 @@ import { describe, it, expectTypeOf } from 'vitest'; import { ExtractNestedArrayType } from '.'; describe('ExtractNestedArrayType', () => { - it('should correctly extract the nested array type when all elements are numbers', () => { + it('모든 요소가 숫자인 중첩 배열에서 올바르게 타입을 추출해야 합니다.', () => { const arr = [1, 2, [3, 4, [5, 6]]]; // (number | (number | number[])[])[] const flattenArray = arr.flat(2); // number[] @@ -11,7 +11,7 @@ describe('ExtractNestedArrayType', () => { expectTypeOf(flattenArray).toEqualTypeOf(); }); - it('should correctly extract the nested array type when elements are of mixed types', () => { + it('요소가 혼합된 타입인 중첩 배열에서 올바르게 타입을 추출해야 합니다.', () => { const arr = [1, 2, ['string', 4, [true, 6]]]; // (number | (string | number | (number | boolean)[])[])[] const flattenArray = arr.flat(3); // (string | number | boolean)[] diff --git a/packages/types/src/Integer/Integer.spec.ts b/packages/types/src/Integer/Integer.spec.ts new file mode 100644 index 000000000..27b3582e1 --- /dev/null +++ b/packages/types/src/Integer/Integer.spec.ts @@ -0,0 +1,14 @@ +import { describe, it, expectTypeOf } from 'vitest'; +import { Integer } from '.'; + +describe('Integer', () => { + it('정수라면 해당 타입을 반환하고, 정수가 아니라면 never를 반환해야 합니다. ', () => { + const validInteger1: Integer<1> = 1; // 양의 정수 + const validInteger2: Integer<-1> = -1; // 음의 정수 + const invalidInteger: Integer<1.5> = null as unknown as Integer<1.5>; + + expectTypeOf(validInteger1).toEqualTypeOf<1>(); + expectTypeOf(validInteger2).toEqualTypeOf<-1>(); + expectTypeOf(invalidInteger).toEqualTypeOf(); + }); +}); diff --git a/packages/types/src/Integer/index.ts b/packages/types/src/Integer/index.ts index e76b0ba38..fb5880562 100644 --- a/packages/types/src/Integer/index.ts +++ b/packages/types/src/Integer/index.ts @@ -1,3 +1,13 @@ +/** + * @description 숫자 타입이 정수인지 확인하는 타입 + * + * @template T - 검사할 숫자 타입 + * @returns 입력된 숫자가 정수이면 해당 타입을 반환하고, 소수점이 있으면 never를 반환 + * + * @example + * type ValidInteger = Integer<1>; // 1 + * type InvalidInteger = Integer<1.5>; // never + */ export type Integer = `${T}` extends `${string}.${string}` ? never : T; diff --git a/packages/types/src/Invert/Invert.spec.ts b/packages/types/src/Invert/Invert.spec.ts new file mode 100644 index 000000000..2f40e4520 --- /dev/null +++ b/packages/types/src/Invert/Invert.spec.ts @@ -0,0 +1,15 @@ +import { describe, it, expectTypeOf } from 'vitest'; +import { Invert } from '.'; + +describe('Invert', () => { + it('객체의 key와 value를 서로 바꾼 새로운 타입을 반환해야 합니다.', () => { + const originObj = { foo: 'a', bar: 'b' } as const; + + const invertedObj: Invert = { a: 'foo', b: 'bar' }; + + expectTypeOf(invertedObj).toEqualTypeOf<{ + readonly a: 'foo'; + readonly b: 'bar'; + }>(); + }); +}); diff --git a/packages/types/src/Invert/index.ts b/packages/types/src/Invert/index.ts new file mode 100644 index 000000000..5d6b87681 --- /dev/null +++ b/packages/types/src/Invert/index.ts @@ -0,0 +1,13 @@ +/** + * @description 객체의 key와 value을 서로 바꾸는 타입입니다. + * + * @template T - 타입을 변경 할 객체 타입 + * @returns 원본 객체의 키와 값이 서로 바뀐 새로운 타입을 반환합니다 + * + * @example + * type originObj = { a: "x", b: "y" }; + * type invertedObj = Invert; // { x: "a", y: "b" } + */ +export type Invert> = { + [K in keyof T as T[K]]: K; +}; diff --git a/packages/types/src/Merge/Merge.spec.ts b/packages/types/src/Merge/Merge.spec.ts new file mode 100644 index 000000000..4af1626a0 --- /dev/null +++ b/packages/types/src/Merge/Merge.spec.ts @@ -0,0 +1,23 @@ +import { describe, it, expectTypeOf } from 'vitest'; +import { Merge } from '.'; + +describe('Merge', () => { + it('두 개의 객체 타입을 병합하여 새로운 객체 타입을 반환해야 합니다.', () => { + type MergedType = Merge< + { a: string; b: number }, + { b: string; c: boolean } + >; // { a: string; b: string; c: boolean } + + const obj: MergedType = { + a: 'a', + b: 'b', + c: true, + }; + + expectTypeOf(obj as { a: string; b: string; c: boolean }).toEqualTypeOf<{ + a: string; + b: string; + c: boolean; + }>(); + }); +}); diff --git a/packages/types/src/Merge/index.ts b/packages/types/src/Merge/index.ts new file mode 100644 index 000000000..d850bfcb2 --- /dev/null +++ b/packages/types/src/Merge/index.ts @@ -0,0 +1,21 @@ +/** + * @description 두 객체 타입을 병합하는 타입입니다. 이때, 겹치는 프로퍼티는 두 번째 타입의 타입으로 대체됩니다. + * + * @template A - 첫 번째 타입 + * @template B - 두 번째 타입 + * + * @example + * type A = { a: string, b: number } + * type B = { b: string, c: boolean } + * type Result = Merge + * + * // 동작 원리와 순서 + * // 1. Result = Merge + * // 2. Result = Omit & B + * // 3. Result = { a: string } & B + * // 4. Result = { a: string, b: string, c: boolean } + */ +export type Merge< + A extends Record, + B extends Record +> = Omit & B; diff --git a/packages/types/src/NaturalNumber/NaturalNumber.spec.ts b/packages/types/src/NaturalNumber/NaturalNumber.spec.ts new file mode 100644 index 000000000..cb0eee0d9 --- /dev/null +++ b/packages/types/src/NaturalNumber/NaturalNumber.spec.ts @@ -0,0 +1,18 @@ +import { describe, it, expectTypeOf } from 'vitest'; +import { NaturalNumber } from '.'; + +describe('NaturalNumber', () => { + it('자연수라면 해당 타입을 반환하고, 자연수가 아니라면 never를 반환해야 합니다. ', () => { + const validNaturalNumber: NaturalNumber<1> = 1; + + const invalidNaturalNumber1: NaturalNumber<0> = null as NaturalNumber<0>; + const invalidNaturalNumber2: NaturalNumber<-1> = null as NaturalNumber<-1>; + const invalidNaturalNumber3: NaturalNumber<1.5> = + null as NaturalNumber<1.5>; + + expectTypeOf(validNaturalNumber).toEqualTypeOf<1>(); + expectTypeOf(invalidNaturalNumber1).toEqualTypeOf(); + expectTypeOf(invalidNaturalNumber2).toEqualTypeOf(); + expectTypeOf(invalidNaturalNumber3).toEqualTypeOf(); + }); +}); diff --git a/packages/types/src/NaturalNumber/index.ts b/packages/types/src/NaturalNumber/index.ts index 5dce9b7eb..e29f431bc 100644 --- a/packages/types/src/NaturalNumber/index.ts +++ b/packages/types/src/NaturalNumber/index.ts @@ -1,7 +1,17 @@ -import { Integer } from 'Integer'; - -export type NaturalNumber = Integer extends never - ? never - : `${T}` extends `-${string}` | '0' +/** + * @description 자연수를 나타내는 타입입니다 (0보다 큰 양의 정수). + * + * @template T - 검사할 숫자 타입 + * @returns 입력된 숫자 T가 자연수인 경우 해당 타입을 반환하고,유효하지 않은 자연수인 경우 never를 반환합니다. + * @example + * type ValidNaturalNumber = NaturalNumber<1>; // 1 + * type InvalidNaturalNumber1 = NaturalNumber<0>; // never + * type InvalidNaturalNumber2 = NaturalNumber<-1>; // never + * type InvalidNaturalNumber3 = NaturalNumber<1.5>; // never + */ +export type NaturalNumber = `${T}` extends + | `${number}.${number}` + | `-${string}` + | '0' ? never : T; diff --git a/packages/types/src/NegativeNumber/NegativeNumber.spec.ts b/packages/types/src/NegativeNumber/NegativeNumber.spec.ts new file mode 100644 index 000000000..84ad7aaaa --- /dev/null +++ b/packages/types/src/NegativeNumber/NegativeNumber.spec.ts @@ -0,0 +1,18 @@ +import { describe, it, expectTypeOf } from 'vitest'; +import { NegativeNumber } from '.'; + +describe('NegativeNumber', () => { + it('음수라면 해당 타입을 반환하고, 음수가 아니라면 never를 반환해야 합니다. ', () => { + const validNegative: NegativeNumber<-1> = -1; + + const invalidNegative1: NegativeNumber<1> = + null as unknown as NegativeNumber<1>; // 양수 + + const invalidNegative2: NegativeNumber<1.5> = + null as unknown as NegativeNumber<1.5>; // 소수 + + expectTypeOf(validNegative).toEqualTypeOf<-1>(); + expectTypeOf(invalidNegative1).toEqualTypeOf(); + expectTypeOf(invalidNegative2).toEqualTypeOf(); + }); +}); diff --git a/packages/types/src/NegativeNumber/index.ts b/packages/types/src/NegativeNumber/index.ts new file mode 100644 index 000000000..59d81f26f --- /dev/null +++ b/packages/types/src/NegativeNumber/index.ts @@ -0,0 +1,13 @@ +/** + * @description 주어진 숫자 타입이 음수인지 확인하는 타입 + * + * @template T - 검사할 숫자 타입 + * @returns 입력된 타입 T가 음수인 경우 T를 반환하고, 그렇지 않으면 never를 반환 + * + * @example + * type validNegativeNumber = NegativeNumber<-1>; // -1 + * type invalidNegativeNumber = NegativeNumber<1>; // never + */ +export type NegativeNumber = `${T}` extends `-${number}` + ? T + : never; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 77529f83b..af29552ee 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,13 +1,15 @@ export * from './Arrayable'; export * from './ArrayWithReadonly'; export * from './ExcludeNullish'; -export * from './ExtendOmittedProperties'; export * from './ExtractMapType'; export * from './ExtractNestedArrayType'; export * from './ExtractSetType'; export * from './IndexSignature'; export * from './Integer'; +export * from './Invert'; +export * from './Merge'; export * from './NaturalNumber'; +export * from './NegativeNumber'; export * from './NonEmptyArray'; export * from './Nullable'; export * from './ObjectEntries'; diff --git a/packages/utils/src/common/asyncNoop/asyncNoop.spec.ts b/packages/utils/src/common/asyncNoop/asyncNoop.spec.ts index 26cef85d3..478d60fe7 100644 --- a/packages/utils/src/common/asyncNoop/asyncNoop.spec.ts +++ b/packages/utils/src/common/asyncNoop/asyncNoop.spec.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { asyncNoop } from '.'; describe('asyncNoop', () => { - it('should return a Promise', () => { + it('Promise를 반환해야 합니다', () => { expect(asyncNoop()).toBeInstanceOf(Promise); }); });