Expose a getter as a simple variable name, ‘detached’ from parent object (without using a `with` statement)

Note: This question is not about ‘regular’ JavaScript, or best practices. It’s about “hacking” the JavaScript language as part of a DSL. I’m working on an update to the DSL which is backwards-compatible with the previous approach, so I need to faithfully match all the existing behavior. Please interpret this question in that light.


Question:

Let’s say we have an object with a getter like this:

let obj = {
  get foo() {
    console.log('executed');
    return 1;
  }
};

And our task is to somehow “pull foo out of obj” so that instead of writing obj.foo we can just write foo.

This is possible using a with statement:

<script>
  with(obj) {
    // example of user-land code, which I have no control over:
    console.log(foo + 1); // logs 'executed' and then '2'
    let bar = 3; // `bar` is trapped inside the `with` block, which is the thing I'm trying to avoid
  }
</script>

However, this creates a block scope, which I want to avoid because it traps variable declarations like let bar = 3; inside the block scope.

Important: I have full control over the construction of the above code. I’m simply given the user-land code (like the above 2 lines) as a string, and I can do whatever I want with it, so long as I expose the obj getters, and ensure that top-level declarations like let bar = 3 aren’t “trapped”. The existing solution involves ‘parsing’ the user code for top-level declarations, and pulling them out of the with ‘manually’, but I’d like to get rid of the with statement.

Thoughts:

I’m fairly sure this is not possible without something quite heavy-handed (e.g. runtime AST parsing stuff). This ability for with to expose a getter as a ‘direct’ variable name seems unique. I figured I’d ask just in case there’s a god-tier JS dev here who knows some dark magic that I don’t.

I don’t think Proxy is useful here, since there is no handler for simple ‘references’ to the Proxy object itself – only properties of it, or function calls on it, etc.

I’m not sure how relevant it will be to this question, since I don’t understand many of the details, but:

13.11 With Statement:

The with statement adds an object Environment Record for a computed object to the lexical environment of the running execution context. It then executes a statement using this augmented lexical environment. Finally, it restores the original lexical environment.

8.1.1 Environment Records:

There are two primary kinds of Environment Record values used in this specification: declarative Environment Records and object Environment Records. Declarative Environment Records are used to define the effect of ECMAScript language syntactic elements such as FunctionDeclarations, VariableDeclarations, and Catch clauses that directly associate identifier bindings with ECMAScript language values. Object Environment Records are used to define the effect of ECMAScript elements such as WithStatement that associate identifier bindings with the properties of some object. Global Environment Records and function Environment Records are specializations that are used for specifically for Script global declarations and for top-level declarations within functions.

The only alternative pathway available to me here is to come up with a robust way to “export”