All files / utils makeEnum.ts

100% Statements 13/13
100% Branches 10/10
100% Functions 6/6
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93                                                                                  11x       11x     29x       29x               2x 2x 2x   5x 4x         5x                     9x 9x               11x    
import { castArray, invert } from 'lodash-uni'
import { LiteralUnion, OneOrMore, ValueOf } from '../types'
 
export type EnumKey = string
export type EnumValue = string | number | boolean
export type EnumMap = Record<EnumKey, EnumValue>
 
// https://stackoverflow.com/questions/56415826/is-it-possible-to-precisely-type-invert-in-typescript
type AllValues<T extends Record<EnumKey, EnumValue>> = {
  [P in keyof T]: {
    label: P
    value: T[P] extends true ? 'true' : T[P] extends false ? 'false' : T[P]
  }
}[keyof T]
type InvertResult<T extends Record<EnumKey, EnumValue>> = {
  // @ts-ignore
  [P in AllValues<T>['value']]: Extract<AllValues<T>, { value: P }>['label']
}
 
type List<T extends Record<EnumKey, EnumValue>> = Array<
  {
    [P in keyof T]: {
      label: P
      value: T[P]
    }
  }[keyof T]
>
 
export type EnumResult<T extends EnumMap> = T &
  InvertResult<T> & {
    $list: List<T>
    $buildList: (keys: Array<keyof T> | Record<keyof T, any>) => List<T>
    $is(value: any, keys: OneOrMore<LiteralUnion<keyof T, ValueOf<T>>>): boolean
  }
 
/**
 * 构造枚举数据。
 *
 * @param map 枚举映射数据
 */
export function makeEnum<T extends EnumMap>(map: T): EnumResult<T> {
  const res: EnumResult<T> = {
    ...map,
    ...invert(map),
  } as any
  Object.defineProperties(res, {
    $list: {
      value: Object.keys(map).reduce<EnumResult<T>['$list']>((res, key) => {
        res.push({
          label: key,
          value: map[key] as any,
        })
        return res
      }, [] as any),
      enumerable: false,
      writable: false,
      configurable: false,
    },
    $buildList: {
      value: (keys => {
        const labelMap = Array.isArray(keys) ? undefined : keys
        keys = Array.isArray(keys) ? keys : Object.keys(keys)
        return Object.keys(map).reduce<ReturnType<EnumResult<T>['$buildList']>>(
          (res, key) => {
            if (keys.includes(key)) {
              res.push({
                label: labelMap?.[key] || key,
                value: map[key] as any,
              })
            }
            return res
          },
          [] as any,
        ) as any
      }) as EnumResult<T>['$buildList'],
      enumerable: false,
      writable: false,
      configurable: false,
    },
    $is: {
      value: ((value, keys) => {
        return castArray(keys).some(
          key => value === key || value === res[key as any],
        )
      }) as EnumResult<T>['$is'],
      enumerable: false,
      writable: false,
      configurable: false,
    },
  })
  return res
}