1- import { template } from "@babel/core" ;
1+ import { template , types as t } from "@babel/core" ;
22import type { NodePath } from "@babel/traverse" ;
3- import type * as t from "@babel/types" ;
43import assert from "assert" ;
54
65type t = typeof t ;
76
7+ const ENUMS = new WeakMap < t . Identifier , PreviousEnumMembers > ( ) ;
8+
89export default function transpileEnum (
910 path : NodePath < t . TSEnumDeclaration > ,
1011 t : t ,
@@ -17,7 +18,7 @@ export default function transpileEnum(
1718 }
1819
1920 const name = node . id . name ;
20- const fill = enumFill ( path , t , node . id ) ;
21+ const { wrapper : fill , data } = enumFill ( path , t , node . id ) ;
2122
2223 switch ( path . parent . type ) {
2324 case "BlockStatement" :
@@ -33,6 +34,7 @@ export default function transpileEnum(
3334 path . scope . registerDeclaration (
3435 path . replaceWith ( makeVar ( node . id , t , isGlobal ? "var" : "let" ) ) [ 0 ] ,
3536 ) ;
37+ ENUMS . set ( path . scope . getBindingIdentifier ( name ) , data ) ;
3638 }
3739 break ;
3840 }
@@ -81,7 +83,7 @@ const buildEnumMember = (isString: boolean, options: Record<string, unknown>) =>
8183 * `(function (E) { ... assignments ... })(E || (E = {}));`
8284 */
8385function enumFill ( path : NodePath < t . TSEnumDeclaration > , t : t , id : t . Identifier ) {
84- const x = translateEnumValues ( path , t ) ;
86+ const { enumValues : x , data } = translateEnumValues ( path , t ) ;
8587 const assignments = x . map ( ( [ memberName , memberValue ] ) =>
8688 buildEnumMember ( t . isStringLiteral ( memberValue ) , {
8789 ENUM : t . cloneNode ( id ) ,
@@ -90,10 +92,13 @@ function enumFill(path: NodePath<t.TSEnumDeclaration>, t: t, id: t.Identifier) {
9092 } ) ,
9193 ) ;
9294
93- return buildEnumWrapper ( {
94- ID : t . cloneNode ( id ) ,
95- ASSIGNMENTS : assignments ,
96- } ) ;
95+ return {
96+ wrapper : buildEnumWrapper ( {
97+ ID : t . cloneNode ( id ) ,
98+ ASSIGNMENTS : assignments ,
99+ } ) ,
100+ data : data ,
101+ } ;
97102}
98103
99104/**
@@ -131,109 +136,172 @@ const enumSelfReferenceVisitor = {
131136 ReferencedIdentifier,
132137} ;
133138
134- export function translateEnumValues (
135- path : NodePath < t . TSEnumDeclaration > ,
136- t : t ,
137- ) : Array < [ name : string , value : t . Expression ] > {
139+ export function translateEnumValues ( path : NodePath < t . TSEnumDeclaration > , t : t ) {
138140 const seen : PreviousEnumMembers = new Map ( ) ;
139141 // Start at -1 so the first enum member is its increment, 0.
140142 let constValue : number | string | undefined = - 1 ;
141143 let lastName : string ;
142144
143- return path . get ( "members" ) . map ( memberPath => {
144- const member = memberPath . node ;
145- const name = t . isIdentifier ( member . id ) ? member . id . name : member . id . value ;
146- const initializer = member . initializer ;
147- let value : t . Expression ;
148- if ( initializer ) {
149- constValue = computeConstantValue ( initializer , seen ) ;
150- if ( constValue !== undefined ) {
151- seen . set ( name , constValue ) ;
152- if ( typeof constValue === "number" ) {
153- value = t . numericLiteral ( constValue ) ;
145+ return {
146+ data : seen ,
147+ enumValues : path . get ( "members" ) . map ( memberPath => {
148+ const member = memberPath . node ;
149+ const name = t . isIdentifier ( member . id ) ? member . id . name : member . id . value ;
150+ const initializerPath = memberPath . get ( "initializer" ) ;
151+ const initializer = member . initializer ;
152+ let value : t . Expression ;
153+ if ( initializer ) {
154+ constValue = computeConstantValue ( initializerPath , seen ) ;
155+ if ( constValue !== undefined ) {
156+ seen . set ( name , constValue ) ;
157+ if ( typeof constValue === "number" ) {
158+ value = t . numericLiteral ( constValue ) ;
159+ } else {
160+ assert ( typeof constValue === "string" ) ;
161+ value = t . stringLiteral ( constValue ) ;
162+ }
154163 } else {
155- assert ( typeof constValue === "string" ) ;
156- value = t . stringLiteral ( constValue ) ;
164+ if ( initializerPath . isReferencedIdentifier ( ) ) {
165+ ReferencedIdentifier ( initializerPath , {
166+ t,
167+ seen,
168+ path,
169+ } ) ;
170+ } else {
171+ initializerPath . traverse ( enumSelfReferenceVisitor , {
172+ t,
173+ seen,
174+ path,
175+ } ) ;
176+ }
177+
178+ value = initializerPath . node ;
179+ seen . set ( name , undefined ) ;
157180 }
181+ } else if ( typeof constValue === "number" ) {
182+ constValue += 1 ;
183+ value = t . numericLiteral ( constValue ) ;
184+ seen . set ( name , constValue ) ;
185+ } else if ( typeof constValue === "string" ) {
186+ throw path . buildCodeFrameError ( "Enum member must have initializer." ) ;
158187 } else {
159- const initializerPath = memberPath . get ( "initializer" ) ;
160-
161- if ( initializerPath . isReferencedIdentifier ( ) ) {
162- ReferencedIdentifier ( initializerPath , {
163- t,
164- seen,
165- path,
166- } ) ;
167- } else {
168- initializerPath . traverse ( enumSelfReferenceVisitor , { t, seen, path } ) ;
169- }
170-
171- value = initializerPath . node ;
188+ // create dynamic initializer: 1 + ENUM["PREVIOUS"]
189+ const lastRef = t . memberExpression (
190+ t . cloneNode ( path . node . id ) ,
191+ t . stringLiteral ( lastName ) ,
192+ true ,
193+ ) ;
194+ value = t . binaryExpression ( "+" , t . numericLiteral ( 1 ) , lastRef ) ;
172195 seen . set ( name , undefined ) ;
173196 }
174- } else if ( typeof constValue === "number" ) {
175- constValue += 1 ;
176- value = t . numericLiteral ( constValue ) ;
177- seen . set ( name , constValue ) ;
178- } else if ( typeof constValue === "string" ) {
179- throw path . buildCodeFrameError ( "Enum member must have initializer." ) ;
180- } else {
181- // create dynamic initializer: 1 + ENUM["PREVIOUS"]
182- const lastRef = t . memberExpression (
183- t . cloneNode ( path . node . id ) ,
184- t . stringLiteral ( lastName ) ,
185- true ,
186- ) ;
187- value = t . binaryExpression ( "+" , t . numericLiteral ( 1 ) , lastRef ) ;
188- seen . set ( name , undefined ) ;
189- }
190197
191- lastName = name ;
192- return [ name , value ] ;
193- } ) ;
198+ lastName = name ;
199+ return [ name , value ] ;
200+ } ) as Array < [ name : string , value : t . Expression ] > ,
201+ } ;
194202}
195203
196204// Based on the TypeScript repository's `computeConstantValue` in `checker.ts`.
197205function computeConstantValue (
198- expr : t . Node ,
199- seen : PreviousEnumMembers ,
200- ) : number | string | typeof undefined {
201- return evaluate ( expr ) ;
206+ path : NodePath ,
207+ prevMembers ?: PreviousEnumMembers ,
208+ seen : Set < t . Identifier > = new Set ( ) ,
209+ ) : number | string | undefined {
210+ return evaluate ( path ) ;
202211
203- function evaluate ( expr : t . Node ) : number | typeof undefined {
212+ function evaluate ( path : NodePath ) : number | string | undefined {
213+ const expr = path . node ;
204214 switch ( expr . type ) {
215+ case "MemberExpression" :
216+ return evaluateRef ( path , prevMembers , seen ) ;
205217 case "StringLiteral" :
206218 return expr . value ;
207219 case "UnaryExpression" :
208- return evalUnaryExpression ( expr ) ;
220+ return evalUnaryExpression ( path as NodePath < t . UnaryExpression > ) ;
209221 case "BinaryExpression" :
210- return evalBinaryExpression ( expr ) ;
222+ return evalBinaryExpression ( path as NodePath < t . BinaryExpression > ) ;
211223 case "NumericLiteral" :
212224 return expr . value ;
213225 case "ParenthesizedExpression" :
214- return evaluate ( expr . expression ) ;
226+ return evaluate ( path . get ( " expression" ) ) ;
215227 case "Identifier" :
216- return seen . get ( expr . name ) ;
217- case "TemplateLiteral" :
228+ return evaluateRef ( path , prevMembers , seen ) ;
229+ case "TemplateLiteral" : {
218230 if ( expr . quasis . length === 1 ) {
219231 return expr . quasis [ 0 ] . value . cooked ;
220232 }
221- /* falls through */
233+
234+ const paths = ( path as NodePath < t . TemplateLiteral > ) . get ( "expressions" ) ;
235+ const quasis = expr . quasis ;
236+ let str = "" ;
237+
238+ for ( let i = 0 ; i < quasis . length ; i ++ ) {
239+ str += quasis [ i ] . value . cooked ;
240+
241+ if ( i + 1 < quasis . length ) {
242+ const value = evaluateRef ( paths [ i ] , prevMembers , seen ) ;
243+ if ( value === undefined ) return undefined ;
244+ str += value ;
245+ }
246+ }
247+ return str ;
248+ }
222249 default :
223250 return undefined ;
224251 }
225252 }
226253
227- function evalUnaryExpression ( {
228- argument,
229- operator,
230- } : t . UnaryExpression ) : number | typeof undefined {
231- const value = evaluate ( argument ) ;
254+ function evaluateRef (
255+ path : NodePath ,
256+ prevMembers : PreviousEnumMembers ,
257+ seen : Set < t . Identifier > ,
258+ ) : number | string | undefined {
259+ if ( path . isMemberExpression ( ) ) {
260+ const expr = path . node ;
261+
262+ const obj = expr . object ;
263+ const prop = expr . property ;
264+ if (
265+ ! t . isIdentifier ( obj ) ||
266+ ( expr . computed ? ! t . isStringLiteral ( prop ) : ! t . isIdentifier ( prop ) )
267+ ) {
268+ return ;
269+ }
270+ const bindingIdentifier = path . scope . getBindingIdentifier ( obj . name ) ;
271+ const data = ENUMS . get ( bindingIdentifier ) ;
272+ if ( ! data ) return ;
273+ // @ts -expect-error checked above
274+ return data . get ( prop . computed ? prop . value : prop . name ) ;
275+ } else if ( path . isIdentifier ( ) ) {
276+ const name = path . node . name ;
277+
278+ let value = prevMembers ?. get ( name ) ;
279+ if ( value !== undefined ) {
280+ return value ;
281+ }
282+
283+ if ( seen . has ( path . node ) ) return ;
284+
285+ const bindingInitPath = path . resolve ( ) ; // It only resolves constant bindings
286+ if ( bindingInitPath ) {
287+ seen . add ( path . node ) ;
288+
289+ value = computeConstantValue ( bindingInitPath , undefined , seen ) ;
290+ prevMembers ?. set ( name , value ) ;
291+ return value ;
292+ }
293+ }
294+ }
295+
296+ function evalUnaryExpression (
297+ path : NodePath < t . UnaryExpression > ,
298+ ) : number | string | undefined {
299+ const value = evaluate ( path . get ( "argument" ) ) ;
232300 if ( value === undefined ) {
233301 return undefined ;
234302 }
235303
236- switch ( operator ) {
304+ switch ( path . node . operator ) {
237305 case "+" :
238306 return value ;
239307 case "-" :
@@ -245,17 +313,19 @@ function computeConstantValue(
245313 }
246314 }
247315
248- function evalBinaryExpression ( expr : t . BinaryExpression ) : number | undefined {
249- const left = evaluate ( expr . left ) ;
316+ function evalBinaryExpression (
317+ path : NodePath < t . BinaryExpression > ,
318+ ) : number | string | undefined {
319+ const left = evaluate ( path . get ( "left" ) ) as any ;
250320 if ( left === undefined ) {
251321 return undefined ;
252322 }
253- const right = evaluate ( expr . right ) ;
323+ const right = evaluate ( path . get ( " right" ) ) as any ;
254324 if ( right === undefined ) {
255325 return undefined ;
256326 }
257327
258- switch ( expr . operator ) {
328+ switch ( path . node . operator ) {
259329 case "|" :
260330 return left | right ;
261331 case "&" :
0 commit comments