Andreas Tzionis
Andreas Tzionis

Reputation: 1086

How to access the return statement of a function in Javascript

I'm looking for a way to get the return expression of a function in Javascript. I'm expecting to get it as a string but any type would do. Also, I'm looking for a way to set the return statement of a function without affecting the rest of it.

Example:

let f1 = () => {
    let x = 1
    return 2 + x
}

let f2 = () => {
    let y = 2
    return 3 + y
}

let s = f1.getReturnExpression()
console.log(s) // 2 + x

f2.setReturnExpression(s)
console.log(f2.toString()) // "function f2() {  let y = 2; return 2 + x; }

Upvotes: 0

Views: 590

Answers (2)

customcommander
customcommander

Reputation: 18951

Extracting the return statements should be "easy" but I am not sure how you can reconstruct a new function and assign it to an existing function at runtime without damaging scope somehow.

Let's say that extracting return statements is enough for now. I would definitely not recommend using regular expressions or strings manipulation for that; you'll just get it wrong.

You should use tools like that allow you to deconstruct a piece of code into a data structure that you can inspect.

Given the following function:

function foo(x) {
  if (x) {
    return x + 10;
  } else {
    return x - 10;
  }
}

Pass it to esprima:

esprima.tokenize(foo.toString());

This is what you get:

[ { type: 'Keyword', value: 'function' },
  { type: 'Identifier', value: 'foo' },
  { type: 'Punctuator', value: '(' },
  { type: 'Identifier', value: 'x' },
  { type: 'Punctuator', value: ')' },
  { type: 'Punctuator', value: '{' },
  { type: 'Keyword', value: 'if' },
  { type: 'Punctuator', value: '(' },
  { type: 'Identifier', value: 'x' },
  { type: 'Punctuator', value: ')' },
  { type: 'Punctuator', value: '{' },
  { type: 'Keyword', value: 'return' },
  { type: 'Identifier', value: 'x' },
  { type: 'Punctuator', value: '+' },
  { type: 'Numeric', value: '10' },
  { type: 'Punctuator', value: ';' },
  { type: 'Punctuator', value: '}' },
  { type: 'Keyword', value: 'else' },
  { type: 'Punctuator', value: '{' },
  { type: 'Keyword', value: 'return' },
  { type: 'Identifier', value: 'x' },
  { type: 'Punctuator', value: '-' },
  { type: 'Numeric', value: '10' },
  { type: 'Punctuator', value: ';' },
  { type: 'Punctuator', value: '}' },
  { type: 'Punctuator', value: '}' } ]

This is function foo in a JavaScript data structure that you can manipulate in ... JavaScript...
A m a z i n g 🤓

Now it should be fairly obvious to work out how to extract the two possible return statements.
However, it is important to note that I do assume that each return statement ends with a semicolon ;.

To identify the start of a return statement we can write a isReturnStatement function:

const isReturnStatement = token =>
  token.type === 'Keyword' &&
  token.value === 'return';

And to identify the end of such statement, we can write a isEndOfStatement function:

const isEndOfStatement = token =>
  token.type === 'Punctuator' &&
  token.value === ';';

As you can see you're now dealing with a JavaScript data structure, an array of tokens. Each token representing a piece of code syntax.

You now need to identify the start of a return statement and its end. Save that and continue the process until you have inspected the entire array.

Here's one way to do it:

function foo(x) {
  if (x) {
    return x + 10;
  } else {
    return x - 10;
  }
}

const ast = esprima.tokenize(foo.toString());

console.log(

  getReturnStatements(ast)
  
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/esprima/2.7.3/esprima.min.js"></script>

<script>

const isReturnStatement = token =>
  token.type === 'Keyword' &&
  token.value === 'return';

const isEndOfStatement = token =>
  token.type === 'Punctuator' &&
  token.value === ';';

const getReturnStatements = ast => {
  const scan = ast.reduce((search, token) => {
    if (isReturnStatement(token)) {
      search.capture = true;
      search.stmt.push('');
    } else if (search.capture && !isEndOfStatement(token)) {
      search.stmt[search.stmt.length - 1] += token.value;
    } else {
      search.capture = false;
    }
    return search;
  }, {capture: false, stmt: []});
  return scan.stmt;
};

</script>

Note that for improved accuracy you should be parsing code instead of tokenizing it but I hope this gives you a good enough start.

Upvotes: 2

Laurent Dhont
Laurent Dhont

Reputation: 1062

I am not sure this is the best way to this but to get the return value you could simply do: functionName.toString().split('return ')[1].split(';')[0].

For changing the second function you could do: let secondFunction = new Function(string)

For string you would need to remove the {} and the value after return (this will need to be done with a regex and the replace method but I am not good at this.)

For removing the {} you could do: functionName.toString().slice(functionName.toString().indexOf('{') + 1, functionName.toString().lastIndexOf('}'))

I hope this helped.

Upvotes: 0

Related Questions