@@ -36,42 +36,35 @@ avoids both code generation and type inconsistencies.
3636
3737``` ts
3838// declare the raw data and reduce repetition with an internal type
39- const _CipherType = {
39+ const CipherType = Object . freeze ( {
4040 Login: 1 ,
4141 SecureNote: 2 ,
4242 Card: 3 ,
4343 Identity: 4 ,
4444 SshKey: 5 ,
45- } as const ;
46-
47- type _CipherType = typeof _CipherType ;
45+ } as const );
4846
4947// derive the enum-like type from the raw data
50- export type CipherType = _CipherType [keyof _CipherType ];
51-
52- // assert that the raw data is of the enum-like type
53- export const CipherType: Readonly <{ [K in keyof _CipherType ]: CipherType }> =
54- Object .freeze (_CipherType );
48+ export type CipherType = _CipherType [keyof typeof CipherType ];
5549```
5650
5751This code creates a ` type CipherType ` that allows arguments and variables to be typed similarly to
58- an enum. It also strongly types the ` const CiperType ` so that direct accesses of its members
59- preserve type safety. This ensures that type inference properly limits the accepted values to those
60- allowed by ` type CipherType ` . Without the type assertion, the compiler infers ` number ` in these
61- cases:
62-
63- ``` ts
64- const s = new Subject (CipherType .Login ); // `s` is a `Subject<CipherType>`
65- const a = [CipherType .Login , CipherType .Card ]; // `a` is an `Array<CipherType>`
66- const m = new Map ([[CipherType .Login , " " ]]); // `m` is a `Map<CipherType, string>`
67- ```
52+ an enum.
6853
6954::: warning
7055
71- - Types that use enums like [ computed property names] [ computed-property-names ] issue a compiler
72- error with this pattern. [ This issue is fixed as of TypeScript 5.8] [ no-member-fields-fixed ] .
73- - Certain objects are more difficult to create with this pattern. This is explored in
74- [ Appendix A] ( #appendix-a-mapped-types-and-enum-likes ) .
56+ Unlike an enum, typescript lifts the type of the members of ` const CipherType ` to ` number ` . Code
57+ like the following requires you explicitly type your variables:
58+
59+ ``` ts
60+ // ✅ Do: strongly type enum-likes
61+ const subject = new Subject <CipherType >();
62+ let value: CipherType = CipherType .Login ;
63+
64+ // ❌ Do not: use type inference
65+ const array = [CipherType .Login ]; // infers `number[]`
66+ let value = CipherType .Login ; // infers `1`
67+ ```
7568
7669:::
7770
@@ -107,92 +100,6 @@ Chosen option: **Deprecate enum use**
107100- Update contributing docs with patterns and best practices for enum replacement.
108101- Update the reporting level of the lint to "warning".
109102
110- ## Appendix A: Mapped Types and Enum-likes
111-
112- Mapped types cannot determine that a mapped enum-like object is fully assigned. Code like the
113- following causes a compiler error:
114-
115- ``` ts
116- const instance: Record <CipherType , boolean > = {
117- [CipherType .Login ]: true ,
118- [CipherType .SecureNote ]: false ,
119- [CipherType .Card ]: true ,
120- [CipherType .Identity ]: true ,
121- [CipherType .SshKey ]: true ,
122- };
123- ```
124-
125- #### Why does this happen?
126-
127- The members of ` const _CipherType ` all have a [ literal type] [ literal-type ] . ` _CipherType.Login ` , for
128- example, has a literal type of ` 1 ` . ` type CipherType ` maps over these members, aggregating them into
129- the structural type ` 1 | 2 | 3 | 4 | 5 ` .
130-
131- ` const CipherType ` asserts its members have ` type CipherType ` , which overrides the literal types the
132- compiler inferred for the member in ` const _CipherType ` . The compiler sees the type of
133- ` CipherType.Login ` as ` type CipherType ` (which aliases ` 1 | 2 | 3 | 4 | 5 ` ).
134-
135- Now consider a mapped type definition:
136-
137- ``` ts
138- // `MappedType` is structurally identical to Record<CipherType, boolean>
139- type MappedType = { [K in CipherType ]: boolean };
140- ```
141-
142- When the compiler examines ` instance ` , it only knows that the type of each of its members is
143- ` CipherType ` . That is, the type of ` instance ` to the compiler is
144- ` { [K in 1 | 2 | 3 | 4 | 5]?: boolean } ` . This doesn't sufficiently overlap with ` MappedType ` , which
145- is looking for ` { [1]: boolean, [2]: boolean, [3]: boolean, [4]: boolean, [5]: boolean } ` . The
146- failure occurs, because the inferred type can have fewer fields than ` MappedType ` .
147-
148- ### Workarounds
149-
150- ** Option A: Assert the type is correct.** You need to manually verify this. The compiler cannot
151- typecheck it.
152-
153- ``` ts
154- const instance: MappedType = {
155- [CipherType .Login ]: true ,
156- // ...
157- } as MappedType ;
158- ```
159-
160- ** Option B: Define the mapped type as a partial.** Then, inspect its properties before using them.
161-
162- ``` ts
163- type MappedType = { [K in CipherType ]? : boolean };
164- const instance: MappedType = {
165- [CipherType .Login ]: true ,
166- // ...
167- };
168-
169- if (CipherType .Login in instance ) {
170- // work with `instance[CipherType.Login]`
171- }
172- ```
173-
174- ** Option C: Use a collection.** Consider this approach when downstream code reflects over the result
175- with ` in ` or using methods like ` Object.keys ` .
176-
177- ``` ts
178- const collection = new Map ([[CipherType .Login , true ]]);
179-
180- const instance = collection .get (CipherType .Login );
181- if (instance ) {
182- // work with `instance`
183- }
184-
185- const available = [CipherType .Login , CipherType .Card ];
186- if (available .includes (CipherType .Login )) {
187- // ...
188- }
189- ```
190-
191- [ computed-property-names] :
192- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names
193- [ literal-type ] : https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types
194103[ no-enum-lint ] : https://github.com/bitwarden/clients/blob/main/libs/eslint/platform/no-enums.mjs
195104[ no-enum-configuration] :
196105 https://github.com/bitwarden/clients/blob/032fedf308ec251f17632d7d08c4daf6f41a4b1d/eslint.config.mjs#L77
197- [ no-member-fields-fixed] :
198- https://devblogs.microsoft.com/typescript/announcing-typescript-5-8-beta/#preserved-computed-property-names-in-declaration-files
0 commit comments