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?language=ruby
Instance Method Summary collapse
-
#arguments ⇒ Array<Parser::AST::Node>
Get arguments of node.
-
#body ⇒ Array<Parser::AST::Node>
Get body of node.
-
#caller ⇒ Parser::AST::Node
Get caller 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 ⇒ Parser::AST::Node
Get key node of hash :pair 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.
-
#message ⇒ Symbol
Get message of node.
-
#method_missing(method_name, *args, &block) ⇒ Object
Respond key value and source for hash node, e.g.
-
#name ⇒ Parser::AST::Node
Get the name of node.
-
#parent ⇒ Parser::AST::Node
Get the parent node.
-
#parent=(node) ⇒ Object
Set the parent node.
-
#parent_class ⇒ Parser::AST::Node
Get parent_class of node.
-
#parent_const ⇒ Parser::AST::Node
Get parent constant of node.
-
#receiver ⇒ Parser::AST::Node
Get receiver of 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.
-
#right_value ⇒ Array<Parser::AST::Node>
Return the right value of node.
-
#siblings ⇒ Array<Parser::AST::Node>
Get the sibling nodes.
-
#strip_curly_braces ⇒ String
Strip curly braces for hash.
-
#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.
-
#value ⇒ Parser::AST::Node
Get value node of hash :pair 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.
35 36 37 38 39 40 41 42 43 44 |
# File 'lib/synvert/core/node_ext.rb', line 35 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.
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/synvert/core/node_ext.rb', line 379 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.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/synvert/core/node_ext.rb', line 156 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.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/synvert/core/node_ext.rb', line 193 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 |
#caller ⇒ Parser::AST::Node
Get caller of node. It support :block node.
178 179 180 181 182 183 184 |
# File 'lib/synvert/core/node_ext.rb', line 178 def caller if :block == type children[0] else raise Synvert::Core::MethodNotSupported, "caller is not handled for #{debug_info}" end end |
#child_node_by_name(child_name) ⇒ Parser::AST::Node
Get child node by the name.
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/synvert/core/node_ext.rb', line 458 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.
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 508 509 510 511 512 513 514 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 |
# File 'lib/synvert/core/node_ext.rb', line 480 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.
443 444 445 |
# File 'lib/synvert/core/node_ext.rb', line 443 def column loc.expression.column end |
#condition ⇒ Parser::AST::Node
Get condition of node. It supports :if node.
217 218 219 220 221 222 223 |
# File 'lib/synvert/core/node_ext.rb', line 217 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.
416 417 418 419 420 421 422 423 424 |
# File 'lib/synvert/core/node_ext.rb', line 416 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.
429 430 431 |
# File 'lib/synvert/core/node_ext.rb', line 429 def filename loc.expression&.source_buffer.name end |
#hash_value(key) ⇒ Parser::AST::Node
Get :hash value node according to specified key.
275 276 277 278 279 280 281 282 |
# File 'lib/synvert/core/node_ext.rb', line 275 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 ⇒ Parser::AST::Node
Get key node of hash :pair node.
290 291 292 293 294 295 296 |
# File 'lib/synvert/core/node_ext.rb', line 290 def key if :pair == type children.first else raise Synvert::Core::MethodNotSupported, "key is not handled for #{debug_info}" end end |
#key?(key) ⇒ Boolean
Check if :hash node contains specified key.
260 261 262 263 264 265 266 |
# File 'lib/synvert/core/node_ext.rb', line 260 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.
231 232 233 234 235 236 237 |
# File 'lib/synvert/core/node_ext.rb', line 231 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.
319 320 321 322 323 324 325 326 327 |
# File 'lib/synvert/core/node_ext.rb', line 319 def left_value if %i[masgn lvasgn ivasgn cvasgn and or].include? type children[0] elsif :or_asgn == type children[0].children[0] else raise Synvert::Core::MethodNotSupported, "left_value is not handled for #{debug_info}" end end |
#line ⇒ Integer
Get the line of node.
450 451 452 |
# File 'lib/synvert/core/node_ext.rb', line 450 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
.
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 |
# File 'lib/synvert/core/node_ext.rb', line 569 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 |
#message ⇒ Symbol
Get message of node. It support :csend, :send, :super and :zsuper nodes.
138 139 140 141 142 143 144 145 146 147 |
# File 'lib/synvert/core/node_ext.rb', line 138 def case type when :super, :zsuper :super when :send, :csend children[1] else raise Synvert::Core::MethodNotSupported, "message is not handled for #{debug_info}" end end |
#name ⇒ Parser::AST::Node
Get the name of node. It supports :arg, :blockarg, :class, :const, :cvar, :def, :defs, :ivar, :lvar, :mlhs, :module and :restarg nodes.
73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/synvert/core/node_ext.rb', line 73 def name case type when :class, :module, :def, :arg, :blockarg, :restarg, :lvar, :ivar, :cvar children[0] when :defs, :const children[1] when :mlhs self else raise Synvert::Core::MethodNotSupported, "name is not handled for #{debug_info}" end end |
#parent ⇒ Parser::AST::Node
Get the parent node.
48 49 50 |
# File 'lib/synvert/core/node_ext.rb', line 48 def parent @mutable_attributes[:parent] end |
#parent=(node) ⇒ Object
Set the parent node.
54 55 56 |
# File 'lib/synvert/core/node_ext.rb', line 54 def parent=(node) @mutable_attributes[:parent] = node end |
#parent_class ⇒ Parser::AST::Node
Get parent_class of node. It supports :class node.
93 94 95 96 97 98 99 |
# File 'lib/synvert/core/node_ext.rb', line 93 def parent_class if :class == type children[1] else raise Synvert::Core::MethodNotSupported, "parent_class is not handled for #{debug_info}" end end |
#parent_const ⇒ Parser::AST::Node
Get parent constant of node. It supports :const node.
108 109 110 111 112 113 114 |
# File 'lib/synvert/core/node_ext.rb', line 108 def parent_const if :const == type children[0] else raise Synvert::Core::MethodNotSupported, "parent_const is not handled for #{debug_info}" end end |
#receiver ⇒ Parser::AST::Node
Get receiver of node. It support :csend and :send nodes.
123 124 125 126 127 128 129 |
# File 'lib/synvert/core/node_ext.rb', line 123 def receiver if %i[csend send].include?(type) children[0] else raise Synvert::Core::MethodNotSupported, "receiver is not handled for #{debug_info}" end end |
#recursive_children {|child| ... } ⇒ Object
Recursively iterate all child nodes of node.
552 553 554 555 556 557 558 559 |
# File 'lib/synvert/core/node_ext.rb', line 552 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
399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/synvert/core/node_ext.rb', line 399 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.
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 |
# File 'lib/synvert/core/node_ext.rb', line 604 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 |
#right_value ⇒ Array<Parser::AST::Node>
Return the right value of node. It supports :cvasgn, :ivasgn, :lvasgn, :masgn, :or and :or_asgn nodes.
336 337 338 339 340 341 342 |
# File 'lib/synvert/core/node_ext.rb', line 336 def right_value if %i[masgn lvasgn ivasgn cvasgn or_asgn and or].include? type children[1] else raise Synvert::Core::MethodNotSupported, "right_value is not handled for #{debug_info}" end end |
#siblings ⇒ Array<Parser::AST::Node>
Get the sibling nodes.
60 61 62 63 |
# File 'lib/synvert/core/node_ext.rb', line 60 def siblings index = parent.children.index(self) parent.children[index + 1..] end |
#strip_curly_braces ⇒ String
Strip curly braces for hash.
650 651 652 653 654 |
# File 'lib/synvert/core/node_ext.rb', line 650 def strip_curly_braces return to_source unless type == :hash to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip } end |
#to_lambda_literal ⇒ String
Convert lambda {} to -> {}
705 706 707 708 709 710 711 712 713 714 715 716 717 718 |
# File 'lib/synvert/core/node_ext.rb', line 705 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.
672 673 674 675 676 |
# File 'lib/synvert/core/node_ext.rb', line 672 def to_single_quote return to_source unless type == :str "'#{to_value}'" end |
#to_source ⇒ String
Get the source code of node.
436 437 438 |
# File 'lib/synvert/core/node_ext.rb', line 436 def to_source loc.expression&.source end |
#to_string ⇒ String
Convert symbol to string.
694 695 696 697 698 |
# File 'lib/synvert/core/node_ext.rb', line 694 def to_string return to_source unless type == :sym to_value.to_s end |
#to_symbol ⇒ String
Convert string to symbol.
683 684 685 686 687 |
# File 'lib/synvert/core/node_ext.rb', line 683 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.
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/synvert/core/node_ext.rb', line 351 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 |
#value ⇒ Parser::AST::Node
Get value node of hash :pair node.
304 305 306 307 308 309 310 |
# File 'lib/synvert/core/node_ext.rb', line 304 def value if :pair == type children.last else raise Synvert::Core::MethodNotSupported, "value is not handled for #{debug_info}" end end |
#values ⇒ Array<Parser::AST::Node>
Get values of :hash node.
245 246 247 248 249 250 251 |
# File 'lib/synvert/core/node_ext.rb', line 245 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.
661 662 663 664 665 |
# File 'lib/synvert/core/node_ext.rb', line 661 def wrap_curly_braces return to_source unless type == :hash "{ #{to_source} }" end |