node_query 1.0.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,12 +13,20 @@ module NodeQuery::Compiler
13
13
 
14
14
  # Query nodes by the current and the rest expression.
15
15
  # @param node [Node] node to match
16
+ # @params including_self [boolean] if query the current node.
16
17
  # @return [Array<Node>] matching nodes.
17
- def query_nodes(node)
18
- matching_nodes = @expression.query_nodes(node)
18
+ def query_nodes(node, including_self = true)
19
+ matching_nodes = @expression.query_nodes(node, including_self)
19
20
  return matching_nodes if @rest.nil?
20
21
 
21
- matching_nodes + @rest.query_nodes(node)
22
+ matching_nodes + @rest.query_nodes(node, including_self)
23
+ end
24
+
25
+ # Check if the node matches the expression list.
26
+ # @param node [Node] the node
27
+ # @return [Boolean]
28
+ def match_node?(node)
29
+ !query_nodes(node).empty?
22
30
  end
23
31
 
24
32
  def to_s
@@ -19,8 +19,8 @@ module NodeQuery::Compiler
19
19
  # if the node is an Array, return the array of each element's actual value,
20
20
  # otherwise, return the String value.
21
21
  def actual_value(node)
22
- if node.is_a?(::Parser::AST::Node)
23
- NodeQuery.get_adapter.get_source(node)
22
+ if NodeQuery.adapter.is_node?(node)
23
+ NodeQuery.adapter.get_source(node)
24
24
  elsif node.is_a?(::Array)
25
25
  node.map { |n| actual_value(n) }
26
26
  else
@@ -13,16 +13,13 @@ module NodeQuery::Compiler
13
13
 
14
14
  # Check if the regexp value matches the node value.
15
15
  # @param node [Node] the node
16
- # @param operator [String] the operator
17
- # @return [Boolean] true if the regexp value matches the node value, otherwise, false.
18
- def match?(node, operator = '=~')
19
- match =
20
- if node.is_a?(::Parser::AST::Node)
21
- @value.match(NodeQuery.get_adapter.get_source(node))
22
- else
23
- @value.match(node.to_s)
24
- end
25
- operator == '=~' ? match : !match
16
+ # @return [Boolean] true if the regexp value matches the node value.
17
+ def is_equal?(node)
18
+ if NodeQuery.adapter.is_node?(node)
19
+ @value.match(NodeQuery.adapter.get_source(node))
20
+ else
21
+ @value.match(node.to_s)
22
+ end
26
23
  end
27
24
 
28
25
  # Get valid operators.
@@ -23,7 +23,7 @@ module NodeQuery::Compiler
23
23
  # Check if node matches the selector.
24
24
  # @param node [Parser::AST::Node] the node
25
25
  def match?(node)
26
- node.is_a?(::Parser::AST::Node) && (!@basic_selector || @basic_selector.match?(node)) && match_pseudo_class?(node)
26
+ NodeQuery.adapter.is_node?(node) && (!@basic_selector || @basic_selector.match?(node)) && match_pseudo_class?(node)
27
27
  end
28
28
 
29
29
  # Query nodes by the selector.
@@ -33,8 +33,9 @@ module NodeQuery::Compiler
33
33
  # * If relationship is next sibling, it try to match next sibling node.
34
34
  # * If relationship is subsequent sibling, it will match in all sibling nodes.
35
35
  # @param node [Node] node to match
36
+ # @params including_self [boolean] if query the current node.
36
37
  # @return [Array<Node>] matching nodes.
37
- def query_nodes(node)
38
+ def query_nodes(node, including_self = true)
38
39
  return find_nodes_by_relationship(node) if @relationship
39
40
 
40
41
  if node.is_a?(::Array)
@@ -44,7 +45,9 @@ module NodeQuery::Compiler
44
45
  return find_nodes_by_goto_scope(node) if @goto_scope
45
46
 
46
47
  nodes = []
47
- nodes << node if match?(node)
48
+ if including_self && match?(node)
49
+ nodes << node
50
+ end
48
51
  if @basic_selector
49
52
  NodeQuery::Helper.handle_recursive_child(node) do |child_node|
50
53
  nodes << child_node if match?(child_node)
@@ -86,7 +89,13 @@ module NodeQuery::Compiler
86
89
  end
87
90
  else
