1"use strict";
2exports.__esModule = true;
3var babylon_1 = require("babylon");
4var b = require("babel-types");
5var binaryOperation_1 = require("./binaryOperation");
6function expressionToConstant(expression, options) {
7    if (options === void 0) { options = {}; }
8    var constant = true;
9    function toConstant(expression) {
10        if (!constant)
11            return;
12        if (b.isArrayExpression(expression)) {
13            var result_1 = [];
14            for (var i = 0; constant && i < expression.elements.length; i++) {
15                var element = expression.elements[i];
16                if (b.isSpreadElement(element)) {
17                    var spread = toConstant(element.argument);
18                    if (!(isSpreadable(spread) && constant)) {
19                        constant = false;
20                    }
21                    else {
22                        result_1.push.apply(result_1, spread);
23                    }
24                }
25                else {
26                    result_1.push(toConstant(element));
27                }
28            }
29            return result_1;
30        }
31        if (b.isBinaryExpression(expression)) {
32            var left = toConstant(expression.left);
33            var right = toConstant(expression.right);
34            return constant && binaryOperation_1["default"](expression.operator, left, right);
35        }
36        if (b.isBooleanLiteral(expression)) {
37            return expression.value;
38        }
39        if (b.isCallExpression(expression)) {
40            var args = [];
41            for (var i = 0; constant && i < expression.arguments.length; i++) {
42                var arg = expression.arguments[i];
43                if (b.isSpreadElement(arg)) {
44                    var spread = toConstant(arg.argument);
45                    if (!(isSpreadable(spread) && constant)) {
46                        constant = false;
47                    }
48                    else {
49                        args.push.apply(args, spread);
50                    }
51                }
52                else {
53                    args.push(toConstant(arg));
54                }
55            }
56            if (!constant)
57                return;
58            if (b.isMemberExpression(expression.callee)) {
59                var object = toConstant(expression.callee.object);
60                if (!object || !constant) {
61                    constant = false;
62                    return;
63                }
64                var member = expression.callee.computed
65                    ? toConstant(expression.callee.property)
66                    : b.isIdentifier(expression.callee.property)
67                        ? expression.callee.property.name
68                        : undefined;
69                if (member === undefined && !expression.callee.computed) {
70                    constant = false;
71                }
72                if (!constant)
73                    return;
74                if (canCallMethod(object, '' + member)) {
75                    return object[member].apply(object, args);
76                }
77            }
78            else {
79                var callee = toConstant(expression.callee);
80                if (!constant)
81                    return;
82                return callee.apply(null, args);
83            }
84        }
85        if (b.isConditionalExpression(expression)) {
86            var test = toConstant(expression.test);
87            return test
88                ? toConstant(expression.consequent)
89                : toConstant(expression.alternate);
90        }
91        if (b.isIdentifier(expression)) {
92            if (options.constants &&
93                {}.hasOwnProperty.call(options.constants, expression.name)) {
94                return options.constants[expression.name];
95            }
96        }
97        if (b.isLogicalExpression(expression)) {
98            var left = toConstant(expression.left);
99            var right = toConstant(expression.right);
100            if (constant && expression.operator === '&&') {
101                return left && right;
102            }
103            if (constant && expression.operator === '||') {
104                return left || right;
105            }
106        }
107        if (b.isMemberExpression(expression)) {
108            var object = toConstant(expression.object);
109            if (!object || !constant) {
110                constant = false;
111                return;
112            }
113            var member = expression.computed
114                ? toConstant(expression.property)
115                : b.isIdentifier(expression.property)
116                    ? expression.property.name
117                    : undefined;
118            if (member === undefined && !expression.computed) {
119                constant = false;
120            }
121            if (!constant)
122                return;
123            if ({}.hasOwnProperty.call(object, '' + member) && member[0] !== '_') {
124                return object[member];
125            }
126        }
127        if (b.isNullLiteral(expression)) {
128            return null;
129        }
130        if (b.isNumericLiteral(expression)) {
131            return expression.value;
132        }
133        if (b.isObjectExpression(expression)) {
134            var result_2 = {};
135            for (var i = 0; constant && i < expression.properties.length; i++) {
136                var property = expression.properties[i];
137                if (b.isObjectProperty(property)) {
138                    if (property.shorthand) {
139                        constant = false;
140                        return;
141                    }
142                    var key = property.computed
143                        ? toConstant(property.key)
144                        : b.isIdentifier(property.key)
145                            ? property.key.name
146                            : b.isStringLiteral(property.key)
147                                ? property.key.value
148                                : undefined;
149                    if (!key || key[0] === '_') {
150                        constant = false;
151                    }
152                    if (!constant)
153                        return;
154                    var value = toConstant(property.value);
155                    if (!constant)
156                        return;
157                    result_2[key] = value;
158                }
159                else if (b.isObjectMethod(property)) {
160                    constant = false;
161                }
162                else if (b.isSpreadProperty(property)) {
163                    var argument = toConstant(property.argument);
164                    if (!argument)
165                        constant = false;
166                    if (!constant)
167                        return;
168                    Object.assign(result_2, argument);
169                }
170            }
171            return result_2;
172        }
173        if (b.isParenthesizedExpression(expression)) {
174            return toConstant(expression.expression);
175        }
176        if (b.isRegExpLiteral(expression)) {
177            return new RegExp(expression.pattern, expression.flags);
178        }
179        if (b.isSequenceExpression(expression)) {
180            for (var i = 0; i < expression.expressions.length - 1 && constant; i++) {
181                toConstant(expression.expressions[i]);
182            }
183            return toConstant(expression.expressions[expression.expressions.length - 1]);
184        }
185        if (b.isStringLiteral(expression)) {
186            return expression.value;
187        }
188        // TODO: TaggedTemplateExpression
189        if (b.isTemplateLiteral(expression)) {
190            var result_3 = '';
191            for (var i = 0; i < expression.quasis.length; i++) {
192                var quasi = expression.quasis[i];
193                result_3 += quasi.value.cooked;
194                if (i < expression.expressions.length) {
195                    result_3 += '' + toConstant(expression.expressions[i]);
196                }
197            }
198            return result_3;
199        }
200        if (b.isUnaryExpression(expression)) {
201            var argument = toConstant(expression.argument);
202            if (!constant) {
203                return;
204            }
205            switch (expression.operator) {
206                case '-':
207                    return -argument;
208                case '+':
209                    return +argument;
210                case '!':
211                    return !argument;
212                case '~':
213                    return ~argument;
214                case 'typeof':
215                    return typeof argument;
216                case 'void':
217                    return void argument;
218            }
219        }
220        constant = false;
221    }
222    var result = toConstant(expression);
223    return constant ? { constant: true, result: result } : { constant: false };
224}
225exports.expressionToConstant = expressionToConstant;
226function isSpreadable(value) {
227    return (typeof value === 'string' ||
228        Array.isArray(value) ||
229        (typeof Set !== 'undefined' && value instanceof Set) ||
230        (typeof Map !== 'undefined' && value instanceof Map));
231}
232function shallowEqual(a, b) {
233    if (a === b)
234        return true;
235    if (a && b && typeof a === 'object' && typeof b === 'object') {
236        for (var key in a) {
237            if (a[key] !== b[key]) {
238                return false;
239            }
240        }
241        for (var key in b) {
242            if (a[key] !== b[key]) {
243                return false;
244            }
245        }
246        return true;
247    }
248    return false;
249}
250function canCallMethod(object, member) {
251    switch (typeof object) {
252        case 'boolean':
253            switch (member) {
254                case 'toString':
255                    return true;
256                default:
257                    return false;
258            }
259        case 'number':
260            switch (member) {
261                case 'toExponential':
262                case 'toFixed':
263                case 'toPrecision':
264                case 'toString':
265                    return true;
266                default:
267                    return false;
268            }
269        case 'string':
270            switch (member) {
271                case 'charAt':
272                case 'charCodeAt':
273                case 'codePointAt':
274                case 'concat':
275                case 'endsWith':
276                case 'includes':
277                case 'indexOf':
278                case 'lastIndexOf':
279                case 'match':
280                case 'normalize':
281                case 'padEnd':
282                case 'padStart':
283                case 'repeat':
284                case 'replace':
285                case 'search':
286                case 'slice':
287                case 'split':
288                case 'startsWith':
289                case 'substr':
290                case 'substring':
291                case 'toLowerCase':
292                case 'toUpperCase':
293                case 'trim':
294                    return true;
295                default:
296                    return false;
297            }
298        default:
299            if (object instanceof RegExp) {
300                switch (member) {
301                    case 'test':
302                    case 'exec':
303                        return true;
304                    default:
305                        return false;
306                }
307            }
308            return {}.hasOwnProperty.call(object, member) && member[0] !== '_';
309    }
310}
311var EMPTY_OBJECT = {};
312var lastSrc = '';
313var lastConstants = EMPTY_OBJECT;
314var lastOptions = EMPTY_OBJECT;
315var lastResult = null;
316var lastWasConstant = false;
317function isConstant(src, constants, options) {
318    if (constants === void 0) { constants = EMPTY_OBJECT; }
319    if (options === void 0) { options = EMPTY_OBJECT; }
320    if (lastSrc === src &&
321        shallowEqual(lastConstants, constants) &&
322        shallowEqual(lastOptions, options)) {
323        return lastWasConstant;
324    }
325    lastSrc = src;
326    lastConstants = constants;
327    var ast;
328    try {
329        ast = babylon_1.parseExpression(src, options);
330    }
331    catch (ex) {
332        return (lastWasConstant = false);
333    }
334    var _a = expressionToConstant(ast, { constants: constants }), result = _a.result, constant = _a.constant;
335    lastResult = result;
336    return (lastWasConstant = constant);
337}
338exports.isConstant = isConstant;
339function toConstant(src, constants, options) {
340    if (constants === void 0) { constants = EMPTY_OBJECT; }
341    if (options === void 0) { options = EMPTY_OBJECT; }
342    if (!isConstant(src, constants, options)) {
343        throw new Error(JSON.stringify(src) + ' is not constant.');
344    }
345    return lastResult;
346}
347exports.toConstant = toConstant;
348exports["default"] = isConstant;
349module.exports = isConstant;
350module.exports["default"] = isConstant;
351module.exports.expressionToConstant = expressionToConstant;
352module.exports.isConstant = isConstant;
353module.exports.toConstant = toConstant;
354