Selecting function calls
Function calls are at the heart of most programs, even outside of JavaScript. To understand how to select them effectively with SYNG, let's briefly revisit the components of a function call in JavaScript.
First, there is the callee, which is the reference to the function being called. This is often an identifier, like the x
in x()
, but technically it can be any valid JavaScript expression, like the member property b
in a.b()
or even the product of another function call like the x()
in x()()
.
Second, there are arguments, which can also be any valid expression but are positional as well.
(call)
selects function calls based on either component and has the following signature:
(call [callee?] [args?])
Callee
The first argument to (call)
selects the callee. As an example, the query (call (id x))
selects a call to the identifier x
, i.e. x()
. If we were looking for a different type of callee, like a.b()
in our previous example, we would substitute the (id x)
selector with one that fits: (mem)
.
This snippet shows different callees to press the point that they may be any expression:
(call x) ; x()
(call (call x)) ; x()()
(call (mem b a)) ; a.b()
(call (mem b (call a))) ; a().b()
Remember that the x
in (call x)
is shorthand for (call (id x))
. In real-world scenarios, you may need to select callees that are not identifiers as frequently as you would identifiers.
Arguments
The second argument to (call)
selects the arguments with which the call is made. This is done through the (arg)
selector, which selects the position of an argument and its value. It is similar to (el)
for selecting Array elements.
(arg)
has the following signature:
(arg [pos?] [val?])
If we wanted to refine the selection of calls to x
to ones that have a number passed as the first argument, the following query captures that:
(call x (arg 1 (num)))
When the position of an argument is not relevant, use the placeholder atom to disregard it, basically asking for "a call to the identifier x made with a number":
(call x (arg _ (num)))
Likewise for the callee, when you only care about the arguments, you can use the placeholder atom to disregard it:
(call _ (arg 1 num))
Which will match both x(1)
and a.b(1)
.
Selecting a call by multiple arguments
Remember that even though (call)
accepts a single selector for its set of arguments, we can make use of logical operators like (:and)
and (:or)
to combine several (arg)
selectors.
(call x (:and (arg 1 (str))
(arg 2 (obj))))
Please refer to the logical operators chapter for details.
Ignoring a call based on its arguments
Just as (arg)
allowed us to select a specific call made using a specific argument, we can use it conversely to ignore calls made using a specific argument through the (:not)
operator.
To use our previous example, we could look for a call to x
that is not made with any number:
(call x (:not (arg _ (num))))
Again, this behavior is consistent with what we've seen before in selecting Array and Object literals.