88
91
  node.children.each do |child_node|
89
- nodes << child_node if @rest.match?(child_node)
92
+ if NodeQuery.adapter.is_node?(child_node) && :begin == NodeQuery.adapter.get_node_type(child_node)
93
+ child_node.children.each do |child_child_node|
94
+ nodes << child_child_node if @rest.match?(child_child_node)
95
+ end
96
+ elsif @rest.match?(child_node)
97
+ nodes << child_node
98
+ end
90
99
  end
91
100
  end
92
101
  when '+'
@@ -5,12 +5,31 @@ module NodeQuery::Compiler
5
5
  class String
6
6
  include Comparable
7
7
 
8
+ attr_accessor :base_node
9
+
8
10
  # Initialize a String.
9
11
  # @param value [String] the string value
10
12
  def initialize(value:)
11
13
  @value = value
12
14
  end
13
15
 
16
+ # Get the expected value.
17
+ # @example
18
+ # if the source code of the node is @id = id,
19
+ # and the @value is "@{{right_vaue}}",
20
+ # then it returns "@id".
21
+ # @return [String] the expected string, if it contains evaluated value, evaluate the node value.
22
+ def expected_value
23
+ NodeQuery::Helper.evaluate_node_value(base_node, @value)
24
+ end
25
+
26
+ # Check if the actual value equals the node value.
27
+ # @param node [Node] the node
28
+ # @return [Boolean] true if the actual value equals the node value.
29
+ def is_equal?(node)
30
+ actual_value(node).to_s == expected_value
31
+ end
32
+
14
33
  # Get valid operators.
15
34
  # @return [Array] valid operators
16
35
  def valid_operators
@@ -15,7 +15,6 @@ module NodeQuery::Compiler
15
15
 
16
16
  autoload :ArrayValue, 'node_query/compiler/array_value'
17
17
  autoload :Boolean, 'node_query/compiler/boolean'
18
- autoload :EvaluatedValue, 'node_query/compiler/evaluated_value'
19
18
  autoload :Float, 'node_query/compiler/float'
20
19
  autoload :Identifier, 'node_query/compiler/identifier'
21
20
  autoload :Integer, 'node_query/compiler/integer'
@@ -1,35 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeQuery::Helper
4
- # Get target node by the keys.
5
- # @param node [Node] ast node
6
- # @param keys [String] keys of child node.
7
- # @return [Node|] the target node.
8
- def self.get_target_node(node, keys)
9
- return unless node
4
+ class <<self
5
+ # Get target node by the keys.
6
+ # @param node [Node] ast node
7
+ # @param keys [String|Array] keys of child node.
8
+ # @return [Node|] the target node.
9
+ def get_target_node(node, keys)
10
+ return unless node
10
11
 
11
- first_key, rest_keys = keys.to_s.split('.', 2)
12
- if (node.is_a?(Array) && first_key === "*")
13
- return node.map { |child_node| get_target_node(child_node, rest_keys) }
14
- end
12
+ first_key, rest_keys = keys.to_s.split('.', 2)
13
+ if node.is_a?(Array) && first_key === "*"
14
+ return node.map { |child_node| get_target_node(child_node, rest_keys) }
15
+ end
15
16
 
16
- if node.respond_to?(first_key)
17
- child_node = node.send(first_key)
18
- end
17
+ if node.is_a?(Array) && first_key =~ /\d+/
18
+ child_node = node[first_key.to_i]
19
+ elsif node.respond_to?(first_key)
20
+ child_node = node.send(first_key)
21
+ elsif first_key == "nodeType"
22
+ child_node = NodeQuery.adapter.get_node_type(node)
23
+ end
19
24
 
20
- return child_node unless rest_keys
25
+ return child_node unless rest_keys
21
26
 
22
- return get_target_node(child_node, rest_keys)
23
- end
27
+ return get_target_node(child_node, rest_keys)
28
+ end
29
+
30
+ # Recursively handle child nodes.
31
+ # @param node [Node] ast node
32
+ # @yield [child] Gives a child node.
33
+ # @yieldparam child [Parser::AST::Node] child node
34
+ def handle_recursive_child(node, &block)
35
+ NodeQuery.adapter.get_children(node).each do |child|
36
+ if NodeQuery.adapter.is_node?(child)
37
+ block.call(child)
38
+ handle_recursive_child(child, &block)
39
+ end
40
+ end
41
+ end
24
42
 
