NodeQuery defines a NQL (node query language) and node rules to query AST nodes.
- NodeQuery
- Table of Contents
- Installation
- Usage
- Node Query Language
- nql matches node type
- nql matches attribute
- nql matches nested attribute
- nql matches evaluated value
- nql matches nested selector
- nql matches method result
- nql matches operators
- nql matches array node attribute
- nql matches * in attribute key
- nql matches multiple selectors
- nql matches goto scope
- nql matches :has and :not_has pseudo selector
- nql matches :first-child and :last-child pseudo selector
- nql matches multiple expressions
- Node Rules
- Write Adapter
- Development
- Contributing
Add this line to your application's Gemfile:
gem 'node_query'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install node_query
It provides two apis: query_nodes
and match_node?
node_query = NodeQuery.new(nql_or_rules: String | Hash, adapter: Symbol) # Initialize NodeQuery
node_query.query_nodes(node: Node, options = { including_self: true, stop_at_first_match: false, recursive: true }): Node[] # Get the matching nodes.
node_query.match_node?(node: Node): boolean # Check if the node matches nql or rules.
Here is an example for parser ast node.
source = `
class User
def initialize(id, name)
@id = id
@name = name
end
end
user = User.new(1, "Murphy")
`
node = Parser::CurrentRuby.parse(source)
# It will get the node of initialize.
NodeQuery.new('.def[name=initialize]', adapter: :parser).query_nodes(node)
NodeQuery.new({ node_type: 'def', name: 'initialize' }, adapter: :parser).query_nodes(node)
.class
It matches class node
.class[name=User]
It matches class node whose name is User
.class[parent_class.name=Base]
It matches class node whose parent class name is Base
.ivasgn[variable="@{{value}}"]
It matches ivasgn node whose left value equals '@' plus the evaluated value of right value.
.def[body.0=.ivasgn]
It matches def node whose first child node is an ivasgn node.
.def[arguments.size=2]
It matches def node whose arguments size is 2.
.class[name=User]
Value of name is equal to User
.class[name^=User]
Value of name starts with User
.class[name$=User]
Value of name ends with User
.class[name*=User]
Value of name contains User
.def[arguments.size!=2]
Size of arguments is not equal to 2
.def[arguments.size>=2]
Size of arguments is greater than or equal to 2
.def[arguments.size>2]
Size of arguments is greater than 2
.def[arguments.size<=2]
Size of arguments is less than or equal to 2
.def[arguments.size<2]
Size of arguments is less than 2
.class[name IN (User Account)]
Value of name is either User or Account
.class[name NOT IN (User Account)]
Value of name is neither User nor Account
.def[arguments INCLUDES id]
Value of arguments includes id
.def[arguments NOT INCLUDES id]
Value of arguments not includes id
.class[name=~/User/]
Value of name matches User
.class[name!~/User/]
Value of name does not match User
.class[name IN (/User/ /Account/)]
Value of name matches either /User/ or /Account/
.def[arguments=(id name)]
It matches def node whose arguments are id and name.
.def[arguments.*.name IN (id name)]
It matches def node whose arguments are either id or name.
.class .send
It matches send node whose ancestor is class node.
.def > .send
It matches send node whose parent is def node.
.send[variable=@id] + .send
It matches send node only if it is immediately follows the send node whose left value is @id.
.send[variable=@id] ~ .send
It matches send node only if it is follows the send node whose left value is @id.
.def body .send
It matches send node who is in the body of def node.
.class:has(.def[name=initialize])
It matches class node who has an initialize def node.
.class:not_has(.def[name=initialize])
It matches class node who does not have an initialize def node.
.def:first-child
It matches the first def node.
.def:last-child
It matches the last def node.
.ivasgn[variable=@id], .ivasgn[variable=@name]
It matches ivasgn node whose left value is either @id or @name.
{ node_type: 'class' }
It matches class node
{ node_type: 'def', name: 'initialize' }
It matches def node whose name is initialize
{ node_type: 'def', arguments: { "0": 1, "1": "Murphy" } }
It matches def node whose arguments are 1 and Murphy.
{ node_type: 'class', parent_class: { name: 'Base' } }
It matches class node whose parent class name is Base
{ node_type: 'ivasgn', variable: '@{{value}}' }
It matches ivasgn node whose left value equals '@' plus the evaluated value of right value.
{ node_type: 'def', body: { "0": { node_type: 'ivasgn' } } }
It matches def node whose first child node is an ivasgn node.
{ node_type: 'def', arguments: { size: 2 } }
It matches def node whose arguments size is 2.
{ node_type: 'class', name: 'User' }
Value of name is equal to User
{ node_type: 'def', arguments: { size { not: 2 } }
Size of arguments is not equal to 2
{ node_type: 'def', arguments: { size { gte: 2 } }
Size of arguments is greater than or equal to 2
{ node_type: 'def', arguments: { size { gt: 2 } }
Size of arguments is greater than 2
{ node_type: 'def', arguments: { size { lte: 2 } }
Size of arguments is less than or equal to 2
{ node_type: 'def', arguments: { size { lt: 2 } }
Size of arguments is less than 2
{ node_type: 'class', name: { in: ['User', 'Account'] } }
Value of name is either User or Account
{ node_type: 'class', name: { not_in: ['User', 'Account'] } }
Value of name is neither User nor Account
{ node_type: 'def', arguments: { includes: 'id' } }
Value of arguments includes id
{ node_type: 'def', arguments: { not_includes: 'id' } }
Value of arguments not includes id
{ node_type: 'class', name: /User/ }
Value of name matches User
{ node_type: 'class', name: { not: /User/ } }
Value of name does not match User
{ node_type: 'class', name: { in: [/User/, /Account/] } }
Value of name matches either /User/ or /Account/
{ node_type: 'def', arguments: ['id', 'name'] }
It matches def node whose arguments are id and name.
Different parser, like prism, parser, syntax_tree, will generate different AST nodes, to make NodeQuery work for them all, we define an Adapter interface, if you implement the Adapter interface, you can set it as NodeQuery's adapter.
It provides 3 adapters
PrismAdapter
ParserAdapter
SyntaxTreeAdapter
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/synvert-hq/node-query-ruby.