• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Haixing-Hu / js-common-decorator / 214d62ca-32aa-44ba-af6c-f0078b9541b4

29 Oct 2024 06:30PM UTC coverage: 82.353% (-0.7%) from 83.06%
214d62ca-32aa-44ba-af6c-f0078b9541b4

push

circleci

Haixing-Hu
build: upgrade dependencies and rebuild the library

473 of 581 branches covered (81.41%)

Branch coverage included in aggregate %.

633 of 762 relevant lines covered (83.07%)

174.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.84
/src/enum.js
1
////////////////////////////////////////////////////////////////////////////////
2
//
3
//    Copyright (c) 2022 - 2023.
4
//    Haixing Hu, Qubit Co. Ltd.
5
//
6
//    All rights reserved.
7
//
8
////////////////////////////////////////////////////////////////////////////////
9
import { registerCloneHook } from '@haixing_hu/clone';
10
import isEnumerator from './impl/utils/is-enumerator';
11
import classMetadataCache from './impl/class-metadata-cache';
12
import setClassMetadata from './impl/utils/set-class-metadata';
13
import { KEY_CLASS_CATEGORY } from './impl/metadata-keys';
14
import defineEnumerator from './impl/enum/define-enumerator';
15
import ofValueImpl from './impl/enum/of-value-impl';
16
import valuesImpl from './impl/enum/values-impl';
17
import ofCodeImpl from './impl/enum/of-code-impl';
18
import ofNameImpl from './impl/enum/of-name-impl';
19
import ofImpl from './impl/enum/of-impl';
20

