Class: Parser::AST::Node
- Inherits:
-
Object
- Object
- Parser::AST::Node
- Defined in:
- lib/synvert/core/node_ext.rb
Overview
Extend Parser::AST::Node. https://github.com/whitequark/parser/blob/master/lib/parser/ast/node.rb
Rules
Synvert compares ast nodes with key / value pairs, each ast node has multiple attributes, e.g. receiver, message and arguments, it matches only when all of key / value pairs match.
type: ‘send’, message: :include, arguments: [‘FactoryGirl::Syntax::Methods’]
Synvert does comparison based on the value type
-
if value is a symbol, then compares ast node value as symbol, e.g. message: :include
-
if value is a string, then compares ast node original source code, e.g. name: ‘Synvert::Application’
-
if value is a regexp, then compares ast node original source code, e.g. message: /find_all_by_/
-
if value is an array, then compares each ast node, e.g. arguments: [‘FactoryGirl::Syntax::Methods’]
-
if value is nil, then check if ast node is nil, e.g. arguments: [nil]
-
if value is true or false, then check if ast node is :true or :false, e.g. arguments: [false]
-
if value is ast, then compare ast node directly, e.g. to_ast: Parser::CurrentRuby.parse(“self.class.serialized_attributes”)
It can also compare nested key / value pairs, like
type: ‘send’, receiver: { type: ‘send’, receiver: { type: ‘send’, message: ‘config’ }, message: ‘active_record’ }, message: ‘identity_map=’
Source Code to Ast Node https://synvert-playground.xinminlabs.com/ruby
Constant Summary collapse
- TYPE_CHILDREN =
{ and: %i[left_value right_value], arg: %i[name], begin: %i[body], block: %i[caller arguments body], blockarg: %i[name], const: %i[parent_const name], class: %i[name parent_class body], csend: %i[receiver message arguments], cvasgn: %i[left_value right_value], cvar: %i[name], def: %i[name arguments body], definded?: %i[arguments], defs: %i[self name arguments body], hash: %i[pairs], ivasgn: %i[left_value right_value], ivar: %i[name], lvar: %i[name], lvasgn: %i[left_value right_value], masgn: %i[left_value right_value], module: %i[name body], or: %i[left_value right_value], or_asgn: %i[left_value right_value], pair: %i[key value], restarg: %i[name], send: %i[receiver message arguments], super: %i[arguments], zsuper: %i[] }
Instance Method Summary collapse
-
#arguments ⇒ Array<Parser::AST::Node>
Get arguments of node.
-
#body ⇒ Array<Parser::AST::Node>
Get body of node.
-
#child_node_by_name(child_name) ⇒ Parser::AST::Node
Get child node by the name.
-
#child_node_range(child_name) ⇒ Parser::Source::Range
Get the source range of child node.
-
#column ⇒ Integer
Get the column of node.
-
#condition ⇒ Parser::AST::Node
Get condition of node.
-
#debug_info ⇒ String
Return the debug info.
-
#filename ⇒ String
Get the file name of node.
-
#hash_value(key) ⇒ Parser::AST::Node
Get :hash value node according to specified key.
-
#initialize(type, children = [], properties = {}) ⇒ Node
constructor
Initialize a Node.
-
#key?(key) ⇒ Boolean
Check if :hash node contains specified key.
-
#keys ⇒ Array<Parser::AST::Node>
Get keys of :hash node.
-
#left_value ⇒ Parser::AST::Node
Return the left value of node.
-
#line ⇒ Integer
Get the line of node.
-
#match?(rules) ⇒ Boolean
Match node with rules.
-
#method_missing(method_name, *args, &block) ⇒ Object
Respond key value and source for hash node, e.g.
-
#parent ⇒ Parser::AST::Node
Get the parent node.
-
#parent=(node) ⇒ Object
Set the parent node.
-
#recursive_children {|child| ... } ⇒ Object
Recursively iterate all child nodes of node.
- #respond_to_missing?(method_name, *args) ⇒ Boolean
-
#rewritten_source(code) ⇒ String
Get rewritten source code.
-
#siblings ⇒ Array<Parser::AST::Node>
Get the sibling nodes.
-
#strip_curly_braces ⇒ String
Strip curly braces for hash.
-
#to_hash ⇒ Object
Convert node to a hash, so that it can be converted to a json.
-
#to_lambda_literal ⇒ String
Convert lambda {} to -> {}.
-
#to_single_quote ⇒ String
Get single quote string.
-
#to_source ⇒ String
Get the source code of node.
-
#to_string ⇒ String
Convert symbol to string.
-
#to_symbol ⇒ String
Convert string to symbol.
-
#to_value ⇒ Object
Return the exact value of node.
-
#values ⇒ Array<Parser::AST::Node>
Get values of :hash node.
-
#wrap_curly_braces ⇒ String
Wrap curly braces for hash.
Constructor Details
#initialize(type, children = [], properties = {}) ⇒ Node
Initialize a Node.
It extends Parser::AST::Node and set parent for its child nodes.
65 66 67 68 69 70 71 72 73 74 |
# File 'lib/synvert/core/node_ext.rb', line 65 def initialize(type, children = [], properties = {}) @mutable_attributes = {} super # children could be nil for s(:array) Array(children).each do |child_node| if child_node.is_a?(Parser::AST::Node) child_node.parent = self end end end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name, *args, &block) ⇒ Object
Respond key value and source for hash node, e.g.
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/synvert/core/node_ext.rb', line 290 def method_missing(method_name, *args, &block) if :args == type && children.respond_to?(method_name) return children.send(method_name, *args, &block) elsif :hash == type && method_name.to_s.include?('_value') key = method_name.to_s.sub('_value', '') return hash_value(key.to_sym)&.to_value if key?(key.to_sym) return hash_value(key.to_s)&.to_value if key?(key.to_s) return nil elsif :hash == type && method_name.to_s.include?('_source') key = method_name.to_s.sub('_source', '') return hash_value(key.to_sym)&.to_source if key?(key.to_sym) return hash_value(key.to_s)&.to_source if key?(key.to_s) return nil end super end |
Instance Method Details
#arguments ⇒ Array<Parser::AST::Node>
Get arguments of node. It supports :block, :csend, :def, :defined?, :defs and :send nodes.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/synvert/core/node_ext.rb', line 142 def arguments case type when :def, :block children[1].children when :defs children[2].children when :send, :csend children[2..-1] when :defined? children else raise Synvert::Core::MethodNotSupported, "arguments is not handled for #{debug_info}" end end |
#body ⇒ Array<Parser::AST::Node>
Get body of node. It supports :begin, :block, :class, :def, :defs and :module node.
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/synvert/core/node_ext.rb', line 164 def body case type when :begin children when :def, :block, :class, :module return [] if children[2].nil? :begin == children[2].type ? children[2].body : children[2..-1] when :defs return [] if children[3].nil? :begin == children[3].type ? children[3].body : children[3..-1] else raise Synvert::Core::MethodNotSupported, "body is not handled for #{debug_info}" end end |
#child_node_by_name(child_name) ⇒ Parser::AST::Node
Get child node by the name.
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/synvert/core/node_ext.rb', line 369 def child_node_by_name(child_name) direct_child_name, nested_child_name = child_name.to_s.split('.', 2) if respond_to?(direct_child_name) child_node = send(direct_child_name) return child_node.child_node_by_name(nested_child_name) if nested_child_name return nil if child_node.nil? return child_node if child_node.is_a?(Parser::AST::Node) return child_node end raise Synvert::Core::MethodNotSupported, "child_node_by_name is not handled for #{debug_info}, child_name: #{child_name}" end |
#child_node_range(child_name) ⇒ Parser::Source::Range
Get the source range of child node.
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 |
# File 'lib/synvert/core/node_ext.rb', line 391 def child_node_range(child_name) case [type, child_name.to_sym] when %i[block pipes], %i[def parentheses], %i[defs parentheses] Parser::Source::Range.new( '(string)', arguments.first.loc.expression.begin_pos - 1, arguments.last.loc.expression.end_pos + 1 ) when %i[block arguments], %i[def arguments], %i[defs arguments] Parser::Source::Range.new( '(string)', arguments.first.loc.expression.begin_pos, arguments.last.loc.expression.end_pos ) when %i[class name], %i[def name], %i[defs name] loc.name when %i[defs dot] loc.operator when %i[defs self] Parser::Source::Range.new('(string)', loc.operator.begin_pos - 'self'.length, loc.operator.begin_pos) when %i[send dot], %i[csend dot] loc.dot when %i[send message], %i[csend message] if loc.operator Parser::Source::Range.new('(string)', loc.selector.begin_pos, loc.operator.end_pos) else loc.selector end when %i[send parentheses], %i[csend parentheses] if loc.begin && loc.end Parser::Source::Range.new('(string)', loc.begin.begin_pos, loc.end.end_pos) end else direct_child_name, nested_child_name = child_name.to_s.split('.', 2) if respond_to?(direct_child_name) child_node = send(direct_child_name) return child_node.child_node_range(nested_child_name) if nested_child_name return nil if child_node.nil? if child_node.is_a?(Parser::AST::Node) return( Parser::Source::Range.new( '(string)', child_node.loc.expression.begin_pos, child_node.loc.expression.end_pos ) ) end # arguments return nil if child_node.empty? return( Parser::Source::Range.new( '(string)', child_node.first.loc.expression.begin_pos, child_node.last.loc.expression.end_pos ) ) end raise Synvert::Core::MethodNotSupported, "child_node_range is not handled for #{debug_info}, child_name: #{child_name}" end end |
#column ⇒ Integer
Get the column of node.
354 355 356 |
# File 'lib/synvert/core/node_ext.rb', line 354 def column loc.expression.column end |
#condition ⇒ Parser::AST::Node
Get condition of node. It supports :if node.
188 189 190 191 192 193 194 |
# File 'lib/synvert/core/node_ext.rb', line 188 def condition if :if == type children[0] else raise Synvert::Core::MethodNotSupported, "condition is not handled for #{debug_info}" end end |
#debug_info ⇒ String
Return the debug info.
327 328 329 330 331 332 333 334 335 |
# File 'lib/synvert/core/node_ext.rb', line 327 def debug_info "\n" + [ "file: #{loc.expression.source_buffer.name}", "line: #{loc.expression.line}", "source: #{to_source}", "node: #{inspect}" ].join("\n") end |
#filename ⇒ String
Get the file name of node.
340 341 342 |
# File 'lib/synvert/core/node_ext.rb', line 340 def filename loc.expression&.source_buffer.name end |
#hash_value(key) ⇒ Parser::AST::Node
Get :hash value node according to specified key.
246 247 248 249 250 251 252 253 |
# File 'lib/synvert/core/node_ext.rb', line 246 def hash_value(key) if :hash == type value_node = children.find { |pair_node| pair_node.key.to_value == key } value_node&.value else raise Synvert::Core::MethodNotSupported, "hash_value is not handled for #{debug_info}" end end |
#key?(key) ⇒ Boolean
Check if :hash node contains specified key.
231 232 233 234 235 236 237 |
# File 'lib/synvert/core/node_ext.rb', line 231 def key?(key) if :hash == type children.any? { |pair_node| pair_node.key.to_value == key } else raise Synvert::Core::MethodNotSupported, "key? is not handled for #{debug_info}" end end |
#keys ⇒ Array<Parser::AST::Node>
Get keys of :hash node.
202 203 204 205 206 207 208 |
# File 'lib/synvert/core/node_ext.rb', line 202 def keys if :hash == type children.map { |child| child.children[0] } else raise Synvert::Core::MethodNotSupported, "keys is not handled for #{debug_info}" end end |
#left_value ⇒ Parser::AST::Node
Return the left value of node. It supports :and, :cvagn, :lvasgn, :masgn, :or and :or_asgn nodes.
126 127 128 129 130 131 132 133 |
# File 'lib/synvert/core/node_ext.rb', line 126 def left_value return children[0].children[0] if type == :or_asgn index = TYPE_CHILDREN[type]&.index(:left_value) return children[index] if index raise Synvert::Core::MethodNotSupported, "#{left_value} is not handled for #{debug_info}" end |
#line ⇒ Integer
Get the line of node.
361 362 363 |
# File 'lib/synvert/core/node_ext.rb', line 361 def line loc.expression.line end |
#match?(rules) ⇒ Boolean
Match node with rules. It provides some additional keywords to match rules, any, contain, not, in, not_in, gt, gte, lt, lte.
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 |
# File 'lib/synvert/core/node_ext.rb', line 480 def match?(rules) keywords = %i[any contain not in not_in gt gte lt lte] flat_hash(rules).keys.all? do |multi_keys| last_key = multi_keys.last actual = keywords.include?(last_key) ? actual_value(multi_keys[0...-1]) : actual_value(multi_keys) expected = expected_value(rules, multi_keys) case last_key when :any, :contain actual.any? { |actual_value| match_value?(actual_value, expected) } when :not !match_value?(actual, expected) when :in expected.any? { |expected_value| match_value?(actual, expected_value) } when :not_in expected.all? { |expected_value| !match_value?(actual, expected_value) } when :gt actual > expected when :gte actual >= expected when :lt actual < expected when :lte actual <= expected else match_value?(actual, expected) end end end |
#parent ⇒ Parser::AST::Node
Get the parent node.
78 79 80 |
# File 'lib/synvert/core/node_ext.rb', line 78 def parent @mutable_attributes[:parent] end |
#parent=(node) ⇒ Object
Set the parent node.
84 85 86 |
# File 'lib/synvert/core/node_ext.rb', line 84 def parent=(node) @mutable_attributes[:parent] = node end |
#recursive_children {|child| ... } ⇒ Object
Recursively iterate all child nodes of node.
463 464 465 466 467 468 469 470 |
# File 'lib/synvert/core/node_ext.rb', line 463 def recursive_children(&block) children.each do |child| if child.is_a?(Parser::AST::Node) stop = yield child child.recursive_children(&block) unless stop == :stop end end end |
#respond_to_missing?(method_name, *args) ⇒ Boolean
310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/synvert/core/node_ext.rb', line 310 def respond_to_missing?(method_name, *args) if :args == type && children.respond_to?(method_name) return true elsif :hash == type && method_name.to_s.include?('_value') key = method_name.to_s.sub('_value', '') return true if key?(key.to_sym) || key?(key.to_s) elsif :hash == type && method_name.to_s.include?('_source') key = method_name.to_s.sub('_source', '') return true if key?(key.to_sym) || key?(key.to_s) end super end |
#rewritten_source(code) ⇒ String
Get rewritten source code.
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 |
# File 'lib/synvert/core/node_ext.rb', line 515 def rewritten_source(code) code.gsub(/{{(.*?)}}/m) do old_code = Regexp.last_match(1) if respond_to?(old_code.split('.').first) evaluated = child_node_by_name(old_code) case evaluated when Parser::AST::Node if evaluated.type == :args evaluated.loc.expression.source[1...-1] else evaluated.loc.expression.source end when Array if evaluated.size > 0 file_source = evaluated.first.loc.expression.source_buffer.source source = file_source[evaluated.first.loc.expression.begin_pos...evaluated.last.loc.expression.end_pos] lines = source.split "\n" lines_count = lines.length if lines_count > 1 && lines_count == evaluated.size new_code = [] lines.each_with_index { |line, index| new_code << (index == 0 ? line : line[evaluated.first.indent - 2..-1]) } new_code.join("\n") else source end end when String, Symbol, Integer, Float evaluated when NilClass 'nil' else raise Synvert::Core::MethodNotSupported, "rewritten_source is not handled for #{evaluated.inspect}" end else "{{#{old_code}}}" end end end |
#siblings ⇒ Array<Parser::AST::Node>
Get the sibling nodes.
90 91 92 93 |
# File 'lib/synvert/core/node_ext.rb', line 90 def siblings index = parent.children.index(self) parent.children[index + 1..] end |
#strip_curly_braces ⇒ String
Strip curly braces for hash.
561 562 563 564 565 |
# File 'lib/synvert/core/node_ext.rb', line 561 def strip_curly_braces return to_source unless type == :hash to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip } end |
#to_hash ⇒ Object
Convert node to a hash, so that it can be converted to a json.
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 |
# File 'lib/synvert/core/node_ext.rb', line 632 def to_hash result = { type: type } if TYPE_CHILDREN[type] TYPE_CHILDREN[type].each do |key| value = send(key) result[key] = case value when Array value.map { |v| v.respond_to?(:to_hash) ? v.to_hash : v } when Parser::AST::Node value.to_hash else value end end else result[:children] = children.map { |c| c.respond_to?(:to_hash) ? c.to_hash : c } end result end |
#to_lambda_literal ⇒ String
Convert lambda {} to -> {}
616 617 618 619 620 621 622 623 624 625 626 627 628 629 |
# File 'lib/synvert/core/node_ext.rb', line 616 def to_lambda_literal if type == :block && caller.type == :send && caller.receiver.nil? && caller. == :lambda new_source = to_source if arguments.size > 1 new_source = new_source[0...arguments.first.loc.expression.begin_pos - 2] + new_source[arguments.last.loc.expression.end_pos + 1..-1] new_source = new_source.sub('lambda', "->(#{arguments.map(&:to_source).join(', ')})") else new_source = new_source.sub('lambda', '->') end new_source else to_source end end |
#to_single_quote ⇒ String
Get single quote string.
583 584 585 586 587 |
# File 'lib/synvert/core/node_ext.rb', line 583 def to_single_quote return to_source unless type == :str "'#{to_value}'" end |
#to_source ⇒ String
Get the source code of node.
347 348 349 |
# File 'lib/synvert/core/node_ext.rb', line 347 def to_source loc.expression&.source end |
#to_string ⇒ String
Convert symbol to string.
605 606 607 608 609 |
# File 'lib/synvert/core/node_ext.rb', line 605 def to_string return to_source unless type == :sym to_value.to_s end |
#to_symbol ⇒ String
Convert string to symbol.
594 595 596 597 598 |
# File 'lib/synvert/core/node_ext.rb', line 594 def to_symbol return to_source unless type == :str ":#{to_value}" end |
#to_value ⇒ Object
Return the exact value of node. It supports :array, :begin, :erange, :false, :float, :irange, :int, :str, :sym and :true nodes.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/synvert/core/node_ext.rb', line 262 def to_value case type when :int, :float, :str, :sym children.last when :true true when :false false when :nil nil when :array children.map(&:to_value) when :irange (children.first.to_value..children.last.to_value) when :erange (children.first.to_value...children.last.to_value) when :begin children.first.to_value else self end end |
#values ⇒ Array<Parser::AST::Node>
Get values of :hash node.
216 217 218 219 220 221 222 |
# File 'lib/synvert/core/node_ext.rb', line 216 def values if :hash == type children.map { |child| child.children[1] } else raise Synvert::Core::MethodNotSupported, "keys is not handled for #{debug_info}" end end |
#wrap_curly_braces ⇒ String
Wrap curly braces for hash.
572 573 574 575 576 |
# File 'lib/synvert/core/node_ext.rb', line 572 def wrap_curly_braces return to_source unless type == :hash "{ #{to_source} }" end |