25
- # Recursively handle child nodes.
26
- # @param node [Node] ast node
27
- # @yield [child] Gives a child node.
28
- # @yieldparam child [Parser::AST::Node] child node
29
- def self.handle_recursive_child(node, &block)
30
- NodeQuery.get_adapter.get_children(node).each do |child|
31
- block.call(child)
32
- handle_recursive_child(child, &block)
43
+ # Evaluate node value.
44
+ # @example
45
+ # source code of the node is @id = id
46
+ # evaluated_node_value(node, "@{{right_value}}") # => @id
47
+ # @param node [Node] ast node
48
+ # @param str [String] string to be evaluated
49
+ # @return [String] evaluated string
50
+ def evaluate_node_value(node, str)
51
+ str.scan(/{{(.*)}}/).each do |match_data|
52
+ target_node = NodeQuery::Helper.get_target_node(node, match_data.first)
53
+ str = str.sub("{{#{match_data.first}}}", NodeQuery.adapter.get_source(target_node))
54
+ end
55
+ str
33
56
  end
34
57
  end
35
58
  end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ class NodeQuery::NodeRules
4
+ KEYWORDS = %i[any includes not in not_in gt gte lt lte]
5
+
6
+ # Initialize a NodeRules.
7
+ # @param rules [Hash] the nod rules
8
+ def initialize(rules)
9
+ @rules = rules
10
+ end
11
+
12
+ # Query nodes by the rules.
13
+ # @param node [Node] node to query
14
+ # @params including_self [boolean] if query the current node.
15
+ # @return [Array<Node>] matching nodes.
16
+ def query_nodes(node, including_self = true)
17
+ matching_nodes = []
18
+ if (including_self && match_node?(node))
19
+ matching_nodes.push(node)
20
+ end
21
+ NodeQuery::Helper.handle_recursive_child(node) do |child_node|
22
+ if (match_node?(child_node))
23
+ matching_nodes.push(child_node)
24
+ end
25
+ end
26
+ matching_nodes
27
+ end
28
+
29
+ # Check if the node matches the rules.
30
+ # @param node [Node] the node
31
+ # @return [Boolean]
32
+ def match_node?(node)
33
+ flat_hash(@rules).keys.all? do |multi_keys|
34
+ last_key = multi_keys.last
35
+ actual = KEYWORDS.include?(last_key) ?
36
+ NodeQuery::Helper.get_target_node(node, multi_keys[0...-1].join('.')) :
37
+ NodeQuery::Helper.get_target_node(node, multi_keys.join('.'))
38
+ expected = expected_value(@rules, multi_keys)
39
+ expected = NodeQuery::Helper.evaluate_node_value(node, expected) if expected.is_a?(String)
40
+ case last_key
41
+ when :any, :includes
42
+ actual.any? { |actual_value| match_value?(actual_value, expected) }
43
+ when :not
44
+ !match_value?(actual, expected)
45
+ when :in
46
+ expected.any? { |expected_value| match_value?(actual, expected_value) }
47
+ when :not_in
48
+ expected.all? { |expected_value| !match_value?(actual, expected_value) }
49
+ when :gt
50
+ actual > expected
51
+ when :gte
52
+ actual >= expected
53
+ when :lt
54
+ actual < expected
55
+ when :lte
56
+ actual <= expected
57
+ else
58
+ match_value?(actual, expected)
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # Compare actual value with expected value.
66
+ #
67
+ # @param actual [Object] actual value.
68
+ # @param expected [Object] expected value.
69
+ # @return [Boolean]
70
+ # @raise [Synvert::Core::MethodNotSupported] if expected class is not supported.
71
+ def match_value?(actual, expected)
72
+ return true if actual == expected
73
+
74
+ case expected
75
+ when Symbol
76
+ if actual.is_a?(Parser::AST::Node)
77
+ actual_source = NodeQuery.adapter.get_source(actual)
78
+ actual_source == ":#{expected}" || actual_source == expected.to_s
79
+ else
80
+ actual.to_sym == expected
81
+ end
82
+ when String
83
+ if actual.is_a?(Parser::AST::Node)
84
+ actual_source = NodeQuery.adapter.get_source(actual)
85
+ actual_source == expected || actual_source == unwrap_quote(expected) ||
86
+ unwrap_quote(actual_source) == expected || unwrap_quote(actual_source) == unwrap_quote(expected)
87
+ else
88
+ actual.to_s == expected || wrap_quote(actual.to_s) == expected
89
+ end
90
+ when Regexp
91
+ if actual.is_a?(Parser::AST::Node)
92
+ actual.to_source =~ Regexp.new(expected.to_s, Regexp::MULTILINE)
93
+ else
94
+ actual.to_s =~ Regexp.new(expected.to_s, Regexp::MULTILINE)
95
+ end
96
+ when Array
97
+ return false unless expected.length == actual.length
98
+
99
+ actual.zip(expected).all? { |a, e| match_value?(a, e) }
100
+ when NilClass
101
+ if actual.is_a?(Parser::AST::Node)
102
+ :nil == actual.type
103
+ else
104
+ actual.nil?
105
+ end
106
+ when Numeric
107
+ if actual.is_a?(Parser::AST::Node)
108
+ actual.children[0] == expected
109
+ else
110
+ actual == expected
111
+ end
112
+ when TrueClass
113
+ :true == actual.type
114
+ when FalseClass
115
+ :false == actual.type
116
+ when Parser::AST::Node
117
+ actual == expected
118
+ when Synvert::Core::Rewriter::AnyValue
119
+ !actual.nil?
120
+ else
121
+ raise Synvert::Core::MethodNotSupported, "#{expected.class} is not handled for match_value?"
122
+ end
123
+ end
124
+
125
+ # Convert a hash to flat one.
126
+ #
127
+ # @example
128
+ # flat_hash(type: 'block', caller: {type: 'send', receiver: 'RSpec'})
129
+ # # {[:type] => 'block', [:caller, :type] => 'send', [:caller, :receiver] => 'RSpec'}
130
+ # @param h [Hash] original hash.
131
+ # @return flatten hash.
132
+ def flat_hash(h, k = [])
133
+ new_hash = {}
134
+ h.each_pair do |key, val|
135
+ if val.is_a?(Hash)
136
+ new_hash.merge!(flat_hash(val, k + [key]))
137
+ else
138
+ new_hash[k + [key]] = val
139
+ end
140
+ end
141
+ new_hash
142
+ end
143
+
144
+ # Get expected value from rules.
145
+ #
146
+ # @param rules [Hash]
147
+ # @param multi_keys [Array<Symbol>]
148
+ # @return [Object] expected value.
149
+ def expected_value(rules, multi_keys)
150
+ multi_keys.inject(rules) { |o, key| o[key] }
151
+ end
152
+
153
+ # Wrap the string with single or double quote.
154
+ # @param string [String]
155
+ # @return [String]
156
+ def wrap_quote(string)
157
+ if string.include?("'")
158
+ "\"#{string}\""
159
+ else
160
+ "'#{string}'"
161
+ end
162
+ end
163
+
164
+ # Unwrap the quote from the string.
165
+ # @param string [String]
166
+ # @return [String]
167
+ def unwrap_quote(string)
168
+ if (string[0] == '"' && string[-1] == '"') || (string[0] == "'" && string[-1] == "'")
169
+ string[1...-1]
170
+ else
171
+ string
172
+ end
173
+ end
174
+ end
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeQuery::ParserAdapter
4
+ def is_node?(node)
5
+ node.is_a?(Parser::AST::Node)
6
+ end
7
+
4
8
  def get_node_type(node)
