UNPKG

5.22 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = transpileEnum;
7
8var _assert = _interopRequireDefault(require("assert"));
9
10var _core = require("@babel/core");
11
12function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
14function transpileEnum(path, t) {
15 const {
16 node
17 } = path;
18
19 if (node.const) {
20 throw path.buildCodeFrameError("'const' enums are not supported.");
21 }
22
23 if (node.declare) {
24 path.remove();
25 return;
26 }
27
28 const name = node.id.name;
29 const fill = enumFill(path, t, node.id);
30
31 switch (path.parent.type) {
32 case "BlockStatement":
33 case "ExportNamedDeclaration":
34 case "Program":
35 {
36 path.insertAfter(fill);
37
38 if (seen(path.parentPath)) {
39 path.remove();
40 } else {
41 const isGlobal = t.isProgram(path.parent);
42 path.scope.registerDeclaration(path.replaceWith(makeVar(node.id, t, isGlobal ? "var" : "let"))[0]);
43 }
44
45 break;
46 }
47
48 default:
49 throw new Error(`Unexpected enum parent '${path.parent.type}`);
50 }
51
52 function seen(parentPath) {
53 if (parentPath.isExportDeclaration()) {
54 return seen(parentPath.parentPath);
55 }
56
57 if (parentPath.getData(name)) {
58 return true;
59 } else {
60 parentPath.setData(name, true);
61 return false;
62 }
63 }
64}
65
66function makeVar(id, t, kind) {
67 return t.variableDeclaration(kind, [t.variableDeclarator(id)]);
68}
69
70const buildEnumWrapper = (0, _core.template)(`
71 (function (ID) {
72 ASSIGNMENTS;
73 })(ID || (ID = {}));
74`);
75const buildStringAssignment = (0, _core.template)(`
76 ENUM["NAME"] = VALUE;
77`);
78const buildNumericAssignment = (0, _core.template)(`
79 ENUM[ENUM["NAME"] = VALUE] = "NAME";
80`);
81
82const buildEnumMember = (isString, options) => (isString ? buildStringAssignment : buildNumericAssignment)(options);
83
84function enumFill(path, t, id) {
85 const x = translateEnumValues(path, t);
86 const assignments = x.map(([memberName, memberValue]) => buildEnumMember(t.isStringLiteral(memberValue), {
87 ENUM: t.cloneNode(id),
88 NAME: memberName,
89 VALUE: memberValue
90 }));
91 return buildEnumWrapper({
92 ID: t.cloneNode(id),
93 ASSIGNMENTS: assignments
94 });
95}
96
97function translateEnumValues(path, t) {
98 const seen = Object.create(null);
99 let prev = -1;
100 return path.node.members.map(member => {
101 const name = t.isIdentifier(member.id) ? member.id.name : member.id.value;
102 const initializer = member.initializer;
103 let value;
104
105 if (initializer) {
106 const constValue = evaluate(initializer, seen);
107
108 if (constValue !== undefined) {
109 seen[name] = constValue;
110
111 if (typeof constValue === "number") {
112 value = t.numericLiteral(constValue);
113 prev = constValue;
114 } else {
115 (0, _assert.default)(typeof constValue === "string");
116 value = t.stringLiteral(constValue);
117 prev = undefined;
118 }
119 } else {
120 value = initializer;
121 prev = undefined;
122 }
123 } else {
124 if (prev !== undefined) {
125 prev++;
126 value = t.numericLiteral(prev);
127 seen[name] = prev;
128 } else {
129 throw path.buildCodeFrameError("Enum member must have initializer.");
130 }
131 }
132
133 return [name, value];
134 });
135}
136
137function evaluate(expr, seen) {
138 return evalConstant(expr);
139
140 function evalConstant(expr) {
141 switch (expr.type) {
142 case "StringLiteral":
143 return expr.value;
144
145 case "UnaryExpression":
146 return evalUnaryExpression(expr);
147
148 case "BinaryExpression":
149 return evalBinaryExpression(expr);
150
151 case "NumericLiteral":
152 return expr.value;
153
154 case "ParenthesizedExpression":
155 return evalConstant(expr.expression);
156
157 case "Identifier":
158 return seen[expr.name];
159
160 case "TemplateLiteral":
161 if (expr.quasis.length === 1) {
162 return expr.quasis[0].value.cooked;
163 }
164
165 default:
166 return undefined;
167 }
168 }
169
170 function evalUnaryExpression({
171 argument,
172 operator
173 }) {
174 const value = evalConstant(argument);
175
176 if (value === undefined) {
177 return undefined;
178 }
179
180 switch (operator) {
181 case "+":
182 return value;
183
184 case "-":
185 return -value;
186
187 case "~":
188 return ~value;
189
190 default:
191 return undefined;
192 }
193 }
194
195 function evalBinaryExpression(expr) {
196 const left = evalConstant(expr.left);
197
198 if (left === undefined) {
199 return undefined;
200 }
201
202 const right = evalConstant(expr.right);
203
204 if (right === undefined) {
205 return undefined;
206 }
207
208 switch (expr.operator) {
209 case "|":
210 return left | right;
211
212 case "&":
213 return left & right;
214
215 case ">>":
216 return left >> right;
217
218 case ">>>":
219 return left >>> right;
220
221 case "<<":
222 return left << right;
223
224 case "^":
225 return left ^ right;
226
227 case "*":
228 return left * right;
229
230 case "/":
231 return left / right;
232
233 case "+":
234 return left + right;
235
236 case "-":
237 return left - right;
238
239 case "%":
240 return left % right;
241
242 default:
243 return undefined;
244 }
245 }
246}
\No newline at end of file