node_query 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,40 +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.is_a?(Array) && first_key =~ /\d+/
17
- child_node = node[first_key.to_i]
18
- end
19
- if node.respond_to?(first_key)
20
- child_node = node.send(first_key)
21
- 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
22
24
 
23
- return child_node unless rest_keys
25
+ return child_node unless rest_keys
24
26
 
25
- return get_target_node(child_node, rest_keys)
26
- 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
27
42
 
28
- # Recursively handle child nodes.
29
- # @param node [Node] ast node
30
- # @yield [child] Gives a child node.
31
- # @yieldparam child [Parser::AST::Node] child node
32
- def self.handle_recursive_child(node, &block)
33
- NodeQuery.adapter.get_children(node).each do |child|
34
- if NodeQuery.adapter.is_node?(child)
35
- block.call(child)
36
- 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))
37
54
  end
55
+ str
38
56
  end
39
57
  end
40
- end
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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeQuery
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.0"
5
5
  end
data/lib/node_query.rb CHANGED
@@ -11,6 +11,7 @@ class NodeQuery
11
11
  autoload :ParserAdapter, "node_query/parser_adapter"
12
12
  autoload :Compiler, "node_query/compiler"
13
13
  autoload :Helper, "node_query/helper"
14
+ autoload :NodeRules, "node_query/node_rules"
14
15
 
15
16
  # Configure NodeQuery
16
17
  # @param [Hash] options options to configure
@@ -26,15 +27,39 @@ class NodeQuery
26
27
  end
27
28
 
28
29
  # Initialize a NodeQuery.
29
- # @param nql [String] node query language
30
- def initialize(nql)
31
- @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
32
37
  end
33
38
 
34
- # Parse ast node.
39
+ # Query matching nodes.
35
40
  # @param node [Node] ast node
41
+ # @param including_self [boolean] if check the node itself
36
42
  # @return [Array<Node>] matching child nodes
37
- def parse(node)
38
- @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
39
64
  end
40
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]*\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] }
@@ -14,27 +14,25 @@ class NodeQueryLexer
14
14
  require 'strscan'
15
15
 
16
16
  # :stopdoc:
17
- OPEN_ATTRIBUTE = /\[/
18
- CLOSE_ATTRIBUTE = /\]/
19
- OPEN_ARRAY = /\(/
20
- CLOSE_ARRAY = /\)/
21
- OPEN_SELECTOR = /\(/
22
- CLOSE_SELECTOR = /\)/
23
- OPEN_DYNAMIC_ATTRIBUTE = /{{/
24
- CLOSE_DYNAMIC_ATTRIBUTE = /}}/
25
- NODE_TYPE = /\.[a-z]+/
26
- IDENTIFIER = /[\*\.\w]*\w/
27
- IDENTIFIER_VALUE = /[\.\w!&:\?<>=]+/
28
- FALSE = /false/
29
- FLOAT = /\d+\.\d+/
30
- INTEGER = /\d+/
31
- NIL = /nil/
32
- REGEXP_BODY = /(?:[^\/]|\\\/)*/
33
- REGEXP = /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
34
- SYMBOL = /:[\w!\?<>=]+/
35
- TRUE = /true/
36
- SINGLE_QUOTE_STRING = /'.*?'/
37
- DOUBLE_QUOTE_STRING = /".*?"/
17
+ OPEN_ATTRIBUTE = /\[/
18
+ CLOSE_ATTRIBUTE = /\]/
19
+ OPEN_ARRAY = /\(/
20
+ CLOSE_ARRAY = /\)/
21
+ OPEN_SELECTOR = /\(/
22
+ CLOSE_SELECTOR = /\)/
23
+ NODE_TYPE = /\.[a-z]+/
24
+ IDENTIFIER = /[@\*\.\w]*\w/
25
+ IDENTIFIER_VALUE = /[@\.\w!&:\?<>=]+/
26
+ FALSE = /false/
27
+ FLOAT = /\d+\.\d+/
28
+ INTEGER = /\d+/
29
+ NIL = /nil/
30
+ REGEXP_BODY = /(?:[^\/]|\\\/)*/
31
+ REGEXP = /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
32
+ SYMBOL = /:[\w!\?<>=]+/
33
+ TRUE = /true/
34
+ SINGLE_QUOTE_STRING = /'.*?'/
35
+ DOUBLE_QUOTE_STRING = /".*?"/
38
36
  # :startdoc:
39
37
  # :stopdoc:
40
38
  class LexerError < StandardError ; end
@@ -201,8 +199,6 @@ class NodeQueryLexer
201
199
  action { [:tSYMBOL, text[1..-1].to_sym] }
202
200
  when text = ss.scan(/:\[\]/) then
203
201
  action { [:tSYMBOL, text[1..-1].to_sym] }
204
- when text = ss.scan(/{{#{IDENTIFIER}}}/) then
205
- action { [:tDYNAMIC_ATTRIBUTE, text[2..-3]] }
206
202
  when text = ss.scan(/#{OPEN_ARRAY}/) then
207
203
  action { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
208
204
  when text = ss.scan(/#{CLOSE_ATTRIBUTE}/) then