5
9
  node.type
6
10
  end
@@ -10,11 +14,11 @@ class NodeQuery::ParserAdapter
10
14
  end
11
15
 
12
16
  def get_children(node)
13
- node.is_a?(Parser::AST::Node) ? node.children : []
17
+ node.children
14
18
  end
15
19
 
16
20
  def get_siblings(node)
17
21
  index = node.parent.children.index(node)
18
22
  node.parent.children[index + 1..]
19
23
  end
20
- end
24
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeQuery
4
- VERSION = "1.0.0"
4
+ VERSION = "1.3.0"
5
5
  end
data/lib/node_query.rb CHANGED
@@ -3,13 +3,16 @@
3
3
  require 'active_support/core_ext/array'
4
4
 
5
5
  require_relative "node_query/version"
6
- require_relative "node_query/parser_adapter"
7
- require_relative "node_query/compiler"
8
- require_relative "node_query/helper"
9
6
  require_relative "./node_query_lexer.rex"
10
7
  require_relative "./node_query_parser.racc"
11
8
 
12
9
  class NodeQuery
10
+ autoload :Adapter, "node_query/adapter"
11
+ autoload :ParserAdapter, "node_query/parser_adapter"
12
+ autoload :Compiler, "node_query/compiler"
13
+ autoload :Helper, "node_query/helper"
14
+ autoload :NodeRules, "node_query/node_rules"
15
+
13
16
  # Configure NodeQuery
