mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			610 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			610 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| /*
 | |
| 	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;
 |