BasicEvaluatedExpression.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. /** @typedef {import("estree").Node} Node */
  7. /** @typedef {import("./JavascriptParser").Range} Range */
  8. /** @typedef {import("./JavascriptParser").VariableInfoInterface} VariableInfoInterface */
  9. const TypeUnknown = 0;
  10. const TypeUndefined = 1;
  11. const TypeNull = 2;
  12. const TypeString = 3;
  13. const TypeNumber = 4;
  14. const TypeBoolean = 5;
  15. const TypeRegExp = 6;
  16. const TypeConditional = 7;
  17. const TypeArray = 8;
  18. const TypeConstArray = 9;
  19. const TypeIdentifier = 10;
  20. const TypeWrapped = 11;
  21. const TypeTemplateString = 12;
  22. const TypeBigInt = 13;
  23. class BasicEvaluatedExpression {
  24. constructor() {
  25. this.type = TypeUnknown;
  26. /** @type {Range | undefined} */
  27. this.range = undefined;
  28. /** @type {boolean} */
  29. this.falsy = false;
  30. /** @type {boolean} */
  31. this.truthy = false;
  32. /** @type {boolean | undefined} */
  33. this.nullish = undefined;
  34. /** @type {boolean} */
  35. this.sideEffects = true;
  36. /** @type {boolean | undefined} */
  37. this.bool = undefined;
  38. /** @type {number | undefined} */
  39. this.number = undefined;
  40. /** @type {bigint | undefined} */
  41. this.bigint = undefined;
  42. /** @type {RegExp | undefined} */
  43. this.regExp = undefined;
  44. /** @type {string | undefined} */
  45. this.string = undefined;
  46. /** @type {BasicEvaluatedExpression[] | undefined} */
  47. this.quasis = undefined;
  48. /** @type {BasicEvaluatedExpression[] | undefined} */
  49. this.parts = undefined;
  50. /** @type {any[] | undefined} */
  51. this.array = undefined;
  52. /** @type {BasicEvaluatedExpression[] | undefined} */
  53. this.items = undefined;
  54. /** @type {BasicEvaluatedExpression[] | undefined} */
  55. this.options = undefined;
  56. /** @type {BasicEvaluatedExpression | undefined | null} */
  57. this.prefix = undefined;
  58. /** @type {BasicEvaluatedExpression | undefined | null} */
  59. this.postfix = undefined;
  60. /** @type {BasicEvaluatedExpression[] | undefined} */
  61. this.wrappedInnerExpressions = undefined;
  62. /** @type {string | VariableInfoInterface | undefined} */
  63. this.identifier = undefined;
  64. /** @type {string | VariableInfoInterface | undefined} */
  65. this.rootInfo = undefined;
  66. /** @type {(() => string[]) | undefined} */
  67. this.getMembers = undefined;
  68. /** @type {(() => boolean[]) | undefined} */
  69. this.getMembersOptionals = undefined;
  70. /** @type {(() => Range[]) | undefined} */
  71. this.getMemberRanges = undefined;
  72. /** @type {Node | undefined} */
  73. this.expression = undefined;
  74. }
  75. isUnknown() {
  76. return this.type === TypeUnknown;
  77. }
  78. isNull() {
  79. return this.type === TypeNull;
  80. }
  81. isUndefined() {
  82. return this.type === TypeUndefined;
  83. }
  84. isString() {
  85. return this.type === TypeString;
  86. }
  87. isNumber() {
  88. return this.type === TypeNumber;
  89. }
  90. isBigInt() {
  91. return this.type === TypeBigInt;
  92. }
  93. isBoolean() {
  94. return this.type === TypeBoolean;
  95. }
  96. isRegExp() {
  97. return this.type === TypeRegExp;
  98. }
  99. isConditional() {
  100. return this.type === TypeConditional;
  101. }
  102. isArray() {
  103. return this.type === TypeArray;
  104. }
  105. isConstArray() {
  106. return this.type === TypeConstArray;
  107. }
  108. isIdentifier() {
  109. return this.type === TypeIdentifier;
  110. }
  111. isWrapped() {
  112. return this.type === TypeWrapped;
  113. }
  114. isTemplateString() {
  115. return this.type === TypeTemplateString;
  116. }
  117. /**
  118. * Is expression a primitive or an object type value?
  119. * @returns {boolean | undefined} true: primitive type, false: object type, undefined: unknown/runtime-defined
  120. */
  121. isPrimitiveType() {
  122. switch (this.type) {
  123. case TypeUndefined:
  124. case TypeNull:
  125. case TypeString:
  126. case TypeNumber:
  127. case TypeBoolean:
  128. case TypeBigInt:
  129. case TypeWrapped:
  130. case TypeTemplateString:
  131. return true;
  132. case TypeRegExp:
  133. case TypeArray:
  134. case TypeConstArray:
  135. return false;
  136. default:
  137. return undefined;
  138. }
  139. }
  140. /**
  141. * Is expression a runtime or compile-time value?
  142. * @returns {boolean} true: compile time value, false: runtime value
  143. */
  144. isCompileTimeValue() {
  145. switch (this.type) {
  146. case TypeUndefined:
  147. case TypeNull:
  148. case TypeString:
  149. case TypeNumber:
  150. case TypeBoolean:
  151. case TypeRegExp:
  152. case TypeConstArray:
  153. case TypeBigInt:
  154. return true;
  155. default:
  156. return false;
  157. }
  158. }
  159. /**
  160. * Gets the compile-time value of the expression
  161. * @returns {any} the javascript value
  162. */
  163. asCompileTimeValue() {
  164. switch (this.type) {
  165. case TypeUndefined:
  166. return undefined;
  167. case TypeNull:
  168. return null;
  169. case TypeString:
  170. return this.string;
  171. case TypeNumber:
  172. return this.number;
  173. case TypeBoolean:
  174. return this.bool;
  175. case TypeRegExp:
  176. return this.regExp;
  177. case TypeConstArray:
  178. return this.array;
  179. case TypeBigInt:
  180. return this.bigint;
  181. default:
  182. throw new Error(
  183. "asCompileTimeValue must only be called for compile-time values"
  184. );
  185. }
  186. }
  187. isTruthy() {
  188. return this.truthy;
  189. }
  190. isFalsy() {
  191. return this.falsy;
  192. }
  193. isNullish() {
  194. return this.nullish;
  195. }
  196. /**
  197. * Can this expression have side effects?
  198. * @returns {boolean} false: never has side effects
  199. */
  200. couldHaveSideEffects() {
  201. return this.sideEffects;
  202. }
  203. /**
  204. * Creates a boolean representation of this evaluated expression.
  205. * @returns {boolean | undefined} true: truthy, false: falsy, undefined: unknown
  206. */
  207. asBool() {
  208. if (this.truthy) return true;
  209. if (this.falsy || this.nullish) return false;
  210. if (this.isBoolean()) return this.bool;
  211. if (this.isNull()) return false;
  212. if (this.isUndefined()) return false;
  213. if (this.isString()) return this.string !== "";
  214. if (this.isNumber()) return this.number !== 0;
  215. if (this.isBigInt()) return this.bigint !== BigInt(0);
  216. if (this.isRegExp()) return true;
  217. if (this.isArray()) return true;
  218. if (this.isConstArray()) return true;
  219. if (this.isWrapped()) {
  220. return (this.prefix && this.prefix.asBool()) ||
  221. (this.postfix && this.postfix.asBool())
  222. ? true
  223. : undefined;
  224. }
  225. if (this.isTemplateString()) {
  226. const str = this.asString();
  227. if (typeof str === "string") return str !== "";
  228. }
  229. return undefined;
  230. }
  231. /**
  232. * Creates a nullish coalescing representation of this evaluated expression.
  233. * @returns {boolean | undefined} true: nullish, false: not nullish, undefined: unknown
  234. */
  235. asNullish() {
  236. const nullish = this.isNullish();
  237. if (nullish === true || this.isNull() || this.isUndefined()) return true;
  238. if (nullish === false) return false;
  239. if (this.isTruthy()) return false;
  240. if (this.isBoolean()) return false;
  241. if (this.isString()) return false;
  242. if (this.isNumber()) return false;
  243. if (this.isBigInt()) return false;
  244. if (this.isRegExp()) return false;
  245. if (this.isArray()) return false;
  246. if (this.isConstArray()) return false;
  247. if (this.isTemplateString()) return false;
  248. if (this.isRegExp()) return false;
  249. return undefined;
  250. }
  251. /**
  252. * Creates a string representation of this evaluated expression.
  253. * @returns {string | undefined} the string representation or undefined if not possible
  254. */
  255. asString() {
  256. if (this.isBoolean()) return `${this.bool}`;
  257. if (this.isNull()) return "null";
  258. if (this.isUndefined()) return "undefined";
  259. if (this.isString()) return this.string;
  260. if (this.isNumber()) return `${this.number}`;
  261. if (this.isBigInt()) return `${this.bigint}`;
  262. if (this.isRegExp()) return `${this.regExp}`;
  263. if (this.isArray()) {
  264. let array = [];
  265. for (const item of /** @type {BasicEvaluatedExpression[]} */ (
  266. this.items
  267. )) {
  268. const itemStr = item.asString();
  269. if (itemStr === undefined) return undefined;
  270. array.push(itemStr);
  271. }
  272. return `${array}`;
  273. }
  274. if (this.isConstArray()) return `${this.array}`;
  275. if (this.isTemplateString()) {
  276. let str = "";
  277. for (const part of /** @type {BasicEvaluatedExpression[]} */ (
  278. this.parts
  279. )) {
  280. const partStr = part.asString();
  281. if (partStr === undefined) return undefined;
  282. str += partStr;
  283. }
  284. return str;
  285. }
  286. return undefined;
  287. }
  288. /**
  289. * @param {string} string value
  290. * @returns {BasicEvaluatedExpression} basic evaluated expression
  291. */
  292. setString(string) {
  293. this.type = TypeString;
  294. this.string = string;
  295. this.sideEffects = false;
  296. return this;
  297. }
  298. setUndefined() {
  299. this.type = TypeUndefined;
  300. this.sideEffects = false;
  301. return this;
  302. }
  303. setNull() {
  304. this.type = TypeNull;
  305. this.sideEffects = false;
  306. return this;
  307. }
  308. /**
  309. * Set's the value of this expression to a number
  310. * @param {number} number number to set
  311. * @returns {this} this
  312. */
  313. setNumber(number) {
  314. this.type = TypeNumber;
  315. this.number = number;
  316. this.sideEffects = false;
  317. return this;
  318. }
  319. /**
  320. * Set's the value of this expression to a BigInt
  321. * @param {bigint} bigint bigint to set
  322. * @returns {this} this
  323. */
  324. setBigInt(bigint) {
  325. this.type = TypeBigInt;
  326. this.bigint = bigint;
  327. this.sideEffects = false;
  328. return this;
  329. }
  330. /**
  331. * Set's the value of this expression to a boolean
  332. * @param {boolean} bool boolean to set
  333. * @returns {this} this
  334. */
  335. setBoolean(bool) {
  336. this.type = TypeBoolean;
  337. this.bool = bool;
  338. this.sideEffects = false;
  339. return this;
  340. }
  341. /**
  342. * Set's the value of this expression to a regular expression
  343. * @param {RegExp} regExp regular expression to set
  344. * @returns {this} this
  345. */
  346. setRegExp(regExp) {
  347. this.type = TypeRegExp;
  348. this.regExp = regExp;
  349. this.sideEffects = false;
  350. return this;
  351. }
  352. /**
  353. * Set's the value of this expression to a particular identifier and its members.
  354. *
  355. * @param {string | VariableInfoInterface} identifier identifier to set
  356. * @param {string | VariableInfoInterface} rootInfo root info
  357. * @param {() => string[]} getMembers members
  358. * @param {() => boolean[]=} getMembersOptionals optional members
  359. * @param {() => Range[]=} getMemberRanges ranges of progressively increasing sub-expressions
  360. * @returns {this} this
  361. */
  362. setIdentifier(
  363. identifier,
  364. rootInfo,
  365. getMembers,
  366. getMembersOptionals,
  367. getMemberRanges
  368. ) {
  369. this.type = TypeIdentifier;
  370. this.identifier = identifier;
  371. this.rootInfo = rootInfo;
  372. this.getMembers = getMembers;
  373. this.getMembersOptionals = getMembersOptionals;
  374. this.getMemberRanges = getMemberRanges;
  375. this.sideEffects = true;
  376. return this;
  377. }
  378. /**
  379. * Wraps an array of expressions with a prefix and postfix expression.
  380. *
  381. * @param {BasicEvaluatedExpression | null | undefined} prefix Expression to be added before the innerExpressions
  382. * @param {BasicEvaluatedExpression | null | undefined} postfix Expression to be added after the innerExpressions
  383. * @param {BasicEvaluatedExpression[] | undefined} innerExpressions Expressions to be wrapped
  384. * @returns {this} this
  385. */
  386. setWrapped(prefix, postfix, innerExpressions) {
  387. this.type = TypeWrapped;
  388. this.prefix = prefix;
  389. this.postfix = postfix;
  390. this.wrappedInnerExpressions = innerExpressions;
  391. this.sideEffects = true;
  392. return this;
  393. }
  394. /**
  395. * Stores the options of a conditional expression.
  396. *
  397. * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be set
  398. * @returns {this} this
  399. */
  400. setOptions(options) {
  401. this.type = TypeConditional;
  402. this.options = options;
  403. this.sideEffects = true;
  404. return this;
  405. }
  406. /**
  407. * Adds options to a conditional expression.
  408. *
  409. * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be added
  410. * @returns {this} this
  411. */
  412. addOptions(options) {
  413. if (!this.options) {
  414. this.type = TypeConditional;
  415. this.options = [];
  416. this.sideEffects = true;
  417. }
  418. for (const item of options) {
  419. this.options.push(item);
  420. }
  421. return this;
  422. }
  423. /**
  424. * Set's the value of this expression to an array of expressions.
  425. *
  426. * @param {BasicEvaluatedExpression[]} items expressions to set
  427. * @returns {this} this
  428. */
  429. setItems(items) {
  430. this.type = TypeArray;
  431. this.items = items;
  432. this.sideEffects = items.some(i => i.couldHaveSideEffects());
  433. return this;
  434. }
  435. /**
  436. * Set's the value of this expression to an array of strings.
  437. *
  438. * @param {string[]} array array to set
  439. * @returns {this} this
  440. */
  441. setArray(array) {
  442. this.type = TypeConstArray;
  443. this.array = array;
  444. this.sideEffects = false;
  445. return this;
  446. }
  447. /**
  448. * Set's the value of this expression to a processed/unprocessed template string. Used
  449. * for evaluating TemplateLiteral expressions in the JavaScript Parser.
  450. *
  451. * @param {BasicEvaluatedExpression[]} quasis template string quasis
  452. * @param {BasicEvaluatedExpression[]} parts template string parts
  453. * @param {"cooked" | "raw"} kind template string kind
  454. * @returns {this} this
  455. */
  456. setTemplateString(quasis, parts, kind) {
  457. this.type = TypeTemplateString;
  458. this.quasis = quasis;
  459. this.parts = parts;
  460. this.templateStringKind = kind;
  461. this.sideEffects = parts.some(p => p.sideEffects);
  462. return this;
  463. }
  464. setTruthy() {
  465. this.falsy = false;
  466. this.truthy = true;
  467. this.nullish = false;
  468. return this;
  469. }
  470. setFalsy() {
  471. this.falsy = true;
  472. this.truthy = false;
  473. return this;
  474. }
  475. /**
  476. * Set's the value of the expression to nullish.
  477. *
  478. * @param {boolean} value true, if the expression is nullish
  479. * @returns {this} this
  480. */
  481. setNullish(value) {
  482. this.nullish = value;
  483. if (value) return this.setFalsy();
  484. return this;
  485. }
  486. /**
  487. * Set's the range for the expression.
  488. *
  489. * @param {[number, number]} range range to set
  490. * @returns {this} this
  491. */
  492. setRange(range) {
  493. this.range = range;
  494. return this;
  495. }
  496. /**
  497. * Set whether or not the expression has side effects.
  498. *
  499. * @param {boolean} sideEffects true, if the expression has side effects
  500. * @returns {this} this
  501. */
  502. setSideEffects(sideEffects = true) {
  503. this.sideEffects = sideEffects;
  504. return this;
  505. }
  506. /**
  507. * Set the expression node for the expression.
  508. *
  509. * @param {Node | undefined} expression expression
  510. * @returns {this} this
  511. */
  512. setExpression(expression) {
  513. this.expression = expression;
  514. return this;
  515. }
  516. }
  517. /**
  518. * @param {string} flags regexp flags
  519. * @returns {boolean} is valid flags
  520. */
  521. BasicEvaluatedExpression.isValidRegExpFlags = flags => {
  522. const len = flags.length;
  523. if (len === 0) return true;
  524. if (len > 4) return false;
  525. // cspell:word gimy
  526. let remaining = 0b0000; // bit per RegExp flag: gimy
  527. for (let i = 0; i < len; i++)
  528. switch (flags.charCodeAt(i)) {
  529. case 103 /* g */:
  530. if (remaining & 0b1000) return false;
  531. remaining |= 0b1000;
  532. break;
  533. case 105 /* i */:
  534. if (remaining & 0b0100) return false;
  535. remaining |= 0b0100;
  536. break;
  537. case 109 /* m */:
  538. if (remaining & 0b0010) return false;
  539. remaining |= 0b0010;
  540. break;
  541. case 121 /* y */:
  542. if (remaining & 0b0001) return false;
  543. remaining |= 0b0001;
  544. break;
  545. default:
  546. return false;
  547. }
  548. return true;
  549. };
  550. module.exports = BasicEvaluatedExpression;