14
17
  # @param [Hash] options options to configure
15
18
  # @option options [NodeQuery::Adapter] :adapter the adpater
@@ -19,20 +22,44 @@ class NodeQuery
19
22
 
20
23
  # Get the adapter
21
24
  # @return [NodeQuery::Adapter] current adapter, by default is {NodeQuery::ParserAdapter}
22
- def self.get_adapter
25
+ def self.adapter
23
26
  @adapter ||= ParserAdapter.new
24
27
  end
25
28
 
26
29
  # Initialize a NodeQuery.
27
- # @param nql [String] node query language
28
- def initialize(nql)
29
- @expression = NodeQueryParser.new.parse(nql)
30
+ # @param nqlOrRules [String | Hash] node query language or node rules
31
+ def initialize(nqlOrRules)
32
+ if nqlOrRules.is_a?(String)
33
+ @expression = NodeQueryParser.new.parse(nqlOrRules)
34
+ else
35
+ @rules = NodeRules.new(nqlOrRules)
36
+ end
30
37
  end
31
38
 
32
- # Parse ast node.
39
+ # Query matching nodes.
33
40
  # @param node [Node] ast node
41
+ # @param including_self [boolean] if check the node itself
34
42
  # @return [Array<Node>] matching child nodes
35
- def parse(node)
36
- @expression.query_nodes(node)
43
+ def query_nodes(node, including_self = true)
44
+ if @expression
45
+ @expression.query_nodes(node, including_self)
46
+ elsif @rules
47
+ @rules.query_nodes(node, including_self)
48
+ else
49
+ []
50
+ end
51
+ end
52
+
53
+ # Check if the node matches the nql or rules.
54
+ # @param node [Node] the node
55
+ # @return [Boolean]
56
+ def match_node?(node)
57
+ if @expression
58
+ @expression.match_node?(node)
59
+ elsif @rules
60
+ @rules.match_node?(node)
61
+ else
62
+ false
63
+ end
37
64
  end
38
65
  end
@@ -7,11 +7,9 @@ macros
7
7
  CLOSE_ARRAY /\)/
8
8
  OPEN_SELECTOR /\(/
9
9
  CLOSE_SELECTOR /\)/
10
- OPEN_DYNAMIC_ATTRIBUTE /{{/
11
- CLOSE_DYNAMIC_ATTRIBUTE /}}/
12
10
  NODE_TYPE /\.[a-z]+/
13
- IDENTIFIER /[\.\w]+/
14
- IDENTIFIER_VALUE /[\.\w!&:\?<>=]+/
11
+ IDENTIFIER /[@\*\.\w]*\w/
12
+ IDENTIFIER_VALUE /[@\.\w!&:\?<>=]+/
15
13
  FALSE /false/
16
14
  FLOAT /\d+\.\d+/
17
15
  INTEGER /\d+/
@@ -59,7 +57,6 @@ rules
59
57
  :VALUE /\[\]/ { [:tIDENTIFIER_VALUE, text] }
60
58
  :VALUE /:\[\]=/ { [:tSYMBOL, text[1..-1].to_sym] }
61
59
  :VALUE /:\[\]/ { [:tSYMBOL, text[1..-1].to_sym] }
62
- :VALUE /{{#{IDENTIFIER}}}/ { [:tDYNAMIC_ATTRIBUTE, text[2..-3]] }
63
60
  :VALUE /#{OPEN_ARRAY}/ { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
64
61
  :VALUE /#{CLOSE_ATTRIBUTE}/ { @nested_count -= 1; @state = @nested_count == 0 ? nil : :VALUE; [:tCLOSE_ATTRIBUTE, text] }
65
62
  :VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }