function extract_all_identifiers_from_expression(expr) {
	/** @type {ESTree.Identifier[]} */
	let nodes = [];
	/** @type {string[]} */
	let keypath = [];

	walk(
		expr,
		{},
		{
			Identifier(node, { path }) {
				const parent = path.at(-1);
				if (parent?.type !== 'MemberExpression' || parent.property !== node || parent.computed) {
					nodes.push(node);
				}

				if (parent?.type === 'MemberExpression' && parent.computed && parent.property === node) {
					keypath.push(`[${node.name}]`);
				} else {
					keypath.push(node.name);
				}
			},
			Literal(node, { path }) {
				const value = typeof node.value === 'string' ? `"${node.value}"` : String(node.value);
				const parent = path.at(-1);
				if (parent?.type === 'MemberExpression' && parent.computed && parent.property === node) {
					keypath.push(`[${value}]`);
				} else {
					keypath.push(value);
				}
			},
			ThisExpression(_, { next }) {
				keypath.push('this');
				next();
			}
		}
	);

	return [keypath.join('.'), nodes];
}
Extracts all identifiers and a stringified keypath from an expression.
TODO replace this with `expression.dependencies`
@param {ESTree.Expression} expr
@returns {[keypath: string, ids: ESTree.Identifier[]]}