index.ts 10 KB

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