1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.default = transpileEnum;
|
7 |
|
8 | var _assert = _interopRequireDefault(require("assert"));
|
9 |
|
10 | var _core = require("@babel/core");
|
11 |
|
12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
13 |
|
14 | function 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 |
|
66 | function makeVar(id, t, kind) {
|
67 | return t.variableDeclaration(kind, [t.variableDeclarator(id)]);
|
68 | }
|
69 |
|
70 | const buildEnumWrapper = (0, _core.template)(`
|
71 | (function (ID) {
|
72 | ASSIGNMENTS;
|
73 | })(ID || (ID = {}));
|
74 | `);
|
75 | const buildStringAssignment = (0, _core.template)(`
|
76 | ENUM["NAME"] = VALUE;
|
77 | `);
|
78 | const buildNumericAssignment = (0, _core.template)(`
|
79 | ENUM[ENUM["NAME"] = VALUE] = "NAME";
|
80 | `);
|
81 |
|
82 | const buildEnumMember = (isString, options) => (isString ? buildStringAssignment : buildNumericAssignment)(options);
|
83 |
|
84 | function 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 |
|
97 | function 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 |
|
137 | function 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 |