21
/**
22
 * This decorator is used to decorate an enumeration class.
23
 *
24
 * It must decorate a class.
25
 *
26
 * An enumeration class is a class whose instances are enumerators. An enumerator
27
 * is an object with the following properties:
28
 * - `value`:the value of the enumerator, which is exactly the name of the
29
 *   static field of the enumeration class that corresponds to the enumerator.
30
 * - `name`: the display name of the enumerator, which could be specified
31
 *   by the default string or object value of the static field of the
32
 *   enumeration class that corresponds to the enumerator. It the default value
33
 *   is not specified, the name of the enumerator is the same as its value.
34
 * - `i18n`: the i18n key of the enumerator, which is an optional property. It
35
 *   could be specified by the default object value of the static field of the
36
 *   enumeration class that corresponds to the enumerator. If this property is
37
 *   specified, the `name` property will be transformed to a `getter`, which will
38
 *   get the i18n value of the enumerator from the i18n resource bundle.
39
 * - `code`: the code of the enumerator, which is an optional property. It could
40
 *   be specified by the default object value of the static field of the
41
 *   enumeration class that corresponds to the enumerator.
42
 * - other properties: other properties of the enumerator could be specified
43
 *   by the default object value of the static field of the enumeration class
44
 *   that corresponds to the enumerator.
45
 *
46
 * An enumerator also has the following prototype method:
47
 * - `toString()`: returns the value of the enumerator.
48
 * - `toJSON()`: also returns the value of the enumerator.
49
 *
50
 * The enumeration class will have the following static methods:
51
 * - `values()`: returns the array of all enumerators of this enumeration class.
52
 * - `ofValue(value): returns the enumerator whose value is `value`, or
53
 *   `undefined` if no such enumerator exists.
54
 * - `hasValue(value): returns `true` if there is an enumerator whose value is
55
 *   `value`, or `false` otherwise.
56
 * - `ofName(name): returns the enumerator whose name is `name`, or `undefined`
57
 *   if no such enumerator exists.
58
 * - `hasName(name): returns `true` if there is an enumerator whose name is
59
 *   `name`, or `false` otherwise.
60
 * - `ofCode(code): returns the enumerator whose code is `code`, or `undefined`
61
 *   if no such enumerator exists.
62
 * - `hasCode(code): returns `true` if there is an enumerator whose code is
63
 *   `code`, or `false` otherwise.
64
 * - `of(expr): returns the enumerator corresponding to the specified expression.
65
 *   The expression could be one of the following:
66
 *      - an enumerator of this enumeration class;
67
 *      - or the value of an enumerator of this enumeration class;
68
 *      - or the name of an enumerator of this enumeration class;
69
 *      - or the code of an enumerator of this enumeration class.
70
 * - `has(value): returns `true` if there is an enumerator corresponding to the
71
 *   specified expression; or `false` otherwise. The expression could be one of
72
 *   the following:
73
 *      - an enumerator of this enumeration class;
74
 *      - or the value of an enumerator of this enumeration class;
75
 *      - or the name of an enumerator of this enumeration class;
76
 *      - or the code of an enumerator of this enumeration class.
77
 *
78
 * ##### Usage example:
79
 *
80
 * ```js
81
 * @Enum
82
 * class Gender {
83
 *   static MALE = 'Male';
84
 *   static FEMALE = 'Female';
85
 * }
86
 * ```
87
 * The above code is equivalent to the following code:
88
 * ```js
89
 * class Gender {
90
 *   static MALE = Object.freeze(new Gender('MALE', 'Male'));
91
 *
92
 *   static FEMALE = Object.freeze(new Gender('FEMALE', 'Female'));
93
 *
94
 *   static values() {
95
 *     return [ Gender.MALE, Gender.FEMALE ];
96
 *   }
97
 *
98
 *   static ofValue(value) {
99
 *     switch (value) {
100
 *     case 'MALE':
101
 *       return Gender.MALE;
102
 *     case 'FEMALE':
103
 *       return Gender.FEMALE;
104
 *     default:
105
 *       return undefined;
106
 *     }
107
 *   }
108
 *
109
 *   static hasValue(value) {
110
 *     return Gender.ofValue(value) !== undefined;
111
 *   }
112
 *
113
 *   static ofName(name) {
114
 *     return Gender.values().find((e) => e.name === name);
115
 *   }
116
 *
117
 *   static hasName(name) {
118
 *     return Gender.ofName(name) !== undefined;
119
 *   }
120
 *
121
 *   static ofCode(code) {
122
 *     return Gender.values().find((e) => e.code === code);
123
 *   }
124
 *
125
 *   static hasCode(code) {
126
 *     return Gender.ofCode(code) !== undefined;
127
 *   }
128
 *
129
 *   static of(expr) {
130
 *     if (expr instanceof Gender) {
131
 *       return expr;
132
 *     } else {
133
 *       return Gender.ofValue(expr) ?? Gender.ofName(expr) ?? Gender.ofCode(expr);
134
 *     }
135
 *   }
136
 *
137
 *   static has(expr) {
138
 *     return Gender.of(expr) !== undefined;
139
 *   }
140
 *
141
 *   constructor(value, name) {
142
 *     this.value = value;
143
 *     this.name = name;
144
 *   }
145
 *
146
 *   toString() {
147
 *     return this.value;
148
 *   }
149
 *
150
 *   toJSON() {
151
 *     return this.value;
152
 *   }
153
 * }
154
 * ```
155
 *
156
 * The static fields of the enumeration class could also be specified as objects,
157
 * which will be used to initialize the enumerators. For example:
158
 * ```js
159
 * @Enum
160
 * class Gender {
161
 *   static MALE = { name: 'Male', i18n: 'i18n.gender.male', code: '001', data: { value: 0 } };
162
 *
163
 *   static FEMALE = { name: 'Female', i18n: 'i18n.gender.female', code: '002', data: { value: 1 } };
164
 * }
165
 * ```
166
 * The above code is equivalent to the following code:
167
 * ```js
168
 * class Gender {
169
 *   static MALE = Object.freeze(new Gender('MALE', 'Male',
170
 *      { i18n: 'i18n.gender.male', code: '001', data: {value: 0 } }));
171
 *
172
 *   static FEMALE = Object.freeze(new Gender('FEMALE', 'Female',
173
 *      { i18n: 'i18n.gender.female', code: '002', data: {value: 1 } }));
174
 *
175
 *   ...
176
 *
177
 *   constructor(value, name, payload) {
178
 *     this.value = value;
179
 *     this.name = name;
180
 *     Object.assign(this, payload);
181
 *   }
182
 *
183
 *   ...
184
 * }
185
 * ```
186
 * Note that the enumerator in the above `Gender` class has a `code`, `i18n`
187
 * and `data` properties. Since it has `i18n` property which specifies the i18n
188
 * key of the enumerator in the resource bundle, the `name` property of the
189
 * enumerator will be transformed to a `getter` which will get the i18n value
190
 * corresponding to the i18n key from the i18n resource bundle.
191
 *
192
 * The enumerators can also be defined without default values, for example:
193
 * ```js
194
 * @Enum
195
 * class Gender {
196
 *   static MALE;
197
 *   static FEMALE;
198
 * }
199
 * ```
200
 * The above code is equivalent to the following code:
201
 * ```js
202
 * class Gender {
203
 *   static MALE = Object.freeze(new Gender('MALE'));
204
 *
205
 *   static FEMALE = Object.freeze(new Gender('FEMALE'));
206
 *
207
 *   ...
208
 *
209
 *   constructor(value) {
210
 *     this.value = value;
211
 *     this.name = value;
212
 *   }
213
 *
214
 *   ...
215
 * }
216
 * ```
217
 * That is, the name of the enumerator is exactly its value.
218
 *
219
 * @param {function} Class
220
 *     The constructor of the class being decorated.
221
 * @param {object} context
222
 *     The context object containing information about the class being decorated.
223
 * @author Haixing Hu
224
 */
