Squery stands for “Selector Query” and uses CSS style selectors to query a abstract syntax tree (AST). It is the default query engine for Grasp, the other being equery. If for some reason your query engine is set to equery, you can set it back to squery with -s, --squery
.
Selectors
There are several types of selectors:
*
- the wildcard - matches any node.
:root
- matches the root node - in most cases this will match the Program
node at the root of JavaScript ASTs.
Node Type
node-type
- node type - eg. if
or IfStatement
- matches a node of the specified type
You can see all available node types on the JavaScript syntax page.
If for some reason the node type cannot be the first thing in a compound statement, you can also use the alternate syntax: ::node-type
.
Identifiers
#name
- identifier - eg. #window
- matches an identifier with the specified name
#/re/
matches an identifier whose name passes the specified regex. For example #/^f/
matches foo
.
ident
or Identifier
matches all identifiers in general.
Literals
2
, 'hi'
, /re/g
, true
, null
, etc. - matches the specified literal.
num
or Number
matches all number literals.
str
or String
matches all string literals.
bool
or Boolean
matches as boolean literals.
regex
or RegExp
matches all regex literals.
null
matches all null literals.
literal
or Literal
matches all literals in general.
Attributes
Attribute selectors match nodes whose attributes match the specified test, not the value of the attributes themselves. To select an attribute of a node, use a property selector, eg. node.attribute
.
You can see all available attributes on the JavaScript syntax page. There are three types of attributes, those which contain other nodes, those which contain arrays of other nodes, and those which contain primitive values (literals such as true
, 34
, etc.). We call attributes which contain either nodes, or arrays of nodes “complex attributes”, and those which contain primitive values “primitive attributes”.
For instance, the update expression node has a primitive attribute called prefix
, which contains a boolean specifying if the operator is prefix or postfix. Its argument
attribute however is a complex one, as it consists of another node.
[attr]
matches a node which has the attribute attr
eg. [left]
matches 2 + 3
in the code 2 + 3
.
[attr=value]
- eg. [left=2]
- matches a node which which has the attribute attr
with the value value
.
You can specify sub attributes: eg. [left.value]
, [left.value=foo]
.
You can specify either complex or primitive attributes when using the attribute selector, eg. [prefix=true]
will match for ++x
, and [left=true]
will match for true && false
. Some attributes such as value
can be either complex (when part of a property) or primitive (when part of a literal). In this case either will work.
If you want to only match primitive attributes, you can add a &
before your attribute name, eg. [&value=2]
.
You can use either the =
equals or !=
not equals operators when searching for complex attributes.
For primitive attributes, you can use either of those, or the <
less than, >
greater than, <=
less than or equal to, >=
greater to or equal to, or ~=
regex test operators.
For example, [value<2]
matches the literals 1
and 0
. [value~=/^hi/]
matches the literal "hi there"
.
There is also the type
operator, that checks a primitive attribute’s type. For example, [value=type(Number)]
matches the literals 2
and 8.2
, but not true
, or "hi"
. The types you can use are the same as those specified in the literals section.
You can specify whatever selectors you want when searching complex attributes. For example, [object=arr #x]
will match [y, x].length
.
Compound
To match node which pass multiple selectors, you put them together without a space.
For example: if[test=bi][else]
matches as if statement whose test is a binary expression and has an else
.
Matches
If you want to match one selector or another, you can use :matches(selectors...)
. For example, :matches(bi, call)
matches either a binary expression or a call expression.
You don’t need the :matches
part actually, you can simply use (bi, call)
instead.
At the top level, you don’t even need the parentheses, you can simply do ident, if.test
.
Newlines are like commas, so if you are getting your selector from a file, you will match either the first line, or the second, or the third, etc.
For example, if this was your selector file:
literal
bi[op=+]
The one line equivalent would be (literal, bi[op=+])
.
Complex
There are several types of complex selectors.
The descendant selector: ancestor descendant
matches any node that falls anywhere underneath the ancestor
node that matches the descendant
selector.
The child selector: parent > child
matches any direct child of the parent
which matches the child
selector.
The sibling selector: before ~ sibling
matches any node which has the same parent as before
and appears after before
, that matches the sibling
selector.
The adjacent selector: before + adjacent
matches any node that has the same parent as before
, and appears directly after before
, which matches the sibling
selector.
And the property selector which warrants its own section.
Nodes can have both attributes which are other nodes, or attributes which contain arrays of other nodes. The previous selectors treat a node which is in an array of other nodes as a direct child of that node. For instance, an array expression contains the attribute elements
which is an array of its elements. For the code [1, 2, 3]
the selector arr > 1
will match 1
.
If you leave out the left hand side of any of these selectors, :root
is assumed. If you leave out the right hand side of any of these selectors, *
wildcard is assumed.
Property
The property selector node.attribute
matches the node contained in the specified attribute, or if the attribute is an array containing multiple nodes, will match all the nodes in the array.
You have several tools at your disposal when you access an array of nodes using the property selector. For the following examples, the code [1, 2, 3, 4]
will be used:
:first
or :head
matches the first element - eg. arr.elements:first
matches 1
.
:tail
matches all but the first element - eg. arr.elements:tail
matches 2
, 3
, and 4
.
:last
matches the last element - eg. arr.elements:last
matches 4
.
:initial
matches all the elements but the last - eg. arr.elements:initial
matches 1
and 2
.
:nth(Int)
matches the nth element, using zero based indexing - eg. arr.elements:nth(2)
matches 3
.
:nth-last(Int)
matches the nth last element, using zero based indexing - eg. arr.elements:nth-last(2)
matches 2
.
Finally, :slice(Int, Maybe Int)
matches the elements in the slice (same behavior as JavaScript’s slice array method) - eg. arr.elements:slice(1, 3)
matches 2
and 3
.
Pseudo
There are several pseudo selectors.
:first-child
matches a node which is the first child of some parent node.
:nth-child(Int)
(zero based indexing) matches the nth node of some parent node.
:last-child
matches the last node of some parent node.
:last-nth-child
(zero based indexing) matches the nth last node of some parent node.
Subject
By default, the last element in your selector is the subject, and will be matched. You can change this by appending a !
bang to part of your selector.
For example:
if! #x
matches all if statements that have the idenfitier x
as a descendant.
You could be more specific and do if!.test #x
, which matches all if statements that have the identifier x
in their test
attribute.
You can have multiple subjects, and each subject you specify will be matched.
For example, if! #x!
will match both if statements that have the identifier x
as a descendant, and identifiers x
who are descendants of if statements.
Not
To negate some portion of a query, use :not(selector)
.
It can take multiple arguments, eg. :not(ident, call)
.
For example while[test=:not(bi)]
matches while statements whose tests are not binary expressions.
Example
Putting a couple things we have learned together, here is the selector for an immediately-invoked function expression.
These can take for form of:
(function(){ ... })();
(function(){ ... }).call(...);
(function(){ ... }).apply(...);
The selector we use to match these is:
call[callee=(func-exp, member[obj=func-exp][prop=(#call, #apply)])]
At the top level, in all cases we are matching a call. The callee (the function being called) of the call is either a function expression in the first case, or a member expression in the second and third cases. In those cases, the object that is being accessed is a function expression, and the property is either the identifier call
in the second case, or apply
in the third case.
As this is a common pattern, you can simply use iife
to match it instead.