Skip to content

Commit 9fd0b79

Browse files
committed
feat: implement enum.values and enum.labels
1 parent 44e2d94 commit 9fd0b79

19 files changed

+311
-112
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ const MyEnum = Enum({
5353
- `enums.filters`
5454
- `enums.valuesEnum`
5555
- Please use the new methods `Enum.items`, `Enum.toSelect`, `Enum.toMenu`, `Enum.toFilter`, and `Enum.toValueMap` instead, which are introduced since `v2.1.0`.
56+
- `ENUM_COLLECTION` symbol is renamed to `IS_ENUM_COLLECTION`.
57+
- `ENUM_ITEM` symbol is renamed to `IS_ENUM_ITEM`.
58+
- `ENUM_ITEMS` symbol is renamed to `IS_ENUM_ITEMS`.
5659
- `EnumValuesArray` interface is renamed to `EnumItemsArray`.
5760
- `IEnumValues` interface is renamed to `IEnumItems`.
5861

e2e/specs/enum-values.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import playwright from '../../test/engines/playwright';
2-
import testEnumValues from '../../test/test-suites/enum-values';
2+
import testEnumValues from '../../test/test-suites/enum-items';
33

44
testEnumValues(playwright);

enum-plus-v3.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
- [x] Remove deprecated `enum.menus`
99
- [x] Remove deprecated `enum.filters`
1010
- [x] Remove deprecated `enum.valuesEnum`
11-
- [ ] Change the behavior of `enum.values`, now it returns an array of the member raw values. Use `enum.items` for the old behavior.
12-
- [ ] Add `enum.labels` property, which returns an readonly array of the member labels.
11+
- [x] Change the behavior of `enum.values`, now it returns an array of the member raw values. Use `enum.items` for the old behavior.
12+
- [x] Add `enum.labels` property, which returns an readonly array of the member labels.
1313
- [ ] Add `enum.find` method, which allows searching for enum items by specific field, including _meta_ fields (i.e. custom fields).
1414
- [ ] Add `enum.meta` object to aggregate all custom fields defined in the enum. The keys are the field names, and values are the raw values of each field. It's a good way of accessing custom fields without iterating through the enum items.
1515
- [x] Add `enum.toList`, method which is an alternative of `toSelect``toMenu``toFilter`. The latter methods are moving out of the core library and will be available as plugins.
1616
- [ ] Add `enum.toMap` as an alternative of `enum.toValueMap`.
17+
<!-- - [ ] 增加isEnum方法,为instanceof增加断言 -->
18+
- [ ] Add `Enum.isEnum` method to check if an object is an instance of `Enum`.
19+
- [ ] Add type assertion for `instanceof` check of EnumCollection.
1720
- [x] Simplify the enum initialization that no longer requires `as const` assertion. _by @otomad_
1821
- [x] Release the `UMD` module format of `enum-plus` in `umd` folder.
1922
- [x] Reuse one copy of testing code for both `Jest` and `e2e` testing.

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = {
77
collectCoverage: true,
88
collectCoverageFrom: ['src/**/*.{ts,tsx}'],
99
coverageReporters: ['json', 'lcov', 'text', 'clover'],
10-
coveragePathIgnorePatterns: ['src/types.ts', 'src/localize-interface.ts', 'src/extension.ts'],
10+
coveragePathIgnorePatterns: ['src/types.ts', 'src/localize-interface.ts', 'src/extension.d.ts'],
1111
moduleNameMapper: {
1212
'^@enum-plus/(.*)$': '<rootDir>/src/$1',
1313
'^@enum-plus': '<rootDir>/src',

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@
115115
],
116116
"scripts": {
117117
"build": "run-p build:*",
118-
"build:es": "run-s task:build-es task:add-umd-banner task:add-es-extensions",
119-
"build:es-legacy": "cross-env LEGACY=1 run-s task:build-es-legacy task:add-umd-banner task:add-es-legacy-extensions",
118+
"build:es": "run-s task:build-es task:update-es-enum task:add-es-extensions task:add-umd-banner",
119+
"build:es-legacy": "cross-env LEGACY=1 run-s task:build-es-legacy task:update-es-legacy-enum task:add-umd-banner task:add-es-legacy-extensions",
120120
"build:lib": "run-s ts2lib task:copy-lib task:copy-dts task:types-legacy",
121121
"build:tses": "run-s ts2es task:add-tses-extensions",
122122
"e2e": "run-s task:bundle-e2e task:run-e2e",
@@ -141,6 +141,10 @@
141141
"task:run-e2e-debug": "playwright test --debug",
142142
"task:run-e2e-ui": "playwright test --ui",
143143
"task:types-legacy": "tsx scripts/gen-legacy-type.ts",
144+
"task:update-es-enum": "tsx scripts/update-enum-ts.ts es",
145+
"task:update-es-legacy-enum": "tsx scripts/update-enum-ts.ts es-legacy",
146+
"task:update-lib-enum": "tsx scripts/update-enum-ts.ts lib",
147+
"task:update-tses-enum": "tsx scripts/update-enum-ts.ts tses",
144148
"test": "npm run task:jest",
145149
"test-all": "run-s task:jest test-node-cjs test-node-esm e2e",
146150
"test-node-cjs": "run-s ts2lib task:jest-cjs",

scripts/update-enum-ts.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { readFileSync, writeFileSync } from 'fs';
2+
import { join } from 'path';
3+
4+
const directory = process.argv[2];
5+
/*
6+
* This script adds .js extensions to `import` paths in output javascript files.
7+
*
8+
* Have tried the babel-plugin-add-import-extension plugin, but encountered "Maximum call stack size exceeded" error.
9+
* So changed to a post-build processing approach instead.
10+
*/
11+
12+
if (directory) {
13+
processEnumTs(directory);
14+
}
15+
16+
function processEnumTs(dir: string) {
17+
const enumPath = join(dir, 'enum.d.ts');
18+
let content = readFileSync(enumPath, 'utf8');
19+
content = content.replace(/\/\/\/ <reference types=["']([^"']+)["'] \/>/g, '').trimStart();
20+
content = `import './extension';\n` + content;
21+
writeFileSync(enumPath, content);
22+
}

src/enum-collection.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import type {
1717
StandardEnumItemInit,
1818
ValueTypeFromSingleInit,
1919
} from './types';
20-
import { ENUM_COLLECTION, ITEMS, KEYS } from './utils';
20+
import type { IS_ENUM_ITEMS } from './utils';
21+
import { IS_ENUM_COLLECTION, ITEMS, KEYS, LABELS, VALUES } from './utils';
2122

2223
/**
2324
* - **EN:** Enum collection extension base class, used to extend the Enums
@@ -41,16 +42,16 @@ export class EnumCollectionClass<
4142
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
4243
>
4344
extends EnumExtensionClass<T, K, V>
44-
implements IEnumItems<T, K, V>
45+
implements Omit<IEnumItems<T, K, V>, typeof IS_ENUM_ITEMS>
4546
{
46-
private _options: EnumItemOptions | undefined;
47+
private __options__: EnumItemOptions | undefined;
4748
readonly items!: EnumItemsArray<T, K, V>;
4849
readonly keys!: K[];
4950
/**
5051
* - **EN:** A boolean value indicates that this is an enum collection instance.
5152
* - **CN:** 布尔值,表示这是一个枚举集合实例
5253
*/
53-
readonly [ENUM_COLLECTION] = true;
54+
readonly [IS_ENUM_COLLECTION] = true;
5455
[Symbol.hasInstance](this: EnumCollectionClass<T, K, V>, instance: unknown): boolean {
5556
// intentionally use == to support both number and string format value
5657
return this.items.some(
@@ -69,45 +70,65 @@ export class EnumCollectionClass<
6970
* set.
7071
*/
7172
get name(): string | undefined {
72-
const localize = this._options?.localize ?? localizer.localize;
73+
const localize = this.__options__?.localize ?? localizer.localize;
7374
if (typeof localize === 'function') {
74-
return localize(this._options?.name);
75+
return localize(this.__options__?.name);
7576
}
76-
return this._options?.name;
77+
return this.__options__?.name;
7778
}
7879

7980
constructor(init: T = {} as T, options?: EnumItemOptions) {
8081
super();
81-
this._options = options;
82+
this.__options__ = options;
83+
84+
// Generate keys array
8285
// exclude number keys with a "reverse mapping" value, it means those "reverse mapping" keys of number enums
8386
const keys = Object.keys(init).filter(
8487
(k) => !(/^-?\d+$/.test(k) && k === `${init[init[k as K] as K] ?? ''}`)
8588
) as K[];
8689
const parsed = keys.map((key) => parseEnumItem<EnumItemInit<V>, K, V>(init[key], key));
8790
keys.forEach((key, index) => {
8891
const { value } = parsed[index];
89-
// @ts-expect-error: because of dynamically define property
92+
// @ts-expect-error: because of dynamic property
9093
this[key] = value;
9194
});
9295
Object.freeze(keys);
9396
// @ts-expect-error: because use KEYS to avoid naming conflicts in case of 'keys' field name is taken
9497
this[Object.keys(init).some((k) => k === 'keys') ? KEYS : 'keys'] = keys;
9598

96-
// Build enum item data
99+
// Generate values array
100+
const values = parsed.map((item) => item.value);
101+
Object.freeze(values);
102+
// @ts-expect-error: because use VALUES to avoid naming conflicts in case of 'values' field name is taken
103+
this[Object.keys(init).some((k) => k === 'values') ? VALUES : 'values'] = values;
104+
105+
// Generate enum items array
97106
const items = new EnumItemsArray<T, K, V>(
98107
init,
99-
this._options,
108+
this.__options__,
100109
...keys.map((key, index) => {
101110
const { value, label } = parsed[index];
102-
return new EnumItemClass<T[K], K, V>(key, value, label, init[key], this._options);
111+
return new EnumItemClass<T[K], K, V>(key, value, label, init[key], this.__options__);
103112
})
104113
);
114+
Object.freeze(items);
105115
// @ts-expect-error: because use ITEMS to avoid naming conflicts in case of 'items' field name is taken
106116
this[Object.keys(init).some((k) => k === 'items') ? ITEMS : 'items'] = items;
107117

118+
// Generate labels array
119+
Object.defineProperty(this, Object.keys(init).some((k) => k === 'labels') ? LABELS : 'labels', {
120+
enumerable: true,
121+
configurable: false,
122+
get: () => {
123+
// Cannot save to static array because labels may be localized contents
124+
// Should not use `items` in the closure because the getter function cannot be fully serialized
125+
return ((this as unknown as { [ITEMS]: EnumItemsArray<T, K, V> })[ITEMS] ?? this.items).map(
126+
(item) => item.label
127+
);
128+
},
129+
});
130+
108131
Object.freeze(this);
109-
Object.freeze(this.items);
110-
Object.freeze(this.keys);
111132
}
112133

113134
label<KV extends V | K | NonNullable<PrimitiveOf<V>> | NonNullable<PrimitiveOf<K>> | undefined>(keyOrValue: KV) {

src/enum-item.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { localizer } from './localize';
22
import type { EnumItemInit, EnumKey, EnumValue, LocalizeInterface, ValueTypeFromSingleInit } from './types';
3-
import { ENUM_ITEM } from './utils';
3+
import { IS_ENUM_ITEM } from './utils';
44

55
/**
66
* Enum item class
@@ -44,7 +44,7 @@ export class EnumItemClass<
4444
* - **EN:** A boolean value indicates that this is an enum item instance.
4545
* - **CN:** 布尔值,表示这是一个枚举项实例
4646
*/
47-
readonly [ENUM_ITEM] = true;
47+
readonly [IS_ENUM_ITEM] = true;
4848
/**
4949
* Auto convert to a correct primitive type. This method is called when the object is used in a
5050
* context that requires a primitive value.
@@ -120,8 +120,8 @@ export class EnumItemClass<
120120
enumerable: true,
121121
configurable: false,
122122
},
123-
[ENUM_ITEM]: {
124-
value: this[ENUM_ITEM],
123+
[IS_ENUM_ITEM]: {
124+
value: this[IS_ENUM_ITEM],
125125
writable: false,
126126
enumerable: true,
127127
configurable: false,

src/enum-items.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
ValueMap,
1313
ValueTypeFromSingleInit,
1414
} from './types';
15-
import { ENUM_ITEMS } from './utils';
15+
import { IS_ENUM_ITEMS } from './utils';
1616

1717
/**
1818
* Enum items array, mostly are simple wrappers for EnumCollectionClass
@@ -33,12 +33,12 @@ export class EnumItemsArray<
3333
extends Array<EnumItemClass<T[K], K, V>>
3434
implements IEnumItems<T, K, V>
3535
{
36-
private _raw: T;
36+
private __raw__: T;
3737
/**
3838
* - **EN:** A boolean value indicates that this is an enum items array.
3939
* - **CN:** 布尔值,表示这是一个枚举项数组
4040
*/
41-
readonly [ENUM_ITEMS] = true;
41+
readonly [IS_ENUM_ITEMS] = true;
4242

4343
/**
4444
* Instantiate an enum items array
@@ -50,7 +50,7 @@ export class EnumItemsArray<
5050
*/
5151
constructor(raw: T, options: EnumItemOptions | undefined, ...items: EnumItemClass<T[K], K, V>[]) {
5252
super(...items);
53-
this._raw = raw;
53+
this.__raw__ = raw;
5454
}
5555

5656
label<KV extends V | K | NonNullable<PrimitiveOf<V>> | NonNullable<PrimitiveOf<K>> | undefined>(
@@ -99,11 +99,11 @@ export class EnumItemsArray<
9999
raw<IK extends EnumValue>(value?: IK | unknown): T | T[K] | T[FindEnumKeyByValue<T, IK>] | undefined {
100100
if (value == null) {
101101
// Return the original initialization object
102-
return this._raw;
102+
return this.__raw__;
103103
} else {
104-
if (Object.keys(this._raw).some((k) => k === (value as string))) {
104+
if (Object.keys(this.__raw__).some((k) => k === (value as string))) {
105105
// Find by key
106-
return this._raw[value as K];
106+
return this.__raw__[value as K];
107107
}
108108
// Find by value
109109
const itemByValue = this.find((i) => i.value === value);
@@ -215,6 +215,11 @@ export interface IEnumItems<
215215
K extends EnumKey<T> = EnumKey<T>,
216216
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
217217
> {
218+
/**
219+
* - **EN:** A boolean value indicates that this is an enum items array.
220+
* - **CN:** 布尔值,表示这是一个枚举项数组
221+
*/
222+
[IS_ENUM_ITEMS]: true;
218223
/**
219224
* - **EN:** The enum collection name, supports localization.
220225
*

src/enum.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
LocalizeInterface,
1313
ValueTypeFromSingleInit,
1414
} from './types';
15-
import type { ITEMS, KEYS } from './utils';
15+
import type { IS_ENUM_COLLECTION, ITEMS, KEYS, LABELS, VALUES } from './utils';
1616

1717
export interface EnumInterface {
1818
/**
@@ -118,6 +118,12 @@ export type IEnum<
118118
V extends EnumValue = ValueTypeFromSingleInit<T[K], K>,
119119
> = IEnumItems<T, K, V> &
120120
EnumExtension<T, K, V> & {
121+
/**
122+
* - **EN:** A boolean value indicates that this is an enum collection instance.
123+
* - **CN:** 布尔值,表示这是一个枚举集合实例
124+
*/
125+
[IS_ENUM_COLLECTION]: true;
126+
} & {
121127
// Add enum item values, just like native enums
122128
[key in K]: ValueTypeFromSingleInit<T[key], key, T[K] extends number | undefined ? number : key>;
123129
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -165,6 +171,52 @@ export type IEnum<
165171
* > 仅支持 `ReadonlyArray<T>` 中的只读方法,不支持push、pop等任何修改的方法
166172
*/
167173
readonly keys: K[];
174+
}) &
175+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
176+
(T extends { values: any }
177+
? {
178+
/**
179+
* - **EN:** Alias for the `values` array, when any enum key conflicts with `values`, you can
180+
* access all enum values through this alias
181+
* - **CN:** `values`数组的别名,当任何枚举的key与`values`冲突时,可以通过此别名访问所有枚举项的values
182+
*/
183+
readonly [VALUES]: V[];
184+
}
185+
: {
186+
/**
187+
* - **EN:** Get all values of the enumeration items as an array
188+
*
189+
* > Only supports read-only methods in `ReadonlyArray<T>`, does not support push, pop, and
190+
* > any modification methods
191+
*
192+
* - **CN:** 获取枚举项的全部values列表
193+
*
194+
* > 仅支持 `ReadonlyArray<T>` 中的只读方法,不支持push、pop等任何修改的方法
195+
*/
196+
readonly values: V[];
197+
}) &
198+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
199+
(T extends { labels: any }
200+
? {
201+
/**
202+
* - **EN:** Alias for the `labels` array, when any enum key conflicts with `labels`, you can
203+
* access all enum labels through this alias
204+
* - **CN:** `labels`数组的别名,当任何枚举的key与`labels`冲突时,可以通过此别名访问所有枚举项的labels
205+
*/
206+
readonly [LABELS]: string[];
207+
}
208+
: {
209+
/**
210+
* - **EN:** Get all labels of the enumeration items as an array
211+
*
212+
* > Only supports read-only methods in `ReadonlyArray<T>`, does not support push, pop, and
213+
* > any modification methods
214+
*
215+
* - **CN:** 获取枚举项的全部labels列表
216+
*
217+
* > 仅支持 `ReadonlyArray<T>` 中的只读方法,不支持push、pop等任何修改的方法
218+
*/
219+
readonly labels: string[];
168220
});
169221

170222
export const Enum = (<

0 commit comments

Comments
 (0)