BasicEvaluatedExpression.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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;
  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. }
  230. /**
  231. * Creates a nullish coalescing representation of this evaluated expression.
  232. * @returns {boolean | undefined} true: nullish, false: not nullish, undefined: unknown
  233. */
  234. asNullish() {
  235. const nullish = this.isNullish();
  236. if (nullish === true || this.isNull() || this.isUndefined()) return true;
  237. if (nullish === false) return false;
  238. if (this.isTruthy()) return false;
  239. if (this.isBoolean()) return false;
  240. if (this.isString()) return false;
  241. if (this.isNumber()) return false;
  242. if (this.isBigInt()) return false;
  243. if (this.isRegExp()) return false;
  244. if (this.isArray()) return false;
  245. if (this.isConstArray()) return false;
  246. if (this.isTemplateString()) return false;
  247. if (this.isRegExp()) return false;
  248. }
  249. /**
  250. * Creates a string representation of this evaluated expression.
  251. * @returns {string | undefined} the string representation or undefined if not possible
  252. */
  253. asString() {
  254. if (this.isBoolean()) return `${this.bool}`;
  255. if (this.isNull()) return "null";
  256. if (this.isUndefined()) return "undefined";
  257. if (this.isString()) return this.string;
  258. if (this.isNumber()) return `${this.number}`;
  259. if (this.isBigInt()) return `${this.bigint}`;
  260. if (this.isRegExp()) return `${this.regExp}`;
  261. if (this.isArray()) {
  262. const array = [];
  263. for (const item of /** @type {BasicEvaluatedExpression[]} */ (
  264. this.items
  265. )) {
  266. const itemStr = item.asString();
  267. if (itemStr === undefined) return;
  268. array.push(itemStr);
  269. }
  270. return `${array}`;
  271. }
  272. if (this.isConstArray()) return `${this.array}`;
  273. if (this.isTemplateString()) {
  274. let str = "";
  275. for (const part of /** @type {BasicEvaluatedExpression[]} */ (
  276. this.parts
  277. )) {
  278. const partStr = part.asString();
  279. if (partStr === undefined) return;
  280. str += partStr;
  281. }
  282. return str;
  283. }
  284. }
  285. /**
  286. * @param {string} string value
  287. * @returns {BasicEvaluatedExpression} basic evaluated expression
  288. */
  289. setString(string) {
  290. this.type = TypeString;
  291. this.string = string;
  292. this.sideEffects = false;
  293. return this;
  294. }
  295. setUndefined() {
  296. this.type = TypeUndefined;
  297. this.sideEffects = false;
  298. return this;
  299. }
  300. setNull() {
  301. this.type = TypeNull;
  302. this.sideEffects = false;
  303. return this;
  304. }
  305. /**
  306. * Set's the value of this expression to a number
  307. * @param {number} number number to set
  308. * @returns {this} this
  309. */
  310. setNumber(number) {
  311. this.type = TypeNumber;
  312. this.number = number;
  313. this.sideEffects = false;
  314. return this;
  315. }
  316. /**
  317. * Set's the value of this expression to a BigInt
  318. * @param {bigint} bigint bigint to set
  319. * @returns {this} this
  320. */
  321. setBigInt(bigint) {
  322. this.type = TypeBigInt;
  323. this.bigint = bigint;
  324. this.sideEffects = false;
  325. return this;
  326. }
  327. /**
  328. * Set's the value of this expression to a boolean
  329. * @param {boolean} bool boolean to set
  330. * @returns {this} this
  331. */
  332. setBoolean(bool) {
  333. this.type = TypeBoolean;
  334. this.bool = bool;
  335. this.sideEffects = false;
  336. return this;
  337. }
  338. /**
  339. * Set's the value of this expression to a regular expression
  340. * @param {RegExp} regExp regular expression to set
  341. * @returns {this} this
  342. */
  343. setRegExp(regExp) {
  344. this.type = TypeRegExp;
  345. this.regExp = regExp;
  346. this.sideEffects = false;
  347. return this;
  348. }
  349. /**
  350. * Set's the value of this expression to a particular identifier and its members.
  351. * @param {string | VariableInfoInterface} identifier identifier to set
  352. * @param {string | VariableInfoInterface} rootInfo root info
  353. * @param {() => string[]} getMembers members
  354. * @param {() => boolean[]=} getMembersOptionals optional members
  355. * @param {() => Range[]=} getMemberRanges ranges of progressively increasing sub-expressions
  356. * @returns {this} this
  357. */
  358. setIdentifier(
  359. identifier,
  360. rootInfo,
  361. getMembers,
  362. getMembersOptionals,
  363. getMemberRanges
  364. ) {
  365. this.type = TypeIdentifier;
  366. this.identifier = identifier;
  367. this.rootInfo = rootInfo;
  368. this.getMembers = getMembers;
  369. this.getMembersOptionals = getMembersOptionals;
  370. this.getMemberRanges = getMemberRanges;
  371. this.sideEffects = true;
  372. return this;
  373. }
  374. /**
  375. * Wraps an array of expressions with a prefix and postfix expression.
  376. * @param {BasicEvaluatedExpression | null | undefined} prefix Expression to be added before the innerExpressions
  377. * @param {BasicEvaluatedExpression | null | undefined} postfix Expression to be added after the innerExpressions
  378. * @param {BasicEvaluatedExpression[] | undefined} innerExpressions Expressions to be wrapped
  379. * @returns {this} this
  380. */
  381. setWrapped(prefix, postfix, innerExpressions) {
  382. this.type = TypeWrapped;
  383. this.prefix = prefix;
  384. this.postfix = postfix;
  385. this.wrappedInnerExpressions = innerExpressions;
  386. this.sideEffects = true;
  387. return this;
  388. }
  389. /**
  390. * Stores the options of a conditional expression.
  391. * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be set
  392. * @returns {this} this
  393. */
  394. setOptions(options) {
  395. this.type = TypeConditional;
  396. this.options = options;
  397. this.sideEffects = true;
  398. return this;
  399. }
  400. /**
  401. * Adds options to a conditional expression.
  402. * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be added
  403. * @returns {this} this
  404. */
  405. addOptions(options) {
  406. if (!this.options) {
  407. this.type = TypeConditional;
  408. this.options = [];
  409. this.sideEffects = true;
  410. }
  411. for (const item of options) {
  412. this.options.push(item);
  413. }
  414. return this;
  415. }
  416. /**
  417. * Set's the value of this expression to an array of expressions.
  418. * @param {BasicEvaluatedExpression[]} items expressions to set
  419. * @returns {this} this
  420. */
  421. setItems(items) {
  422. this.type = TypeArray;
  423. this.items = items;
  424. this.sideEffects = items.some(i => i.couldHaveSideEffects());
  425. return this;
  426. }
  427. /**
  428. * Set's the value of this expression to an array of strings.
  429. * @param {string[]} array array to set
  430. * @returns {this} this
  431. */
  432. setArray(array) {
  433. this.type = TypeConstArray;
  434. this.array = array;
  435. this.sideEffects = false;
  436. return this;
  437. }
  438. /**
  439. * Set's the value of this expression to a processed/unprocessed template string. Used
  440. * for evaluating TemplateLiteral expressions in the JavaScript Parser.
  441. * @param {BasicEvaluatedExpression[]} quasis template string quasis
  442. * @param {BasicEvaluatedExpression[]} parts template string parts
  443. * @param {"cooked" | "raw"} kind template string kind
  444. * @returns {this} this
  445. */
  446. setTemplateString(quasis, parts, kind) {
  447. this.type = TypeTemplateString;
  448. this.quasis = quasis;
  449. this.parts = parts;
  450. this.templateStringKind = kind;
  451. this.sideEffects = parts.some(p => p.sideEffects);
  452. return this;
  453. }
  454. setTruthy() {
  455. this.falsy = false;
  456. this.truthy = true;
  457. this.nullish = false;
  458. return this;
  459. }
  460. setFalsy() {
  461. this.falsy = true;
  462. this.truthy = false;
  463. return this;
  464. }
  465. /**
  466. * Set's the value of the expression to nullish.
  467. * @param {boolean} value true, if the expression is nullish
  468. * @returns {this} this
  469. */
  470. setNullish(value) {
  471. this.nullish = value;
  472. if (value) return this.setFalsy();
  473. return this;
  474. }
  475. /**
  476. * Set's the range for the expression.
  477. * @param {[number, number]} range range to set
  478. * @returns {this} this
  479. */
  480. setRange(range) {
  481. this.range = range;
  482. return this;
  483. }
  484. /**
  485. * Set whether or not the expression has side effects.
  486. * @param {boolean} sideEffects true, if the expression has side effects
  487. * @returns {this} this
  488. */
  489. setSideEffects(sideEffects = true) {
  490. this.sideEffects = sideEffects;
  491. return this;
  492. }
  493. /**
  494. * Set the expression node for the expression.
  495. * @param {Node | undefined} expression expression
  496. * @returns {this} this
  497. */
  498. setExpression(expression) {
  499. this.expression = expression;
  500. return this;
  501. }
  502. }
  503. /**
  504. * @param {string} flags regexp flags
  505. * @returns {boolean} is valid flags
  506. */
  507. BasicEvaluatedExpression.isValidRegExpFlags = flags => {
  508. const len = flags.length;
  509. if (len === 0) return true;
  510. if (len > 4) return false;
  511. // cspell:word gimy
  512. let remaining = 0b0000; // bit per RegExp flag: gimy
  513. for (let i = 0; i < len; i++)
  514. switch (flags.charCodeAt(i)) {
  515. case 103 /* g */:
  516. if (remaining & 0b1000) return false;
  517. remaining |= 0b1000;
  518. break;
  519. case 105 /* i */:
  520. if (remaining & 0b0100) return false;
  521. remaining |= 0b0100;
  522. break;
  523. case 109 /* m */:
  524. if (remaining & 0b0010) return false;
  525. remaining |= 0b0010;
  526. break;
  527. case 121 /* y */:
  528. if (remaining & 0b0001) return false;
  529. remaining |= 0b0001;
  530. break;
  531. default:
  532. return false;
  533. }
  534. return true;
  535. };
  536. module.exports = BasicEvaluatedExpression;