node_query 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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