tapEvaluateWithVariableInfo = (exprType, getInfo) => {
			/** @type {Expression | undefined} */
			let cachedExpression;
			/** @type {GetInfoResult | undefined} */
			let cachedInfo;
			this.hooks.evaluate.for(exprType).tap(CLASS_NAME, expr => {
				const expression =
					/** @type {Identifier | ThisExpression | MemberExpression} */ (expr);

				const info = getInfo(expression);
				if (info !== undefined) {
					return this.callHooksForInfoWithFallback(
						this.hooks.evaluateIdentifier,
						info.name,
						name => {
							cachedExpression = expression;
							cachedInfo = info;
							return undefined;
						},
						name => {
							const hook = this.hooks.evaluateDefinedIdentifier.get(name);
							if (hook !== undefined) {
								return hook.call(expression);
							}
						},
						expression
					);
				}
			});
			this.hooks.evaluate
				.for(exprType)
				.tap({ name: CLASS_NAME, stage: 100 }, expr => {
					const expression =
						/** @type {Identifier | ThisExpression | MemberExpression} */
						(expr);
					const info =
						cachedExpression === expression ? cachedInfo : getInfo(expression);
					if (info !== undefined) {
						return new BasicEvaluatedExpression()
							.setIdentifier(
								info.name,
								info.rootInfo,
								info.getMembers,
								info.getMembersOptionals,
								info.getMemberRanges
							)
							.setRange(/** @type {Range} */ (expression.range));
					}
				});
			this.hooks.finish.tap(CLASS_NAME, () => {
				// Cleanup for GC
				cachedExpression = cachedInfo = undefined;
			});
		}
@param {"Identifier" | "ThisExpression" | "MemberExpression"} exprType expression type name
@param {(node: Expression | SpreadElement) => GetInfoResult | undefined} getInfo get info
@returns {void}