1

I have simple JavScript REPL:

function(command) {
     if (command !== '') {
         try {
             var result = window.eval(command);
             if (result) {
                 if (result instanceof $.fn.init) {
                     this.echo(`#<jQuery>`);
                 } else if (typeof result === 'object') {
                     var name = result.constructor.name;
                     if (name) {
                         this.echo(`#<${name}>`);
                     } else {
                         this.echo(result.toString());
                     }
                 } else {
                     this.echo(new String(result));
                 }
             }
         } catch(e) {
             this.error(new String(e));
         }
     }
 }

But it doesn't work with let or const:

js> let x = 10
js> x
ReferenceError: x is not defined
js> 

Is there a hack that will make this work? I have no idea how Google Chrome Dev Tools make it work. It will probably take a long time to read the code. Just want to ask before I will possibly spend a lot of time reading it's code.

I know that I can just replace let with var before evaluation, but I don't like this solution.

EDIT: the answers to duplicated question only show why let and const doesn't work in eval, but don't answer how to make eval work with let or const, like in Dev Tools. My question is: how to make eval work with let and const.

jcubic
  • 61,973
  • 54
  • 229
  • 402
  • I have no idea who vote to close the question. This is single question how to make `window.eval` work with `let` and `const`? I don't see any other question here. – jcubic Nov 28 '21 at 15:39
  • You don't, for the reasons stated. The dev console doesn't use `eval` in the same way a userscript does (if it does at all). – Dave Newton Nov 28 '21 at 15:57

1 Answers1

1

Someone posted on Reddit this code:

var __EVAL = (s) => eval(`void (__EVAL = ${__EVAL}); ${s}`);

__EVAL("const a = 42;"); // undefined
__EVAL("a;"); // 42

And it do exactly what I need.

The source of this code came from answer to this question: Context-preserving eval (as noted by the OP on Reddit).

EDIT:

For future reference, because I will delete this code, I'm posting the solution I was using:

 const context = {
     __$$__: {},
     ...globals(),
     $,
     console: {
         log(...args) {
             console.log(...args);
             term.echo(args.map(repr).join(' '));
         }
     }
 };
 function unset(context, name) {
     delete context[name];
 }
 function exec(code, ctx = context) {
     ctx = {...ctx};
     code = patch(code, ctx)
     return vm.runInNewContext(code, ctx);
 }

 function is_scoped(name, context) {
     return name in context.__$$__;
 }
 function patch(code, context) {
     let patching = [];
     let result = falafel(code, function(node) {
         if (node.type == 'VariableDeclaration') {

             node.declarations.forEach(declaration => {
                 const name = declaration.id.name;
                 if (is_scoped(name, context)) {
                     unset(context, name);
                 }
                 patching.push(`__$$__.${name} = ${name}`);
             });
         } else if (node.type === 'Identifier' &&
                    is_scoped(node.name, context) &&
                    node.parent.type !== 'VariableDeclarator') {
             const name = node.name;
             node.update(`__$$__.${name}`);
         }
     });
     result = result.toString();
     if (patching.length) {
         result += ';' + patching.join(';');
     }
     return result;
 }

Using vm node module (part of browserify) and node-falafel converted using browserify as well.

jcubic
  • 61,973
  • 54
  • 229
  • 402