123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- /** @typedef {import("estree").Node} Node */
- /** @typedef {import("./JavascriptParser").Range} Range */
- /** @typedef {import("./JavascriptParser").VariableInfoInterface} VariableInfoInterface */
- const TypeUnknown = 0;
- const TypeUndefined = 1;
- const TypeNull = 2;
- const TypeString = 3;
- const TypeNumber = 4;
- const TypeBoolean = 5;
- const TypeRegExp = 6;
- const TypeConditional = 7;
- const TypeArray = 8;
- const TypeConstArray = 9;
- const TypeIdentifier = 10;
- const TypeWrapped = 11;
- const TypeTemplateString = 12;
- const TypeBigInt = 13;
- class BasicEvaluatedExpression {
- constructor() {
- this.type = TypeUnknown;
- /** @type {Range | undefined} */
- this.range = undefined;
- /** @type {boolean} */
- this.falsy = false;
- /** @type {boolean} */
- this.truthy = false;
- /** @type {boolean | undefined} */
- this.nullish = undefined;
- /** @type {boolean} */
- this.sideEffects = true;
- /** @type {boolean | undefined} */
- this.bool = undefined;
- /** @type {number | undefined} */
- this.number = undefined;
- /** @type {bigint | undefined} */
- this.bigint = undefined;
- /** @type {RegExp | undefined} */
- this.regExp = undefined;
- /** @type {string | undefined} */
- this.string = undefined;
- /** @type {BasicEvaluatedExpression[] | undefined} */
- this.quasis = undefined;
- /** @type {BasicEvaluatedExpression[] | undefined} */
- this.parts = undefined;
- /** @type {any[] | undefined} */
- this.array = undefined;
- /** @type {BasicEvaluatedExpression[] | undefined} */
- this.items = undefined;
- /** @type {BasicEvaluatedExpression[] | undefined} */
- this.options = undefined;
- /** @type {BasicEvaluatedExpression | undefined | null} */
- this.prefix = undefined;
- /** @type {BasicEvaluatedExpression | undefined | null} */
- this.postfix = undefined;
- /** @type {BasicEvaluatedExpression[] | undefined} */
- this.wrappedInnerExpressions = undefined;
- /** @type {string | VariableInfoInterface | undefined} */
- this.identifier = undefined;
- /** @type {string | VariableInfoInterface | undefined} */
- this.rootInfo = undefined;
- /** @type {(() => string[]) | undefined} */
- this.getMembers = undefined;
- /** @type {(() => boolean[]) | undefined} */
- this.getMembersOptionals = undefined;
- /** @type {(() => Range[]) | undefined} */
- this.getMemberRanges = undefined;
- /** @type {Node | undefined} */
- this.expression = undefined;
- }
- isUnknown() {
- return this.type === TypeUnknown;
- }
- isNull() {
- return this.type === TypeNull;
- }
- isUndefined() {
- return this.type === TypeUndefined;
- }
- isString() {
- return this.type === TypeString;
- }
- isNumber() {
- return this.type === TypeNumber;
- }
- isBigInt() {
- return this.type === TypeBigInt;
- }
- isBoolean() {
- return this.type === TypeBoolean;
- }
- isRegExp() {
- return this.type === TypeRegExp;
- }
- isConditional() {
- return this.type === TypeConditional;
- }
- isArray() {
- return this.type === TypeArray;
- }
- isConstArray() {
- return this.type === TypeConstArray;
- }
- isIdentifier() {
- return this.type === TypeIdentifier;
- }
- isWrapped() {
- return this.type === TypeWrapped;
- }
- isTemplateString() {
- return this.type === TypeTemplateString;
- }
- /**
- * Is expression a primitive or an object type value?
- * @returns {boolean | undefined} true: primitive type, false: object type, undefined: unknown/runtime-defined
- */
- isPrimitiveType() {
- switch (this.type) {
- case TypeUndefined:
- case TypeNull:
- case TypeString:
- case TypeNumber:
- case TypeBoolean:
- case TypeBigInt:
- case TypeWrapped:
- case TypeTemplateString:
- return true;
- case TypeRegExp:
- case TypeArray:
- case TypeConstArray:
- return false;
- default:
- return undefined;
- }
- }
- /**
- * Is expression a runtime or compile-time value?
- * @returns {boolean} true: compile time value, false: runtime value
- */
- isCompileTimeValue() {
- switch (this.type) {
- case TypeUndefined:
- case TypeNull:
- case TypeString:
- case TypeNumber:
- case TypeBoolean:
- case TypeRegExp:
- case TypeConstArray:
- case TypeBigInt:
- return true;
- default:
- return false;
- }
- }
- /**
- * Gets the compile-time value of the expression
- * @returns {any} the javascript value
- */
- asCompileTimeValue() {
- switch (this.type) {
- case TypeUndefined:
- return undefined;
- case TypeNull:
- return null;
- case TypeString:
- return this.string;
- case TypeNumber:
- return this.number;
- case TypeBoolean:
- return this.bool;
- case TypeRegExp:
- return this.regExp;
- case TypeConstArray:
- return this.array;
- case TypeBigInt:
- return this.bigint;
- default:
- throw new Error(
- "asCompileTimeValue must only be called for compile-time values"
- );
- }
- }
- isTruthy() {
- return this.truthy;
- }
- isFalsy() {
- return this.falsy;
- }
- isNullish() {
- return this.nullish;
- }
- /**
- * Can this expression have side effects?
- * @returns {boolean} false: never has side effects
- */
- couldHaveSideEffects() {
- return this.sideEffects;
- }
- /**
- * Creates a boolean representation of this evaluated expression.
- * @returns {boolean | undefined} true: truthy, false: falsy, undefined: unknown
- */
- asBool() {
- if (this.truthy) return true;
- if (this.falsy || this.nullish) return false;
- if (this.isBoolean()) return this.bool;
- if (this.isNull()) return false;
- if (this.isUndefined()) return false;
- if (this.isString()) return this.string !== "";
- if (this.isNumber()) return this.number !== 0;
- if (this.isBigInt()) return this.bigint !== BigInt(0);
- if (this.isRegExp()) return true;
- if (this.isArray()) return true;
- if (this.isConstArray()) return true;
- if (this.isWrapped()) {
- return (this.prefix && this.prefix.asBool()) ||
- (this.postfix && this.postfix.asBool())
- ? true
- : undefined;
- }
- if (this.isTemplateString()) {
- const str = this.asString();
- if (typeof str === "string") return str !== "";
- }
- return undefined;
- }
- /**
- * Creates a nullish coalescing representation of this evaluated expression.
- * @returns {boolean | undefined} true: nullish, false: not nullish, undefined: unknown
- */
- asNullish() {
- const nullish = this.isNullish();
- if (nullish === true || this.isNull() || this.isUndefined()) return true;
- if (nullish === false) return false;
- if (this.isTruthy()) return false;
- if (this.isBoolean()) return false;
- if (this.isString()) return false;
- if (this.isNumber()) return false;
- if (this.isBigInt()) return false;
- if (this.isRegExp()) return false;
- if (this.isArray()) return false;
- if (this.isConstArray()) return false;
- if (this.isTemplateString()) return false;
- if (this.isRegExp()) return false;
- return undefined;
- }
- /**
- * Creates a string representation of this evaluated expression.
- * @returns {string | undefined} the string representation or undefined if not possible
- */
- asString() {
- if (this.isBoolean()) return `${this.bool}`;
- if (this.isNull()) return "null";
- if (this.isUndefined()) return "undefined";
- if (this.isString()) return this.string;
- if (this.isNumber()) return `${this.number}`;
- if (this.isBigInt()) return `${this.bigint}`;
- if (this.isRegExp()) return `${this.regExp}`;
- if (this.isArray()) {
- let array = [];
- for (const item of /** @type {BasicEvaluatedExpression[]} */ (
- this.items
- )) {
- const itemStr = item.asString();
- if (itemStr === undefined) return undefined;
- array.push(itemStr);
- }
- return `${array}`;
- }
- if (this.isConstArray()) return `${this.array}`;
- if (this.isTemplateString()) {
- let str = "";
- for (const part of /** @type {BasicEvaluatedExpression[]} */ (
- this.parts
- )) {
- const partStr = part.asString();
- if (partStr === undefined) return undefined;
- str += partStr;
- }
- return str;
- }
- return undefined;
- }
- /**
- * @param {string} string value
- * @returns {BasicEvaluatedExpression} basic evaluated expression
- */
- setString(string) {
- this.type = TypeString;
- this.string = string;
- this.sideEffects = false;
- return this;
- }
- setUndefined() {
- this.type = TypeUndefined;
- this.sideEffects = false;
- return this;
- }
- setNull() {
- this.type = TypeNull;
- this.sideEffects = false;
- return this;
- }
- /**
- * Set's the value of this expression to a number
- * @param {number} number number to set
- * @returns {this} this
- */
- setNumber(number) {
- this.type = TypeNumber;
- this.number = number;
- this.sideEffects = false;
- return this;
- }
- /**
- * Set's the value of this expression to a BigInt
- * @param {bigint} bigint bigint to set
- * @returns {this} this
- */
- setBigInt(bigint) {
- this.type = TypeBigInt;
- this.bigint = bigint;
- this.sideEffects = false;
- return this;
- }
- /**
- * Set's the value of this expression to a boolean
- * @param {boolean} bool boolean to set
- * @returns {this} this
- */
- setBoolean(bool) {
- this.type = TypeBoolean;
- this.bool = bool;
- this.sideEffects = false;
- return this;
- }
- /**
- * Set's the value of this expression to a regular expression
- * @param {RegExp} regExp regular expression to set
- * @returns {this} this
- */
- setRegExp(regExp) {
- this.type = TypeRegExp;
- this.regExp = regExp;
- this.sideEffects = false;
- return this;
- }
- /**
- * Set's the value of this expression to a particular identifier and its members.
- *
- * @param {string | VariableInfoInterface} identifier identifier to set
- * @param {string | VariableInfoInterface} rootInfo root info
- * @param {() => string[]} getMembers members
- * @param {() => boolean[]=} getMembersOptionals optional members
- * @param {() => Range[]=} getMemberRanges ranges of progressively increasing sub-expressions
- * @returns {this} this
- */
- setIdentifier(
- identifier,
- rootInfo,
- getMembers,
- getMembersOptionals,
- getMemberRanges
- ) {
- this.type = TypeIdentifier;
- this.identifier = identifier;
- this.rootInfo = rootInfo;
- this.getMembers = getMembers;
- this.getMembersOptionals = getMembersOptionals;
- this.getMemberRanges = getMemberRanges;
- this.sideEffects = true;
- return this;
- }
- /**
- * Wraps an array of expressions with a prefix and postfix expression.
- *
- * @param {BasicEvaluatedExpression | null | undefined} prefix Expression to be added before the innerExpressions
- * @param {BasicEvaluatedExpression | null | undefined} postfix Expression to be added after the innerExpressions
- * @param {BasicEvaluatedExpression[] | undefined} innerExpressions Expressions to be wrapped
- * @returns {this} this
- */
- setWrapped(prefix, postfix, innerExpressions) {
- this.type = TypeWrapped;
- this.prefix = prefix;
- this.postfix = postfix;
- this.wrappedInnerExpressions = innerExpressions;
- this.sideEffects = true;
- return this;
- }
- /**
- * Stores the options of a conditional expression.
- *
- * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be set
- * @returns {this} this
- */
- setOptions(options) {
- this.type = TypeConditional;
- this.options = options;
- this.sideEffects = true;
- return this;
- }
- /**
- * Adds options to a conditional expression.
- *
- * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be added
- * @returns {this} this
- */
- addOptions(options) {
- if (!this.options) {
- this.type = TypeConditional;
- this.options = [];
- this.sideEffects = true;
- }
- for (const item of options) {
- this.options.push(item);
- }
- return this;
- }
- /**
- * Set's the value of this expression to an array of expressions.
- *
- * @param {BasicEvaluatedExpression[]} items expressions to set
- * @returns {this} this
- */
- setItems(items) {
- this.type = TypeArray;
- this.items = items;
- this.sideEffects = items.some(i => i.couldHaveSideEffects());
- return this;
- }
- /**
- * Set's the value of this expression to an array of strings.
- *
- * @param {string[]} array array to set
- * @returns {this} this
- */
- setArray(array) {
- this.type = TypeConstArray;
- this.array = array;
- this.sideEffects = false;
- return this;
- }
- /**
- * Set's the value of this expression to a processed/unprocessed template string. Used
- * for evaluating TemplateLiteral expressions in the JavaScript Parser.
- *
- * @param {BasicEvaluatedExpression[]} quasis template string quasis
- * @param {BasicEvaluatedExpression[]} parts template string parts
- * @param {"cooked" | "raw"} kind template string kind
- * @returns {this} this
- */
- setTemplateString(quasis, parts, kind) {
- this.type = TypeTemplateString;
- this.quasis = quasis;
- this.parts = parts;
- this.templateStringKind = kind;
- this.sideEffects = parts.some(p => p.sideEffects);
- return this;
- }
- setTruthy() {
- this.falsy = false;
- this.truthy = true;
- this.nullish = false;
- return this;
- }
- setFalsy() {
- this.falsy = true;
- this.truthy = false;
- return this;
- }
- /**
- * Set's the value of the expression to nullish.
- *
- * @param {boolean} value true, if the expression is nullish
- * @returns {this} this
- */
- setNullish(value) {
- this.nullish = value;
- if (value) return this.setFalsy();
- return this;
- }
- /**
- * Set's the range for the expression.
- *
- * @param {[number, number]} range range to set
- * @returns {this} this
- */
- setRange(range) {
- this.range = range;
- return this;
- }
- /**
- * Set whether or not the expression has side effects.
- *
- * @param {boolean} sideEffects true, if the expression has side effects
- * @returns {this} this
- */
- setSideEffects(sideEffects = true) {
- this.sideEffects = sideEffects;
- return this;
- }
- /**
- * Set the expression node for the expression.
- *
- * @param {Node | undefined} expression expression
- * @returns {this} this
- */
- setExpression(expression) {
- this.expression = expression;
- return this;
- }
- }
- /**
- * @param {string} flags regexp flags
- * @returns {boolean} is valid flags
- */
- BasicEvaluatedExpression.isValidRegExpFlags = flags => {
- const len = flags.length;
- if (len === 0) return true;
- if (len > 4) return false;
- // cspell:word gimy
- let remaining = 0b0000; // bit per RegExp flag: gimy
- for (let i = 0; i < len; i++)
- switch (flags.charCodeAt(i)) {
- case 103 /* g */:
- if (remaining & 0b1000) return false;
- remaining |= 0b1000;
- break;
- case 105 /* i */:
- if (remaining & 0b0100) return false;
- remaining |= 0b0100;
- break;
- case 109 /* m */:
- if (remaining & 0b0010) return false;
- remaining |= 0b0010;
- break;
- case 121 /* y */:
- if (remaining & 0b0001) return false;
- remaining |= 0b0001;
- break;
- default:
- return false;
- }
- return true;
- };
- module.exports = BasicEvaluatedExpression;
|