Skip to content

Commit 08f9a01

Browse files
coroiuaudreyality
andauthored
Recommend same type and value name for enums (#607)
* feat: recommend same type and value names Use the same name for the type and the value allows us to emulate regular Enums much better. * feat: merge with @audreyality version from #605 Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com> * fix: remove broken link * fix: change name -> value Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com> --------- Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com>
1 parent cb1cecf commit 08f9a01

File tree

1 file changed

+75
-13
lines changed

1 file changed

+75
-13
lines changed

docs/contributing/code-style/enums.md

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,87 @@
1-
# TypeScript Const Enums
1+
# Avoid TypeScript Enums
22

3-
We don't use Typescript enums because they are not fully type-safe and can cause surprises. Instead
4-
of using enums, we use constant objects.
3+
TypeScript enums are not fully type-safe and can [cause surprises][enum-surprises]. Your code should
4+
use [constant objects][constant-object-pattern] instead of introducing a new enum.
55

66
## Our Recommended Approach
77

8+
- Use the same name for your type- and value-declaration.
9+
- Use `type` to derive type information from the const object.
10+
- Create utilities to convert and identify enums modelled as primitives.
11+
12+
:::tip
13+
14+
This pattern should simplify the usage of your new objects, improve type safety in files that have
15+
adopted TS-strict, and make transitioning an enum to a const object much easier.
16+
17+
:::
18+
19+
### Example
20+
21+
Given the following enum:
22+
823
```ts
9-
export const PasskeyActions = {
10-
Register: "register",
11-
Authenticate: "authenticate",
24+
export enum CipherType = {
25+
Login: 1,
26+
SecureNote: 2,
27+
Card: 3,
28+
Identity: 4,
29+
SshKey: 5,
30+
};
31+
```
32+
33+
You can redefine it as an object like so:
34+
35+
```ts
36+
const _CipherType = {
37+
Login: 1,
38+
SecureNote: 2,
39+
Card: 3,
40+
Identity: 4,
41+
SshKey: 5,
1242
} as const;
1343

14-
export type PasskeyActionValue = (typeof PasskeyActions)[keyof typeof PasskeyActions];
44+
type _CipherType = typeof _CipherType;
45+
46+
export type CipherType = _CipherType[keyof _CipherType];
47+
export const CipherType: Readonly<{ [K in keyof typeof _CipherType]: CipherType }> =
48+
Object.freeze(_CipherType);
49+
```
50+
51+
And use it like so:
52+
53+
```ts
54+
// Can be imported together
55+
import { CipherType } from "./cipher-type";
1556

16-
declare function usePasskeyAction(action: PasskeyActionValue): void;
57+
// Used as a type
58+
function doSomething(type: CipherType) {}
1759

18-
usePasskey(PasskeyActions.Register); // ✅ Valid
19-
usePasskey(0); // ❌ Invalid
60+
// And used as a value (just like a regular `enum`)
61+
doSomething(CipherType.Card);
2062
```
2163

22-
## Resources Used
64+
The following utilities may assist introspection:
65+
66+
```ts
67+
import { CipherType } from "./cipher-type";
68+
69+
const namesByCipherType = new Map<CipherType, keyof CipherType>(
70+
Array.fromEntries(Object.entries(CipherType), ([k, v]) => [v, k]),
71+
);
72+
73+
export function isCipherType(value: number): value is CipherType {
74+
return namesByCipherType.has(value);
75+
}
76+
77+
export function asCipherType(value: number): CipherType | undefined {
78+
return isCipherType(value) ? value : undefined;
79+
}
80+
81+
export function nameOfCipherType(value: CipherType): keyof CipherType | undefined {
82+
return namesByCipherType.get(value);
83+
}
84+
```
2385

24-
- https://dev.to/ivanzm123/dont-use-enums-in-typescript-they-are-very-dangerous-57bh
25-
- https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums
86+
[enum-surprises]: https://dev.to/ivanzm123/dont-use-enums-in-typescript-they-are-very-dangerous-57bh
87+
[constant-object-pattern]: https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums

0 commit comments

Comments
 (0)