225
function Enum(Class, context) {
226
  if (context === null || typeof context !== 'object') {
35!
227
    throw new TypeError('The context must be an object.');
×
228
  }
229
  if (typeof Class !== 'function' || context.kind !== 'class') {
35!
230
    throw new TypeError('The `@Enum` can only decorate a class.');
×
231
  }
232
  // put the context.metadata to the cache
233
  classMetadataCache.set(Class, context.metadata);
35✔
234
  // The category of the class modified by `@Enum` is set to 'enum'
235
  setClassMetadata(Class, KEY_CLASS_CATEGORY, 'enum');
35✔
236
  // Loops through all static fields of the `Class` and defines them as enumerators
237
  context.addInitializer(() => {
35✔
238
    // NOTE that we must perform the following code in the `context.addInitializer()`
239
    // function, since the decorator @Enum is applied **BEFORE** the static fields
240
    // of the class were defined, but the `context.addInitializer()` function is
241
    // called **AFTER** the static fields of the class were defined.
242
    // see https://github.com/tc39/proposal-decorators#adding-initialization-logic-with-addinitializer
243
    Object.keys(Class).forEach((field) => {
35✔
244
      if (typeof Class[field] !== 'function') {
584✔
245
        defineEnumerator(Class, field);
269✔
246
      }
247
    });
248
    // freeze the enumeration class
249
    Object.freeze(Class);
34✔
250
  });
251
  // Add prototype method toString()
252
  Class.prototype.toString = function toString() {
35✔
253
    return this.value;
6✔
254
  };
255
  // Add prototype method toJSON()
256
  Class.prototype.toJSON = function toJSON() {
35✔
257
    return this.value;
15✔
258
  };
259

260
  /**
261
   * Returns the array of all enumerators of this enumeration class.
262
   *
263
   * @returns {Array<object>}
264
   *     The array of all enumerators of this enumeration class.
265
   */
266
  Class.values = function values() {
35✔
267
    return valuesImpl(Class);
1✔
268
  };
269

270
  /**
271
   * Returns the enumerator of this enumeration class which has the specified
272
   * value.
273
   *
274
   * @param {string} value
275
   *     The value of the enumerator to be returned. If the value is a primitive
276
   *     string or a `String` object, it will be trimmed and converted to an
277
   *     uppercase string.
278
   * @returns {undefined|Class}
279
   *     The enumerator of this enumeration class which has the specified value;
280
   *     or `undefined` if there is no such enumerator.
281
   * @author Haixing Hu
282
   */
283
  Class.ofValue = function ofValue(value) {
35✔
284
    return ofValueImpl(Class, value);
106✔
285
  };
286

287
  /**
288
   * Tests whether there is an enumerator of this enumeration class which has
289
   * the specified value.
290
   *
291
   * @param {string} value
292
   *     The value of the enumerator to be tested. If the value is a primitive
293
   *     string or a `String` object, it will be trimmed and converted to an
294
   *     uppercase string.
295
   * @returns {boolean}
296
   *     `true` if there is an enumerator of this enumeration class which has
297
   *     the specified value; `false` otherwise.
298
   * @author Haixing Hu
299
   */
300
  Class.hasValue = function hasValue(value) {
35✔
301
    return (ofValueImpl(Class, value) !== undefined);
35✔
302
  };
303

304
  /**
305
   * Returns the enumerator of this enumeration class which has the specified
306
   * name.
307
   *
308
   * @param {string} name
309
   *     The name of the enumerator to be returned.  If the name is a primitive
310
   *     string or a `String` object, it will be trimmed.
311
   * @returns {undefined|Class}
312
   *     The enumerator of this enumeration class which has the specified name;
313
   *     or `undefined` if there is no such enumerator.
314
   * @author Haixing Hu
315
   */
316
  Class.ofName = function ofName(name) {
35✔
317
    return ofNameImpl(Class, name);
13✔
318
  };
319

320
  /**
321
   * Tests whether there is an enumerator of this enumeration class which has
322
   * the specified name.
323
   *
324
   * @param {string} name
325
   *     The specified name. If the name is a primitive string or a `String`
326
   *     object, it will be trimmed.
327
   * @returns {boolean}
328
   *     `true` if there is an enumerator of this enumeration class which has
329
   *     the specified name; `false` otherwise.
330
   * @author Haixing Hu
331
   */
332
  Class.hasName = function hasName(name) {
35✔
333
    return (ofNameImpl(Class, name) !== undefined);
13✔
334
  };
335

336
  /**
337
   * Returns the enumerator of this enumeration class which has the specified
338
   * code.
339
   *
340
   * @param {string} code
341
   *     The code of the enumerator to be returned. If the code is a primitive
342
   *     string or a `String` object, it will be trimmed.
343
   * @returns {undefined|Class}
344
   *     The enumerator of this enumeration class which has the specified code;
345
   *     or `undefined` if there is no such enumerator.
346
   * @author Haixing Hu
347
   * @private
348
   */
349
  Class.ofCode = function ofCode(code) {
35✔
350
    return ofCodeImpl(Class, code);
13✔
351
  };
352

353
  /**
354
   * Tests whether there is an enumerator of this enumeration class which has
355
   * the specified code.
356
   *
357
   * @param {string} code
358
   *     The specified code. If the code is a primitive string or a `String`
359
   *     object, it will be trimmed.
360
   * @returns {boolean}
361
   *     `true` if there is an enumerator of this enumeration class which has
362
   *     the specified code; `false` otherwise.
363
   * @author Haixing Hu
364
   */
365
  Class.hasCode = function hasCode(code) {
35✔
366
    return (ofCodeImpl(Class, code) !== undefined);
13✔
367
  };
368

369
  /**
370
   * Returns the enumerator of this enumeration class corresponding to the
371
   * specified expression.
372
   *
373
   * The expression could be one of the following:
374
   * - an enumerator of this enumeration class;
375
   * - or the value of an enumerator of this enumeration class;
376
   * - or the name of an enumerator of this enumeration class;
377
   * - or the code of an enumerator of this enumeration class.
378
   *
379
   * @param {object|string} expr
380
   *     The specified value, which could be an enumerator object, the value of
381
   *     an enumerator, the name of an enumerator, or the code of an enumerator.
382
   * @returns {undefined|object}
383
   *     The enumerator corresponding to the specified enumerator, value, name
384
   *     or code; or `undefined` if there is no such enumerator.
385
   * @author Haixing Hu
386
   */
387
  Class.of = function of(expr) {
35✔
388
    return ofImpl(Class, expr);
50✔
389
  };
390

391
  /**
392
   * Tests whether there is an enumerator of this enumeration class corresponding
393
   * to the specified expression.
394
   *
395
   * The expression could be one of the following:
396
   * - an enumerator of this enumeration class;
397
   * - or the value of an enumerator of this enumeration class;
398
   * - or the name of an enumerator of this enumeration class;
399
   * - or the code of an enumerator of this enumeration class.
400
   *
401
   * @param {object|string} expr
402
   *     The specified expression, which could be an enumerator object, the value
403
   *     of an enumerator, the name of an enumerator, or the code of an enumerator.
404
   * @returns {boolean}
405
   *     `true` if there is an enumerator corresponding to the specified
406
   *     enumerator, value, name or code; `false` otherwise.
407
   * @author Haixing Hu
408
   */
409
  Class.has = function has(expr) {
35✔
410
    return (ofImpl(Class, expr) !== undefined);
33✔
411
  };
412
}
413

414
// Globally register the clone hook of enumerators.
415
registerCloneHook((info, obj) => {
35✔
416
  if (isEnumerator(obj)) {
542✔
417
    return obj;     // the enumerator should not be cloned
10✔
418
  }
419
  return null;
532✔
420
});
421

422
export default Enum;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc