From 208014961ba6b84e640f94ed2f96458389159f0f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 31 Mar 2022 13:13:41 -0400 Subject: [PATCH 01/23] Fix up CHANGELOG links --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 354776f1..afeb8899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,7 +128,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - 🎉 Initial release! 🎉 -[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.0...HEAD +[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.1...HEAD +[2.0.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.2.0...v2.0.0 [1.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.1...v1.2.0 [1.1.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.0...v1.1.1 From 45ebf0b15f6bdef12e32a6023bb0d4a19cc1ec4c Mon Sep 17 00:00:00 2001 From: Maple Ong Date: Thu, 31 Mar 2022 15:46:06 -0400 Subject: [PATCH 02/23] Implement visitor pattern to SyntaxTree::Node This pattern will expand our horizons and allow us to do fun stuff with the nodes through the SyntaxTree::Visitor class. We've chosen to require the Visitor class in node.rb to make it clear that the file requires it. Otherwise, we'd have to add it to lib/syntax_tree.rb in an order-dependent way, which is undesirable. Co-authored-by: Kaan Ozkan Co-authored-by: Alexandre Terrasa Co-authored-by: Vinicius Stock Co-authored-by: Emily Giurleo Co-authored-by: Rafael Franca --- lib/syntax_tree/node.rb | 25 ++++++++++++++++++ lib/syntax_tree/visitor.rb | 15 +++++++++++ test/visitor_test.rb | 54 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 lib/syntax_tree/visitor.rb create mode 100644 test/visitor_test.rb diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 937e1981..b146c6b4 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "syntax_tree/visitor" + module SyntaxTree # Represents the location of a node in the tree from the source code. class Location @@ -56,6 +58,29 @@ class Node # [Location] the location of this node attr_reader :location + def self.inherited(child) + child.class_eval(<<~EOS, __FILE__, __LINE__ + 1) + def accept(visitor) + visitor.#{child.visit_method_name}(self) + end + EOS + + Visitor.class_eval(<<~EOS, __FILE__, __LINE__ + 1) + def #{child.visit_method_name}(node) + visit_all(node.child_nodes) + end + EOS + end + + def self.visit_method_name + method_suffix = name.split("::").last + method_suffix.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') + method_suffix.gsub!(/([a-z\d])([A-Z])/, '\1_\2') + method_suffix.tr!("-", "_") + method_suffix.downcase! + "visit_#{method_suffix}" + end + def child_nodes raise NotImplementedError end diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb new file mode 100644 index 00000000..d1da8e3a --- /dev/null +++ b/lib/syntax_tree/visitor.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module SyntaxTree + class Visitor + def visit_all(nodes) + nodes.each do |node| + visit(node) + end + end + + def visit(node) + node&.accept(self) + end + end +end diff --git a/test/visitor_test.rb b/test/visitor_test.rb new file mode 100644 index 00000000..de166948 --- /dev/null +++ b/test/visitor_test.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require_relative "test_helper" +require "objspace" + +class VisitorTest < Minitest::Test + def test_can_visit_all_nodes + visitor = SyntaxTree::Visitor.new + + ObjectSpace.each_object(SyntaxTree::Node.singleton_class) + .reject { |node| node.singleton_class? || node == SyntaxTree::Node } + .each { |node| assert_respond_to(visitor, node.visit_method_name) } + end + + def test_node_visit_method_name + assert_equal("visit_t_string_end", SyntaxTree::TStringEnd.visit_method_name) + end + + def test_visit_tree + parsed_tree = SyntaxTree.parse(<<~RUBY) + class Foo + def foo; end + + class Bar + def bar; end + end + end + + def baz; end + RUBY + + visitor = DummyVisitor.new + visitor.visit(parsed_tree) + assert_equal(["Foo", "foo", "Bar", "bar", "baz"], visitor.visited_nodes) + end + + class DummyVisitor < SyntaxTree::Visitor + attr_reader :visited_nodes + + def initialize + super + @visited_nodes = [] + end + + def visit_class_declaration(node) + @visited_nodes << node.constant.constant.value + super + end + + def visit_def(node) + @visited_nodes << node.name.value + end + end +end From 15075dbbe3f8bda5bc4d2ebdb6f2c25b8cbcbea2 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 1 Apr 2022 09:40:27 -0400 Subject: [PATCH 03/23] Reformat node.rb --- lib/syntax_tree/node.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index b146c6b4..6c06c93b 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -1871,7 +1871,6 @@ def to_json(*opts) end end - # Binary represents any expression that involves two sub-expressions with an # operator in between. This can be something that looks like a mathematical # operation: From 2a914c9ffacd1992cb4aa7830f0fc46e7c83bdc2 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 1 Apr 2022 10:53:11 -0400 Subject: [PATCH 04/23] More bespoke visitor pattern --- lib/syntax_tree/node.rb | 663 +++++++++++++++++++++++++++++++++++-- lib/syntax_tree/visitor.rb | 536 +++++++++++++++++++++++++++++- test/visitor_test.rb | 23 +- 3 files changed, 1187 insertions(+), 35 deletions(-) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 6c06c93b..b41cbc0a 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -58,27 +58,8 @@ class Node # [Location] the location of this node attr_reader :location - def self.inherited(child) - child.class_eval(<<~EOS, __FILE__, __LINE__ + 1) - def accept(visitor) - visitor.#{child.visit_method_name}(self) - end - EOS - - Visitor.class_eval(<<~EOS, __FILE__, __LINE__ + 1) - def #{child.visit_method_name}(node) - visit_all(node.child_nodes) - end - EOS - end - - def self.visit_method_name - method_suffix = name.split("::").last - method_suffix.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') - method_suffix.gsub!(/([a-z\d])([A-Z])/, '\1_\2') - method_suffix.tr!("-", "_") - method_suffix.downcase! - "visit_#{method_suffix}" + def accept(visitor) + raise NotImplementedError end def child_nodes @@ -132,6 +113,10 @@ def initialize(lbrace:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_BEGIN(self) + end + def child_nodes [lbrace, statements] end @@ -201,6 +186,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_CHAR(self) + end + def child_nodes [] end @@ -265,6 +254,10 @@ def initialize(lbrace:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_END(self) + end + def child_nodes [lbrace, statements] end @@ -337,6 +330,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit___end__(self) + end + def child_nodes [] end @@ -426,6 +423,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_alias(self) + end + def child_nodes [left, right] end @@ -507,6 +508,10 @@ def initialize(collection:, index:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_aref(self) + end + def child_nodes [collection, index] end @@ -588,6 +593,10 @@ def initialize(collection:, index:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_aref_field(self) + end + def child_nodes [collection, index] end @@ -670,6 +679,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_arg_paren(self) + end + def child_nodes [arguments] end @@ -734,6 +747,10 @@ def initialize(parts:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_args(self) + end + def child_nodes parts end @@ -783,6 +800,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_arg_block(self) + end + def child_nodes [value] end @@ -835,6 +856,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_arg_star(self) + end + def child_nodes [value] end @@ -898,6 +923,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_args_forward(self) + end + def child_nodes [] end @@ -1027,6 +1056,10 @@ def initialize(lbracket:, contents:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_array(self) + end + def child_nodes [lbracket, contents] end @@ -1199,6 +1232,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_aryptn(self) + end + def child_nodes [constant, *requireds, rest, *posts] end @@ -1326,6 +1363,10 @@ def initialize(target:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_assign(self) + end + def child_nodes [target, value] end @@ -1408,6 +1449,10 @@ def initialize(key:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_assoc(self) + end + def child_nodes [key, value] end @@ -1488,6 +1533,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_assoc_splat(self) + end + def child_nodes [value] end @@ -1542,6 +1591,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_backref(self) + end + def child_nodes [] end @@ -1590,6 +1643,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_backtick(self) + end + def child_nodes [] end @@ -1704,6 +1761,10 @@ def initialize(assocs:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_bare_assoc_hash(self) + end + def child_nodes assocs end @@ -1762,6 +1823,10 @@ def initialize(bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_begin(self) + end + def child_nodes [bodystmt] end @@ -1826,6 +1891,10 @@ def initialize(statement:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_pinned_begin(self) + end + def child_nodes [statement] end @@ -1902,6 +1971,10 @@ def initialize(left:, operator:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_binary(self) + end + def child_nodes [left, right] end @@ -2035,6 +2108,10 @@ def initialize(params:, locals:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_block_var(self) + end + def child_nodes [params, *locals] end @@ -2101,6 +2178,10 @@ def initialize(name:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_block_arg(self) + end + def child_nodes [name] end @@ -2202,6 +2283,10 @@ def empty? statements.empty? && !rescue_clause && !else_clause && !ensure_clause end + def accept(visitor) + visitor.visit_bodystmt(self) + end + def child_nodes [statements, rescue_clause, else_clause, ensure_clause] end @@ -2471,6 +2556,10 @@ def initialize(lbrace:, block_var:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_brace_block(self) + end + def child_nodes [lbrace, block_var, statements] end @@ -2591,6 +2680,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_break(self) + end + def child_nodes [arguments] end @@ -2682,6 +2775,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_call(self) + end + def child_nodes [ receiver, @@ -2799,6 +2896,10 @@ def initialize(keyword:, value:, consequent:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_case(self) + end + def child_nodes [keyword, value, consequent] end @@ -2889,6 +2990,10 @@ def initialize(value:, operator:, pattern:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rassign(self) + end + def child_nodes [value, operator, pattern] end @@ -3002,6 +3107,10 @@ def initialize(constant:, superclass:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_class(self) + end + def child_nodes [constant, superclass, bodystmt] end @@ -3088,6 +3197,10 @@ class Comma < Node # [String] the comma in the string attr_reader :value + def accept(visitor) + visitor.visit_comma(self) + end + def initialize(value:, location:) @value = value @location = location @@ -3117,6 +3230,10 @@ def initialize(message:, arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_command(self) + end + def child_nodes [message, arguments] end @@ -3202,6 +3319,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_command_call(self) + end + def child_nodes [receiver, message, arguments] end @@ -3381,6 +3502,10 @@ def comments [] end + def accept(visitor) + visitor.visit_comment(self) + end + def child_nodes [] end @@ -3441,6 +3566,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_const(self) + end + def child_nodes [] end @@ -3496,6 +3625,10 @@ def initialize(parent:, constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_const_path_field(self) + end + def child_nodes [parent, constant] end @@ -3563,6 +3696,10 @@ def initialize(parent:, constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_const_path_ref(self) + end + def child_nodes [parent, constant] end @@ -3628,6 +3765,10 @@ def initialize(constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_const_ref(self) + end + def child_nodes [constant] end @@ -3680,6 +3821,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_cvar(self) + end + def child_nodes [] end @@ -3737,6 +3882,10 @@ def initialize(name:, params:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_def(self) + end + def child_nodes [name, params, bodystmt] end @@ -3843,6 +3992,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_def_endless(self) + end + def child_nodes [target, operator, name, paren, statement] end @@ -3945,6 +4098,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_defined(self) + end + def child_nodes [value] end @@ -4024,6 +4181,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_defs(self) + end + def child_nodes [target, operator, name, params, bodystmt] end @@ -4128,6 +4289,10 @@ def initialize(keyword:, block_var:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_do_block(self) + end + def child_nodes [keyword, block_var, bodystmt] end @@ -4231,6 +4396,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_dot2(self) + end + def child_nodes [left, right] end @@ -4303,6 +4472,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_dot3(self) + end + def child_nodes [left, right] end @@ -4414,6 +4587,10 @@ def initialize(parts:, quote:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_dyna_symbol(self) + end + def child_nodes parts end @@ -4522,6 +4699,10 @@ def initialize(statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_else(self) + end + def child_nodes [statements] end @@ -4596,6 +4777,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_elsif(self) + end + def child_nodes [predicate, statements, consequent] end @@ -4694,6 +4879,10 @@ def comments [] end + def accept(visitor) + visitor.visit_embdoc(self) + end + def child_nodes [] end @@ -4733,6 +4922,10 @@ class EmbExprBeg < Node # [String] the #{ used in the string attr_reader :value + def accept(visitor) + visitor.visit_embexpr_beg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -4749,6 +4942,10 @@ class EmbExprEnd < Node # [String] the } used in the string attr_reader :value + def accept(visitor) + visitor.visit_embexpr_end(self) + end + def initialize(value:, location:) @value = value @location = location @@ -4767,6 +4964,10 @@ class EmbVar < Node # [String] the # used in the string attr_reader :value + def accept(visitor) + visitor.visit_embvar(self) + end + def initialize(value:, location:) @value = value @location = location @@ -4797,6 +4998,10 @@ def initialize(keyword:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_ensure(self) + end + def child_nodes [keyword, statements] end @@ -4868,6 +5073,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_excessed_comma(self) + end + def child_nodes [] end @@ -4927,6 +5136,10 @@ def initialize(value:, arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_fcall(self) + end + def child_nodes [value, arguments] end @@ -5000,6 +5213,10 @@ def initialize(parent:, operator:, name:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_field(self) + end + def child_nodes [parent, (operator if operator != :"::"), name] end @@ -5070,6 +5287,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_float(self) + end + def child_nodes [] end @@ -5135,6 +5356,10 @@ def initialize(constant:, left:, values:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_fndptn(self) + end + def child_nodes [constant, left, *values, right] end @@ -5229,6 +5454,10 @@ def initialize(index:, collection:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_for(self) + end + def child_nodes [index, collection, statements] end @@ -5310,6 +5539,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_gvar(self) + end + def child_nodes [] end @@ -5363,6 +5596,10 @@ def initialize(lbrace:, assocs:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_hash(self) + end + def child_nodes [lbrace] + assocs end @@ -5451,6 +5688,10 @@ def initialize(beginning:, ending: nil, parts: [], location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_heredoc(self) + end + def child_nodes [beginning, *parts] end @@ -5541,6 +5782,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_heredoc_beg(self) + end + def child_nodes [] end @@ -5649,6 +5894,10 @@ def initialize(constant:, keywords:, keyword_rest:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_hshptn(self) + end + def child_nodes [constant, *keywords.flatten(1), keyword_rest] end @@ -5756,6 +6005,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_ident(self) + end + def child_nodes [] end @@ -5901,6 +6154,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_if(self) + end + def child_nodes [predicate, statements, consequent] end @@ -5977,6 +6234,10 @@ def initialize(predicate:, truthy:, falsy:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_if_op(self) + end + def child_nodes [predicate, truthy, falsy] end @@ -6139,6 +6400,10 @@ def initialize(statement:, predicate:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_if_mod(self) + end + def child_nodes [statement, predicate] end @@ -6200,6 +6465,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_imaginary(self) + end + def child_nodes [] end @@ -6260,6 +6529,10 @@ def initialize(pattern:, statements:, consequent:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_in(self) + end + def child_nodes [pattern, statements, consequent] end @@ -6345,6 +6618,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_int(self) + end + def child_nodes [] end @@ -6400,6 +6677,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_ivar(self) + end + def child_nodes [] end @@ -6458,6 +6739,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_kw(self) + end + def child_nodes [] end @@ -6506,6 +6791,10 @@ def initialize(name:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_kwrest_param(self) + end + def child_nodes [name] end @@ -6568,6 +6857,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_label(self) + end + def child_nodes [] end @@ -6612,6 +6905,10 @@ class LabelEnd < Node # [String] the end of the label attr_reader :value + def accept(visitor) + visitor.visit_label_end(self) + end + def initialize(value:, location:) @value = value @location = location @@ -6639,6 +6936,10 @@ def initialize(params:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_lambda(self) + end + def child_nodes [params, statements] end @@ -6728,6 +7029,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_lbrace(self) + end + def child_nodes [] end @@ -6774,6 +7079,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_lbracket(self) + end + def child_nodes [] end @@ -6820,6 +7129,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_lparen(self) + end + def child_nodes [] end @@ -6883,6 +7196,10 @@ def initialize(target:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_massign(self) + end + def child_nodes [target, value] end @@ -6950,6 +7267,10 @@ def initialize(call:, block:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_method_add_block(self) + end + def child_nodes [call, block] end @@ -7015,6 +7336,10 @@ def initialize(parts:, comma: false, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_mlhs(self) + end + def child_nodes parts end @@ -7070,6 +7395,10 @@ def initialize(contents:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_mlhs_paren(self) + end + def child_nodes [contents] end @@ -7140,6 +7469,10 @@ def initialize(constant:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_module(self) + end + def child_nodes [constant, bodystmt] end @@ -7227,6 +7560,10 @@ def initialize(parts:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_mrhs(self) + end + def child_nodes parts end @@ -7289,6 +7626,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_next(self) + end + def child_nodes [arguments] end @@ -7339,6 +7680,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_op(self) + end + def child_nodes [] end @@ -7396,6 +7741,10 @@ def initialize(target:, operator:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_op_assign(self) + end + def child_nodes [target, operator, value] end @@ -7646,6 +7995,10 @@ def empty? keywords.empty? && !keyword_rest && !block end + def accept(visitor) + visitor.visit_params(self) + end + def child_nodes [ *requireds, @@ -7811,6 +8164,10 @@ def initialize(lparen:, contents:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_paren(self) + end + def child_nodes [lparen, contents] end @@ -7879,6 +8236,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_period(self) + end + def child_nodes [] end @@ -7925,6 +8286,10 @@ def initialize(statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_program(self) + end + def child_nodes [statements] end @@ -7987,6 +8352,10 @@ def initialize(beginning:, elements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_qsymbols(self) + end + def child_nodes [] end @@ -8053,6 +8422,10 @@ class QSymbolsBeg < Node # [String] the beginning of the array literal attr_reader :value + def accept(visitor) + visitor.visit_qsymbols_beg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -8080,6 +8453,10 @@ def initialize(beginning:, elements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_qwords(self) + end + def child_nodes [] end @@ -8143,6 +8520,10 @@ class QWordsBeg < Node # [String] the beginning of the array literal attr_reader :value + def accept(visitor) + visitor.visit_qwords_beg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -8166,6 +8547,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rational(self) + end + def child_nodes [] end @@ -8203,6 +8588,10 @@ class RBrace < Node # [String] the right brace attr_reader :value + def accept(visitor) + visitor.visit_rbrace(self) + end + def initialize(value:, location:) @value = value @location = location @@ -8214,6 +8603,10 @@ class RBracket < Node # [String] the right bracket attr_reader :value + def accept(visitor) + visitor.visit_rbracket(self) + end + def initialize(value:, location:) @value = value @location = location @@ -8237,6 +8630,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_redo(self) + end + def child_nodes [] end @@ -8283,6 +8680,10 @@ class RegexpContent < Node # regular expression attr_reader :parts + def accept(visitor) + visitor.visit_regexp_content(self) + end + def initialize(beginning:, parts:, location:) @beginning = beginning @parts = parts @@ -8303,6 +8704,10 @@ class RegexpBeg < Node # [String] the beginning of the regular expression attr_reader :value + def accept(visitor) + visitor.visit_regexp_beg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -8323,6 +8728,10 @@ class RegexpEnd < Node # [String] the end of the regular expression attr_reader :value + def accept(visitor) + visitor.visit_regexp_end(self) + end + def initialize(value:, location:) @value = value @location = location @@ -8355,6 +8764,10 @@ def initialize(beginning:, ending:, parts:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_regexp_literal(self) + end + def child_nodes parts end @@ -8478,6 +8891,10 @@ def initialize(exceptions:, variable:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rescue_ex(self) + end + def child_nodes [*exceptions, variable] end @@ -8582,6 +8999,10 @@ def bind_end(end_char) end end + def accept(visitor) + visitor.visit_rescue(self) + end + def child_nodes [exception, statements, consequent] end @@ -8676,6 +9097,10 @@ def initialize(statement:, value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rescue_mod(self) + end + def child_nodes [statement, value] end @@ -8750,6 +9175,10 @@ def initialize(name:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_rest_param(self) + end + def child_nodes [name] end @@ -8800,6 +9229,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_retry(self) + end + def child_nodes [] end @@ -8849,6 +9282,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_return(self) + end + def child_nodes [arguments] end @@ -8898,6 +9335,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_return0(self) + end + def child_nodes [] end @@ -8935,6 +9376,10 @@ class RParen < Node # [String] the parenthesis attr_reader :value + def accept(visitor) + visitor.visit_rparen(self) + end + def initialize(value:, location:) @value = value @location = location @@ -8965,6 +9410,10 @@ def initialize(target:, bodystmt:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_sclass(self) + end + def child_nodes [target, bodystmt] end @@ -9081,6 +9530,10 @@ def empty? end end + def accept(visitor) + visitor.visit_statements(self) + end + def child_nodes body end @@ -9203,6 +9656,10 @@ class StringContent < Node # string attr_reader :parts + def accept(visitor) + visitor.visit_string_content(self) + end + def initialize(parts:, location:) @parts = parts @location = location @@ -9232,6 +9689,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_string_concat(self) + end + def child_nodes [left, right] end @@ -9297,6 +9758,10 @@ def initialize(variable:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_string_dvar(self) + end + def child_nodes [variable] end @@ -9353,6 +9818,10 @@ def initialize(statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_string_embexpr(self) + end + def child_nodes [statements] end @@ -9427,6 +9896,10 @@ def initialize(parts:, quote:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_string_literal(self) + end + def child_nodes parts end @@ -9507,6 +9980,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_super(self) + end + def child_nodes [arguments] end @@ -9568,6 +10045,10 @@ class SymBeg < Node # [String] the beginning of the symbol attr_reader :value + def accept(visitor) + visitor.visit_sym_beg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -9584,6 +10065,10 @@ class SymbolContent < Node # symbol attr_reader :value + def accept(visitor) + visitor.visit_symbol_content(self) + end + def initialize(value:, location:) @value = value @location = location @@ -9609,6 +10094,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_symbol_literal(self) + end + def child_nodes [value] end @@ -9666,6 +10155,10 @@ def initialize(beginning:, elements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_symbols(self) + end + def child_nodes [] end @@ -9733,6 +10226,10 @@ class SymbolsBeg < Node # [String] the beginning of the symbol literal array attr_reader :value + def accept(visitor) + visitor.visit_symbols_beg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -9748,6 +10245,10 @@ class TLambda < Node # [String] the beginning of the lambda literal attr_reader :value + def accept(visitor) + visitor.visit_tlambda(self) + end + def initialize(value:, location:) @value = value @location = location @@ -9764,6 +10265,10 @@ class TLamBeg < Node # [String] the beginning of the body of the lambda literal attr_reader :value + def accept(visitor) + visitor.visit_tlambeg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -9789,6 +10294,10 @@ def initialize(constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_top_const_field(self) + end + def child_nodes [constant] end @@ -9843,6 +10352,10 @@ def initialize(constant:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_top_const_ref(self) + end + def child_nodes [constant] end @@ -9893,6 +10406,10 @@ class TStringBeg < Node # [String] the beginning of the string attr_reader :value + def accept(visitor) + visitor.visit_tstring_beg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -9924,6 +10441,10 @@ def match?(pattern) value.match?(pattern) end + def accept(visitor) + visitor.visit_tstring_content(self) + end + def child_nodes [] end @@ -9973,6 +10494,10 @@ class TStringEnd < Node # [String] the end of the string attr_reader :value + def accept(visitor) + visitor.visit_tstring_end(self) + end + def initialize(value:, location:) @value = value @location = location @@ -10000,6 +10525,10 @@ def initialize(statement:, parentheses:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_not(self) + end + def child_nodes [statement] end @@ -10065,6 +10594,10 @@ def initialize(operator:, statement:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_unary(self) + end + def child_nodes [statement] end @@ -10148,6 +10681,10 @@ def initialize(symbols:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_undef(self) + end + def child_nodes symbols end @@ -10220,6 +10757,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_unless(self) + end + def child_nodes [predicate, statements, consequent] end @@ -10292,6 +10833,10 @@ def initialize(statement:, predicate:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_unless_mod(self) + end + def child_nodes [statement, predicate] end @@ -10407,6 +10952,10 @@ def initialize(predicate:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_until(self) + end + def child_nodes [predicate, statements] end @@ -10483,6 +11032,10 @@ def initialize(statement:, predicate:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_until_mod(self) + end + def child_nodes [statement, predicate] end @@ -10569,6 +11122,10 @@ def initialize(left:, right:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_var_alias(self) + end + def child_nodes [left, right] end @@ -10632,6 +11189,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_var_field(self) + end + def child_nodes [value] end @@ -10685,6 +11246,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_var_ref(self) + end + def child_nodes [value] end @@ -10739,6 +11304,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_pinned_var_ref(self) + end + def child_nodes [value] end @@ -10795,6 +11364,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_vcall(self) + end + def child_nodes [value] end @@ -10843,6 +11416,10 @@ def initialize(location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_void_stmt(self) + end + def child_nodes [] end @@ -10901,6 +11478,10 @@ def initialize( @comments = comments end + def accept(visitor) + visitor.visit_when(self) + end + def child_nodes [arguments, statements, consequent] end @@ -11008,6 +11589,10 @@ def initialize(predicate:, statements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_while(self) + end + def child_nodes [predicate, statements] end @@ -11084,6 +11669,10 @@ def initialize(statement:, predicate:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_while_mod(self) + end + def child_nodes [statement, predicate] end @@ -11173,6 +11762,10 @@ def match?(pattern) parts.any? { |part| part.is_a?(TStringContent) && part.match?(pattern) } end + def accept(visitor) + visitor.visit_word(self) + end + def child_nodes parts end @@ -11226,6 +11819,10 @@ def initialize(beginning:, elements:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_words(self) + end + def child_nodes [] end @@ -11290,6 +11887,10 @@ class WordsBeg < Node # [String] the start of the word literal array attr_reader :value + def accept(visitor) + visitor.visit_words_beg(self) + end + def initialize(value:, location:) @value = value @location = location @@ -11305,6 +11906,10 @@ class XString < Node # xstring attr_reader :parts + def accept(visitor) + visitor.visit_xstring(self) + end + def initialize(parts:, location:) @parts = parts @location = location @@ -11329,6 +11934,10 @@ def initialize(parts:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_xstring_literal(self) + end + def child_nodes parts end @@ -11383,6 +11992,10 @@ def initialize(arguments:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_yield(self) + end + def child_nodes [arguments] end @@ -11446,6 +12059,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_yield0(self) + end + def child_nodes [] end @@ -11495,6 +12112,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def accept(visitor) + visitor.visit_zsuper(self) + end + def child_nodes [] end diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index d1da8e3a..18e6f277 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -2,14 +2,542 @@ module SyntaxTree class Visitor - def visit_all(nodes) - nodes.each do |node| - visit(node) + # This is raised when you use the Visitor.visit_method method and it fails. + # It is correctable to through DidYouMean. + class VisitMethodError < StandardError + attr_reader :visit_method + + def initialize(visit_method) + @visit_method = visit_method + super("Invalid visit method: #{visit_method}") + end + end + + # This class is used by DidYouMean to offer corrections to invalid visit + # method names. + class VisitMethodChecker + attr_reader :visit_method + + def initialize(error) + @visit_method = error.visit_method + end + + def corrections + @corrections ||= + DidYouMean::SpellChecker.new(dictionary: Visitor.visit_methods).correct(visit_method) + end + + DidYouMean.correct_error(VisitMethodError, self) + end + + class << self + # This method is here to help folks write visitors. + # + # It's not always easy to ensure you're writing the correct method name in + # the visitor since it's perfectly valid to define methods that don't + # override these parent methods. + # + # If you use this method, you can ensure you're writing the correct method + # name. It will raise an error if the visit method you're defining isn't + # actually a method on the parent visitor. + def visit_method(method_name) + return if visit_methods.include?(method_name) + + raise VisitMethodError, method_name + end + + # This is the list of all of the valid visit methods. + def visit_methods + Visitor.instance_methods.grep(/^visit_(?!child_nodes)/) end end def visit(node) - node&.accept(self) + node.accept(self) end + + def visit_child_nodes(node) + node.child_nodes.each { |child_node| visit(child_node) if child_node } + end + + # Visit an ARef node. + alias visit_aref visit_child_nodes + + # Visit an ARefField node. + alias visit_aref_field visit_child_nodes + + # Visit an Alias node. + alias visit_alias visit_child_nodes + + # Visit an ArgBlock node. + alias visit_arg_block visit_child_nodes + + # Visit an ArgParen node. + alias visit_arg_paren visit_child_nodes + + # Visit an ArgStar node. + alias visit_arg_star visit_child_nodes + + # Visit an Args node. + alias visit_args visit_child_nodes + + # Visit an ArgsForward node. + alias visit_args_forward visit_child_nodes + + # Visit an ArrayLiteral node. + alias visit_array visit_child_nodes + + # Visit an AryPtn node. + alias visit_aryptn visit_child_nodes + + # Visit an Assign node. + alias visit_assign visit_child_nodes + + # Visit an Assoc node. + alias visit_assoc visit_child_nodes + + # Visit an AssocSplat node. + alias visit_assoc_splat visit_child_nodes + + # Visit a BEGINBlock node. + alias visit_BEGIN visit_child_nodes + + # Visit a Backref node. + alias visit_backref visit_child_nodes + + # Visit a Backtick node. + alias visit_backtick visit_child_nodes + + # Visit a BareAssocHash node. + alias visit_bare_assoc_hash visit_child_nodes + + # Visit a Begin node. + alias visit_begin visit_child_nodes + + # Visit a Binary node. + alias visit_binary visit_child_nodes + + # Visit a BlockArg node. + alias visit_block_arg visit_child_nodes + + # Visit a BlockVar node. + alias visit_block_var visit_child_nodes + + # Visit a BodyStmt node. + alias visit_bodystmt visit_child_nodes + + # Visit a BraceBlock node. + alias visit_brace_block visit_child_nodes + + # Visit a Break node. + alias visit_break visit_child_nodes + + # Visit a CHAR node. + alias visit_CHAR visit_child_nodes + + # Visit a CVar node. + alias visit_cvar visit_child_nodes + + # Visit a Call node. + alias visit_call visit_child_nodes + + # Visit a Case node. + alias visit_case visit_child_nodes + + # Visit a ClassDeclaration node. + alias visit_class visit_child_nodes + + # Visit a Comma node. + alias visit_comma visit_child_nodes + + # Visit a Command node. + alias visit_command visit_child_nodes + + # Visit a CommandCall node. + alias visit_command_call visit_child_nodes + + # Visit a Comment node. + alias visit_comment visit_child_nodes + + # Visit a Const node. + alias visit_const visit_child_nodes + + # Visit a ConstPathField node. + alias visit_const_path_field visit_child_nodes + + # Visit a ConstPathRef node. + alias visit_const_path_ref visit_child_nodes + + # Visit a ConstRef node. + alias visit_const_ref visit_child_nodes + + # Visit a Def node. + alias visit_def visit_child_nodes + + # Visit a DefEndless node. + alias visit_def_endless visit_child_nodes + + # Visit a Defined node. + alias visit_defined visit_child_nodes + + # Visit a Defs node. + alias visit_defs visit_child_nodes + + # Visit a DoBlock node. + alias visit_do_block visit_child_nodes + + # Visit a Dot2 node. + alias visit_dot2 visit_child_nodes + + # Visit a Dot3 node. + alias visit_dot3 visit_child_nodes + + # Visit a DynaSymbol node. + alias visit_dyna_symbol visit_child_nodes + + # Visit an ENDBlock node. + alias visit_END visit_child_nodes + + # Visit an Else node. + alias visit_else visit_child_nodes + + # Visit an Elsif node. + alias visit_elsif visit_child_nodes + + # Visit an EmbDoc node. + alias visit_embdoc visit_child_nodes + + # Visit an EmbExprBeg node. + alias visit_embexpr_beg visit_child_nodes + + # Visit an EmbExprEnd node. + alias visit_embexpr_end visit_child_nodes + + # Visit an EmbVar node. + alias visit_embvar visit_child_nodes + + # Visit an EndContent node. + alias visit___end__ visit_child_nodes + + # Visit an Ensure node. + alias visit_ensure visit_child_nodes + + # Visit an ExcessedComma node. + alias visit_excessed_comma visit_child_nodes + + # Visit a FCall node. + alias visit_fcall visit_child_nodes + + # Visit a Field node. + alias visit_field visit_child_nodes + + # Visit a FloatLiteral node. + alias visit_float visit_child_nodes + + # Visit a FndPtn node. + alias visit_fndptn visit_child_nodes + + # Visit a For node. + alias visit_for visit_child_nodes + + # Visit a GVar node. + alias visit_gvar visit_child_nodes + + # Visit a HashLiteral node. + alias visit_hash visit_child_nodes + + # Visit a Heredoc node. + alias visit_heredoc visit_child_nodes + + # Visit a HeredocBeg node. + alias visit_heredoc_beg visit_child_nodes + + # Visit a HshPtn node. + alias visit_hshptn visit_child_nodes + + # Visit an IVar node. + alias visit_ivar visit_child_nodes + + # Visit an Ident node. + alias visit_ident visit_child_nodes + + # Visit an If node. + alias visit_if visit_child_nodes + + # Visit an IfMod node. + alias visit_if_mod visit_child_nodes + + # Visit an IfOp node. + alias visit_if_op visit_child_nodes + + # Visit an Imaginary node. + alias visit_imaginary visit_child_nodes + + # Visit an In node. + alias visit_in visit_child_nodes + + # Visit an Int node. + alias visit_int visit_child_nodes + + # Visit a Kw node. + alias visit_kw visit_child_nodes + + # Visit a KwRestParam node. + alias visit_kwrest_param visit_child_nodes + + # Visit a LBrace node. + alias visit_lbrace visit_child_nodes + + # Visit a LBracket node. + alias visit_lbracket visit_child_nodes + + # Visit a LParen node. + alias visit_lparen visit_child_nodes + + # Visit a Label node. + alias visit_label visit_child_nodes + + # Visit a LabelEnd node. + alias visit_label_end visit_child_nodes + + # Visit a Lambda node. + alias visit_lambda visit_child_nodes + + # Visit a MAssign node. + alias visit_massign visit_child_nodes + + # Visit a MLHS node. + alias visit_mlhs visit_child_nodes + + # Visit a MLHSParen node. + alias visit_mlhs_paren visit_child_nodes + + # Visit a MRHS node. + alias visit_mrhs visit_child_nodes + + # Visit a MethodAddBlock node. + alias visit_method_add_block visit_child_nodes + + # Visit a ModuleDeclaration node. + alias visit_module visit_child_nodes + + # Visit a Next node. + alias visit_next visit_child_nodes + + # Visit a Not node. + alias visit_not visit_child_nodes + + # Visit an Op node. + alias visit_op visit_child_nodes + + # Visit an OpAssign node. + alias visit_op_assign visit_child_nodes + + # Visit a Params node. + alias visit_params visit_child_nodes + + # Visit a Paren node. + alias visit_paren visit_child_nodes + + # Visit a Period node. + alias visit_period visit_child_nodes + + # Visit a PinnedBegin node. + alias visit_pinned_begin visit_child_nodes + + # Visit a PinnedVarRef node. + alias visit_pinned_var_ref visit_child_nodes + + # Visit a Program node. + alias visit_program visit_child_nodes + + # Visit a QSymbols node. + alias visit_qsymbols visit_child_nodes + + # Visit a QSymbolsBeg node. + alias visit_qsymbols_beg visit_child_nodes + + # Visit a QWords node. + alias visit_qwords visit_child_nodes + + # Visit a QWordsBeg node. + alias visit_qwords_beg visit_child_nodes + + # Visit a RAssign node. + alias visit_rassign visit_child_nodes + + # Visit a RBrace node. + alias visit_rbrace visit_child_nodes + + # Visit a RBracket node. + alias visit_rbracket visit_child_nodes + + # Visit a RParen node. + alias visit_rparen visit_child_nodes + + # Visit a RationalLiteral node. + alias visit_rational visit_child_nodes + + # Visit a Redo node. + alias visit_redo visit_child_nodes + + # Visit a RegexpBeg node. + alias visit_regexp_beg visit_child_nodes + + # Visit a RegexpContent node. + alias visit_regexp_content visit_child_nodes + + # Visit a RegexpEnd node. + alias visit_regexp_end visit_child_nodes + + # Visit a RegexpLiteral node. + alias visit_regexp_literal visit_child_nodes + + # Visit a Rescue node. + alias visit_rescue visit_child_nodes + + # Visit a RescueEx node. + alias visit_rescue_ex visit_child_nodes + + # Visit a RescueMod node. + alias visit_rescue_mod visit_child_nodes + + # Visit a RestParam node. + alias visit_rest_param visit_child_nodes + + # Visit a Retry node. + alias visit_retry visit_child_nodes + + # Visit a Return node. + alias visit_return visit_child_nodes + + # Visit a Return0 node. + alias visit_return0 visit_child_nodes + + # Visit a SClass node. + alias visit_sclass visit_child_nodes + + # Visit a Statements node. + alias visit_statements visit_child_nodes + + # Visit a StringConcat node. + alias visit_string_concat visit_child_nodes + + # Visit a StringContent node. + alias visit_string_content visit_child_nodes + + # Visit a StringDVar node. + alias visit_string_dvar visit_child_nodes + + # Visit a StringEmbExpr node. + alias visit_string_embexpr visit_child_nodes + + # Visit a StringLiteral node. + alias visit_string_literal visit_child_nodes + + # Visit a Super node. + alias visit_super visit_child_nodes + + # Visit a SymBeg node. + alias visit_sym_beg visit_child_nodes + + # Visit a SymbolContent node. + alias visit_symbol_content visit_child_nodes + + # Visit a SymbolLiteral node. + alias visit_symbol_literal visit_child_nodes + + # Visit a Symbols node. + alias visit_symbols visit_child_nodes + + # Visit a SymbolsBeg node. + alias visit_symbols_beg visit_child_nodes + + # Visit a TLamBeg node. + alias visit_tlambeg visit_child_nodes + + # Visit a TLambda node. + alias visit_tlambda visit_child_nodes + + # Visit a TStringBeg node. + alias visit_tstring_beg visit_child_nodes + + # Visit a TStringContent node. + alias visit_tstring_content visit_child_nodes + + # Visit a TStringEnd node. + alias visit_tstring_end visit_child_nodes + + # Visit a TopConstField node. + alias visit_top_const_field visit_child_nodes + + # Visit a TopConstRef node. + alias visit_top_const_ref visit_child_nodes + + # Visit an Unary node. + alias visit_unary visit_child_nodes + + # Visit an Undef node. + alias visit_undef visit_child_nodes + + # Visit an Unless node. + alias visit_unless visit_child_nodes + + # Visit an UnlessMod node. + alias visit_unless_mod visit_child_nodes + + # Visit an Until node. + alias visit_until visit_child_nodes + + # Visit an UntilMod node. + alias visit_until_mod visit_child_nodes + + # Visit a VCall node. + alias visit_vcall visit_child_nodes + + # Visit a VarAlias node. + alias visit_var_alias visit_child_nodes + + # Visit a VarField node. + alias visit_var_field visit_child_nodes + + # Visit a VarRef node. + alias visit_var_ref visit_child_nodes + + # Visit a VoidStmt node. + alias visit_void_stmt visit_child_nodes + + # Visit a When node. + alias visit_when visit_child_nodes + + # Visit a While node. + alias visit_while visit_child_nodes + + # Visit a WhileMod node. + alias visit_while_mod visit_child_nodes + + # Visit a Word node. + alias visit_word visit_child_nodes + + # Visit a Words node. + alias visit_words visit_child_nodes + + # Visit a WordsBeg node. + alias visit_words_beg visit_child_nodes + + # Visit a XString node. + alias visit_xstring visit_child_nodes + + # Visit a XStringLiteral node. + alias visit_xstring_literal visit_child_nodes + + # Visit a Yield node. + alias visit_yield visit_child_nodes + + # Visit a Yield0 node. + alias visit_yield0 visit_child_nodes + + # Visit a ZSuper node. + alias visit_zsuper visit_child_nodes end end diff --git a/test/visitor_test.rb b/test/visitor_test.rb index de166948..95244c96 100644 --- a/test/visitor_test.rb +++ b/test/visitor_test.rb @@ -1,19 +1,22 @@ # frozen_string_literal: true require_relative "test_helper" -require "objspace" class VisitorTest < Minitest::Test - def test_can_visit_all_nodes + def test_visit_all_nodes visitor = SyntaxTree::Visitor.new - ObjectSpace.each_object(SyntaxTree::Node.singleton_class) - .reject { |node| node.singleton_class? || node == SyntaxTree::Node } - .each { |node| assert_respond_to(visitor, node.visit_method_name) } - end + filepath = File.expand_path("../lib/syntax_tree/node.rb", __dir__) + program = SyntaxTree.parse(SyntaxTree.read(filepath)) + + program.statements.body.last.bodystmt.statements.body.each do |node| + next unless node in SyntaxTree::ClassDeclaration[superclass: { value: { value: "Node" } }] - def test_node_visit_method_name - assert_equal("visit_t_string_end", SyntaxTree::TStringEnd.visit_method_name) + accept = node.bodystmt.statements.body.detect { |defm| defm in SyntaxTree::Def[name: { value: "accept" }] } + accept => { bodystmt: { statements: { body: [SyntaxTree::Call[message: { value: visit_method }]] } } } + + assert_respond_to(visitor, visit_method) + end end def test_visit_tree @@ -42,12 +45,12 @@ def initialize @visited_nodes = [] end - def visit_class_declaration(node) + visit_method def visit_class(node) @visited_nodes << node.constant.constant.value super end - def visit_def(node) + visit_method def visit_def(node) @visited_nodes << node.name.value end end From c682cd62c26ba1c8c9b4602460259ec3c3a17898 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 1 Apr 2022 11:20:22 -0400 Subject: [PATCH 05/23] Memoize Visitor.visit_methods --- lib/syntax_tree/visitor.rb | 3 ++- test/visitor_test.rb | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index 18e6f277..ddab8f0a 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -48,7 +48,8 @@ def visit_method(method_name) # This is the list of all of the valid visit methods. def visit_methods - Visitor.instance_methods.grep(/^visit_(?!child_nodes)/) + @visit_methods ||= + Visitor.instance_methods.grep(/^visit_(?!child_nodes)/) end end diff --git a/test/visitor_test.rb b/test/visitor_test.rb index 95244c96..3cdf0d85 100644 --- a/test/visitor_test.rb +++ b/test/visitor_test.rb @@ -3,19 +3,21 @@ require_relative "test_helper" class VisitorTest < Minitest::Test - def test_visit_all_nodes - visitor = SyntaxTree::Visitor.new + unless ENV["FAST"] + def test_visit_all_nodes + visitor = SyntaxTree::Visitor.new - filepath = File.expand_path("../lib/syntax_tree/node.rb", __dir__) - program = SyntaxTree.parse(SyntaxTree.read(filepath)) + filepath = File.expand_path("../lib/syntax_tree/node.rb", __dir__) + program = SyntaxTree.parse(SyntaxTree.read(filepath)) - program.statements.body.last.bodystmt.statements.body.each do |node| - next unless node in SyntaxTree::ClassDeclaration[superclass: { value: { value: "Node" } }] + program.statements.body.last.bodystmt.statements.body.each do |node| + next unless node in SyntaxTree::ClassDeclaration[superclass: { value: { value: "Node" } }] - accept = node.bodystmt.statements.body.detect { |defm| defm in SyntaxTree::Def[name: { value: "accept" }] } - accept => { bodystmt: { statements: { body: [SyntaxTree::Call[message: { value: visit_method }]] } } } + accept = node.bodystmt.statements.body.detect { |defm| defm in SyntaxTree::Def[name: { value: "accept" }] } + accept => { bodystmt: { statements: { body: [SyntaxTree::Call[message: { value: visit_method }]] } } } - assert_respond_to(visitor, visit_method) + assert_respond_to(visitor, visit_method) + end end end From d224dc22ccd2535564635449d63d44a2af8d3aee Mon Sep 17 00:00:00 2001 From: Alexandre Terrasa Date: Fri, 1 Apr 2022 11:34:01 -0400 Subject: [PATCH 06/23] Allow passing `nil` to `visit` Signed-off-by: Alexandre Terrasa --- lib/syntax_tree/visitor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index ddab8f0a..87e6ef76 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -54,11 +54,11 @@ def visit_methods end def visit(node) - node.accept(self) + node&.accept(self) end def visit_child_nodes(node) - node.child_nodes.each { |child_node| visit(child_node) if child_node } + node.child_nodes.each { |child_node| visit(child_node) } end # Visit an ARef node. From a7681a6f38e5ba13748ed2b7654741d58b3b99cf Mon Sep 17 00:00:00 2001 From: Alexandre Terrasa Date: Fri, 1 Apr 2022 11:36:16 -0400 Subject: [PATCH 07/23] Add `arm64-darwin-21` platform Signed-off-by: Alexandre Terrasa --- Gemfile.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile.lock b/Gemfile.lock index f3ddbb47..8fef7cc3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -25,6 +25,7 @@ GEM stackprof (0.2.19) PLATFORMS + arm64-darwin-21 x86_64-darwin-19 x86_64-darwin-21 x86_64-linux From a33e675a01187db6be5f234510fe58e22bbffaca Mon Sep 17 00:00:00 2001 From: Alexandre Terrasa Date: Fri, 1 Apr 2022 11:34:33 -0400 Subject: [PATCH 08/23] Introduce `visit_all` to visit multiple nodes Signed-off-by: Alexandre Terrasa --- lib/syntax_tree/visitor.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index 87e6ef76..27561cdf 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -57,8 +57,12 @@ def visit(node) node&.accept(self) end + def visit_all(nodes) + nodes.each { |node| visit(node) } + end + def visit_child_nodes(node) - node.child_nodes.each { |child_node| visit(child_node) } + visit_all(node.child_nodes) end # Visit an ARef node. From 4865a46842d362f7fb380ceccebbe8bcecc37378 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 17:33:50 +0000 Subject: [PATCH 09/23] Bump ruby_parser from 3.19.0 to 3.19.1 Bumps [ruby_parser](https://github.com/seattlerb/ruby_parser) from 3.19.0 to 3.19.1. - [Release notes](https://github.com/seattlerb/ruby_parser/releases) - [Changelog](https://github.com/seattlerb/ruby_parser/blob/master/History.rdoc) - [Commits](https://github.com/seattlerb/ruby_parser/compare/v3.19.0...v3.19.1) --- updated-dependencies: - dependency-name: ruby_parser dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8fef7cc3..8c8e93bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,7 +13,7 @@ GEM parser (3.1.1.0) ast (~> 2.4.1) rake (13.0.6) - ruby_parser (3.19.0) + ruby_parser (3.19.1) sexp_processor (~> 4.16) sexp_processor (4.16.0) simplecov (0.21.2) From fb04c4a746e07e68148cfbe0c379c98d3f138271 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 7 Apr 2022 20:30:22 -0400 Subject: [PATCH 10/23] Method prefixes We shouldn't indent Command nodes if they end in Def/Defs/DefEndless nodes. --- lib/syntax_tree/node.rb | 50 ++++++++++++++++++++++++++++++++++------ test/fixtures/command.rb | 24 +++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index b41cbc0a..16fb7b2e 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -1998,12 +1998,18 @@ def format(q) q.group { q.format(left) } q.text(" ") unless power - q.group do + if operator == :<< q.text(operator) + q.text(" ") + q.format(right) + else + q.group do + q.text(operator) - q.indent do - q.breakable(power ? "" : " ") - q.format(right) + q.indent do + q.breakable(power ? "" : " ") + q.format(right) + end end end end @@ -3253,7 +3259,12 @@ def format(q) q.group do q.format(message) q.text(" ") - q.nest(message.value.length + 1) { q.format(arguments) } + + if align?(self) + q.nest(message.value.length + 1) { q.format(arguments) } + else + q.format(arguments) + end end end @@ -3280,6 +3291,18 @@ def to_json(*opts) cmts: comments }.to_json(*opts) end + + private + + def align?(node) + if node.arguments in Args[parts: [Def | Defs | DefEndless]] + false + elsif node.arguments in Args[parts: [Command => command]] + align?(command) + else + true + end + end end # CommandCall represents a method call on an object with arguments and no @@ -7766,9 +7789,15 @@ def format(q) q.format(target) q.text(" ") q.format(operator) - q.indent do - q.breakable + + if skip_indent? + q.text(" ") q.format(value) + else + q.indent do + q.breakable + q.format(value) + end end end end @@ -7800,6 +7829,13 @@ def to_json(*opts) cmts: comments }.to_json(*opts) end + + private + + def skip_indent? + target.comments.empty? && + (target.is_a?(ARefField) || AssignFormatting.skip_indent?(value)) + end end # If you have a modifier statement (for instance a modifier if statement or a diff --git a/test/fixtures/command.rb b/test/fixtures/command.rb index d74ae8ab..d457aa28 100644 --- a/test/fixtures/command.rb +++ b/test/fixtures/command.rb @@ -5,3 +5,27 @@ - foo barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr, bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +% +meta1 def foo +end +% +meta2 meta1 def foo +end +% +meta3 meta2 meta1 def foo +end +% +meta1 def self.foo +end +% +meta2 meta1 def self.foo +end +% +meta3 meta2 meta1 def self.foo +end +% +meta1 def foo = 1 +% +meta2 meta1 def foo = 1 +% +meta3 meta2 meta1 def foo = 1 From ca3a09028352ad74a1bc698c209f98b1472a7450 Mon Sep 17 00:00:00 2001 From: Alexandre Terrasa Date: Fri, 8 Apr 2022 11:57:12 -0400 Subject: [PATCH 11/23] Fix documented type for BareAssocHash#assocs Signed-off-by: Alexandre Terrasa --- lib/syntax_tree/node.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 16fb7b2e..a10cca60 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -1749,7 +1749,7 @@ def self.for(container) # method(key1: value1, key2: value2) # class BareAssocHash < Node - # [Array[ AssocNew | AssocSplat ]] + # [Array[ Assoc | AssocSplat ]] attr_reader :assocs # [Array[ Comment | EmbDoc ]] the comments attached to this node From 4efb084ec7e9a6ef8e6119e96f0bb2ca03051247 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 11 Apr 2022 13:10:34 -0400 Subject: [PATCH 12/23] Handle multiple Ruby versions --- .github/workflows/main.yml | 10 +++++++- Gemfile.lock | 1 + lib/syntax_tree/node.rb | 5 ++-- test/fixtures/begin.rb | 4 --- test/fixtures/command.rb | 6 ----- test/fixtures/command_def_endless.rb | 6 +++++ test/fixtures/pinned_begin.rb | 4 +++ test/fixtures/var_field.rb | 10 -------- test/fixtures/var_field_rassign.rb | 10 ++++++++ test/formatting_test.rb | 27 +++++++++++++++----- test/node_test.rb | 38 ++++++++++++++++------------ test/parser_test.rb | 33 ++++++++++++++++++++++++ test/visitor_test.rb | 23 ++++++++++++++--- 13 files changed, 128 insertions(+), 49 deletions(-) create mode 100644 test/fixtures/command_def_endless.rb create mode 100644 test/fixtures/pinned_begin.rb create mode 100644 test/fixtures/var_field_rassign.rb create mode 100644 test/parser_test.rb diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5a7c30c9..63d51a3c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,14 @@ on: - pull_request_target jobs: ci: + strategy: + fail-fast: false + matrix: + ruby: + - '2.7' + - '3.0' + - '3.1' + - head name: CI runs-on: ubuntu-latest env: @@ -13,7 +21,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: bundler-cache: true - ruby-version: '3.1' + ruby-version: ${{ matrix.ruby }} - name: Test run: bundle exec rake test automerge: diff --git a/Gemfile.lock b/Gemfile.lock index 8c8e93bd..b489b9ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,6 +26,7 @@ GEM PLATFORMS arm64-darwin-21 + ruby x86_64-darwin-19 x86_64-darwin-21 x86_64-linux diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index a10cca60..f66ef40a 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -3295,9 +3295,10 @@ def to_json(*opts) private def align?(node) - if node.arguments in Args[parts: [Def | Defs | DefEndless]] + case node.arguments + in Args[parts: [Def | Defs | DefEndless]] false - elsif node.arguments in Args[parts: [Command => command]] + in Args[parts: [Command => command]] align?(command) else true diff --git a/test/fixtures/begin.rb b/test/fixtures/begin.rb index f9e5b775..efd12dad 100644 --- a/test/fixtures/begin.rb +++ b/test/fixtures/begin.rb @@ -5,7 +5,3 @@ begin expression end -% -case value -in ^(expression) -end diff --git a/test/fixtures/command.rb b/test/fixtures/command.rb index d457aa28..7f061acd 100644 --- a/test/fixtures/command.rb +++ b/test/fixtures/command.rb @@ -23,9 +23,3 @@ % meta3 meta2 meta1 def self.foo end -% -meta1 def foo = 1 -% -meta2 meta1 def foo = 1 -% -meta3 meta2 meta1 def foo = 1 diff --git a/test/fixtures/command_def_endless.rb b/test/fixtures/command_def_endless.rb new file mode 100644 index 00000000..e6890b83 --- /dev/null +++ b/test/fixtures/command_def_endless.rb @@ -0,0 +1,6 @@ +% +meta1 def foo = 1 +% +meta2 meta1 def foo = 1 +% +meta3 meta2 meta1 def foo = 1 diff --git a/test/fixtures/pinned_begin.rb b/test/fixtures/pinned_begin.rb new file mode 100644 index 00000000..d67ac9cf --- /dev/null +++ b/test/fixtures/pinned_begin.rb @@ -0,0 +1,4 @@ +% +case value +in ^(expression) +end diff --git a/test/fixtures/var_field.rb b/test/fixtures/var_field.rb index 2b78c098..8c1258af 100644 --- a/test/fixtures/var_field.rb +++ b/test/fixtures/var_field.rb @@ -8,13 +8,3 @@ foo = bar % @foo = bar -% -foo in bar -% -foo in ^bar -% -foo in ^@bar -% -foo in ^@@bar -% -foo in ^$gvar diff --git a/test/fixtures/var_field_rassign.rb b/test/fixtures/var_field_rassign.rb new file mode 100644 index 00000000..3e019c5c --- /dev/null +++ b/test/fixtures/var_field_rassign.rb @@ -0,0 +1,10 @@ +% +foo in bar +% +foo in ^bar +% +foo in ^@bar +% +foo in ^@@bar +% +foo in ^$gvar diff --git a/test/formatting_test.rb b/test/formatting_test.rb index f7c3b3f7..4b649052 100644 --- a/test/formatting_test.rb +++ b/test/formatting_test.rb @@ -4,13 +4,28 @@ module SyntaxTree class FormattingTest < Minitest::Test - delimiter = /%(?: # (.+?))?\n/ + FIXTURES_3_0_0 = %w[ + command_def_endless + def_endless + fndptn + rassign + rassign_rocket + ] + + FIXTURES_3_1_0 = %w[ + pinned_begin + var_field_rassign + ] - Dir[File.join(__dir__, "fixtures", "*.rb")].each do |filepath| - basename = File.basename(filepath, ".rb") - sources = File.readlines(filepath).slice_before(delimiter) + fixtures = Dir[File.join(__dir__, "fixtures", "*.rb")].map { |filepath| File.basename(filepath, ".rb") } + fixtures -= FIXTURES_3_1_0 if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.1.0") + fixtures -= FIXTURES_3_0_0 if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0.0") + + delimiter = /%(?: # (.+?))?\n/ + fixtures.each do |fixture| + filepath = File.join(__dir__, "fixtures", "#{fixture}.rb") - sources.each_with_index do |source, index| + File.readlines(filepath).slice_before(delimiter).each_with_index do |source, index| comment = source.shift.match(delimiter)[1] original, expected = source.join.split("-\n") @@ -22,7 +37,7 @@ class FormattingTest < Minitest::Test next if Gem::Version.new(RUBY_VERSION) < version end - define_method(:"test_formatting_#{basename}_#{index}") do + define_method(:"test_formatting_#{fixture}_#{index}") do assert_equal(expected || original, SyntaxTree.format(original)) end end diff --git a/test/node_test.rb b/test/node_test.rb index 1382ed79..9c29f79d 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -289,12 +289,14 @@ def test_case assert_node(Case, "case", source) end - def test_rassign_in - assert_node(RAssign, "rassign", "value in pattern") - end + guard_version("3.0.0") do + def test_rassign_in + assert_node(RAssign, "rassign", "value in pattern") + end - def test_rassign_rocket - assert_node(RAssign, "rassign", "value => pattern") + def test_rassign_rocket + assert_node(RAssign, "rassign", "value => pattern") + end end def test_class @@ -352,8 +354,10 @@ def method assert_node(Def, "def", source) end - def test_def_endless - assert_node(DefEndless, "def_endless", "def method = result") + guard_version("3.0.0") do + def test_def_endless + assert_node(DefEndless, "def_endless", "def method = result") + end end guard_version("3.1.0") do @@ -478,16 +482,18 @@ def test_float_literal assert_node(FloatLiteral, "float", "1.0") end - def test_fndptn - source = <<~SOURCE - case value - in Container[*, 7, *] - end - SOURCE + guard_version("3.0.0") do + def test_fndptn + source = <<~SOURCE + case value + in Container[*, 7, *] + end + SOURCE - at = location(lines: 2..2, chars: 14..32) - assert_node(FndPtn, "fndptn", source, at: at) do |node| - node.consequent.pattern + at = location(lines: 2..2, chars: 14..32) + assert_node(FndPtn, "fndptn", source, at: at) do |node| + node.consequent.pattern + end end end diff --git a/test/parser_test.rb b/test/parser_test.rb new file mode 100644 index 00000000..e5861398 --- /dev/null +++ b/test/parser_test.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SyntaxTree + class ParserTest < Minitest::Test + def test_parses_ripper_methods + # First, get a list of all of the dispatched events from ripper. + events = Ripper::EVENTS + + # Next, subtract all of the events that we have explicitly defined. + events -= Parser.private_instance_methods(false).grep(/^on_(\w+)/) { $1.to_sym } + + # Next, subtract the list of events that we purposefully skipped. + events -= %i[ + arg_ambiguous + assoclist_from_args + ignored_nl + ignored_sp + magic_comment + nl + nokw_param + operator_ambiguous + semicolon + sp + words_sep + ] + + # Finally, assert that we have no remaining events. + assert_empty(events) + end + end +end diff --git a/test/visitor_test.rb b/test/visitor_test.rb index 3cdf0d85..dd9f1e11 100644 --- a/test/visitor_test.rb +++ b/test/visitor_test.rb @@ -11,12 +11,27 @@ def test_visit_all_nodes program = SyntaxTree.parse(SyntaxTree.read(filepath)) program.statements.body.last.bodystmt.statements.body.each do |node| - next unless node in SyntaxTree::ClassDeclaration[superclass: { value: { value: "Node" } }] + case node + in SyntaxTree::ClassDeclaration[superclass: { value: { value: "Node" } }] + # this is a class we want to look at + else + next + end - accept = node.bodystmt.statements.body.detect { |defm| defm in SyntaxTree::Def[name: { value: "accept" }] } - accept => { bodystmt: { statements: { body: [SyntaxTree::Call[message: { value: visit_method }]] } } } + accept = + node.bodystmt.statements.body.detect do |defm| + case defm + in SyntaxTree::Def[name: { value: "accept" }] + true + else + false + end + end - assert_respond_to(visitor, visit_method) + case accept + in { bodystmt: { statements: { body: [SyntaxTree::Call[message: { value: visit_method }]] } } } + assert_respond_to(visitor, visit_method) + end end end end From 5c6615e1fa76ed204869554b6f64e057be044533 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 11 Apr 2022 14:23:50 -0400 Subject: [PATCH 13/23] Handle comments on keywords in rescue and else clauses --- lib/syntax_tree/node.rb | 45 +++++++++++++++++++++++++++++---------- lib/syntax_tree/parser.rb | 13 +++++++---- test/fixtures/bodystmt.rb | 20 +++++++++++++++++ test/fixtures/else.rb | 4 ++++ 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index f66ef40a..24cb49ea 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -2233,6 +2233,9 @@ class BodyStmt < Node # [nil | Rescue] the optional rescue chain attached to the begin clause attr_reader :rescue_clause + # [nil | Kw] the optional else keyword + attr_reader :else_keyword + # [nil | Statements] the optional set of statements inside the else clause attr_reader :else_clause @@ -2245,6 +2248,7 @@ class BodyStmt < Node def initialize( statements:, rescue_clause:, + else_keyword:, else_clause:, ensure_clause:, location:, @@ -2252,6 +2256,7 @@ def initialize( ) @statements = statements @rescue_clause = rescue_clause + @else_keyword = else_keyword @else_clause = else_clause @ensure_clause = ensure_clause @location = location @@ -2294,7 +2299,7 @@ def accept(visitor) end def child_nodes - [statements, rescue_clause, else_clause, ensure_clause] + [statements, rescue_clause, else_keyword, else_clause, ensure_clause] end alias deconstruct child_nodes @@ -2324,10 +2329,13 @@ def format(q) if else_clause q.nest(-2) do q.breakable(force: true) - q.text("else") + q.format(else_keyword) + end + + unless else_clause.empty? + q.breakable(force: true) + q.format(else_clause) end - q.breakable(force: true) - q.format(else_clause) end if ensure_clause @@ -4711,13 +4719,17 @@ def quotes(q) # end # class Else < Node + # [Kw] the else keyword + attr_reader :keyword + # [Statements] the expressions to be executed attr_reader :statements # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(statements:, location:, comments: []) + def initialize(keyword:, statements:, location:, comments: []) + @keyword = keyword @statements = statements @location = location @comments = comments @@ -4728,18 +4740,23 @@ def accept(visitor) end def child_nodes - [statements] + [keyword, statements] end alias deconstruct child_nodes def deconstruct_keys(keys) - { statements: statements, location: location, comments: comments } + { + keyword: keyword, + statements: statements, + location: location, + comments: comments + } end def format(q) q.group do - q.text("else") + q.format(keyword) unless statements.empty? q.indent do @@ -8993,6 +9010,9 @@ def to_json(*opts) # end # class Rescue < Node + # [Kw] the rescue keyword + attr_reader :keyword + # [RescueEx] the exceptions being rescued attr_reader :exception @@ -9006,12 +9026,14 @@ class Rescue < Node attr_reader :comments def initialize( + keyword:, exception:, statements:, consequent:, location:, comments: [] ) + @keyword = keyword @exception = exception @statements = statements @consequent = consequent @@ -9041,13 +9063,14 @@ def accept(visitor) end def child_nodes - [exception, statements, consequent] + [keyword, exception, statements, consequent] end alias deconstruct child_nodes def deconstruct_keys(keys) { + keyword: keyword, exception: exception, statements: statements, consequent: consequent, @@ -9058,10 +9081,10 @@ def deconstruct_keys(keys) def format(q) q.group do - q.text("rescue") + q.format(keyword) if exception - q.nest("rescue ".length) { q.format(exception) } + q.nest(keyword.value.length + 1) { q.format(exception) } else q.text(" StandardError") end diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index 33eeef8b..5bd89dc2 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -682,6 +682,7 @@ def on_bodystmt(statements, rescue_clause, else_clause, ensure_clause) BodyStmt.new( statements: statements, rescue_clause: rescue_clause, + else_keyword: else_clause && find_token(Kw, "else"), else_clause: else_clause, ensure_clause: ensure_clause, location: Location.fixed(line: lineno, char: char_pos) @@ -1115,7 +1116,7 @@ def on_dyna_symbol(string_content) # :call-seq: # on_else: (Statements statements) -> Else def on_else(statements) - beginning = find_token(Kw, "else") + keyword = find_token(Kw, "else") # else can either end with an end keyword (in which case we'll want to # consume that event) or it can end with an ensure keyword (in which case @@ -1127,13 +1128,16 @@ def on_else(statements) node = tokens[index] ending = node.value == "end" ? tokens.delete_at(index) : node - # ending = node - statements.bind(beginning.location.end_char, ending.location.start_char) + statements.bind( + find_next_statement_start(keyword.location.end_char), + ending.location.start_char + ) Else.new( + keyword: keyword, statements: statements, - location: beginning.location.to(ending.location) + location: keyword.location.to(ending.location) ) end @@ -2316,6 +2320,7 @@ def on_rescue(exceptions, variable, statements, consequent) end Rescue.new( + keyword: keyword, exception: rescue_ex, statements: statements, consequent: consequent, diff --git a/test/fixtures/bodystmt.rb b/test/fixtures/bodystmt.rb index 120255a8..4cbb8f5e 100644 --- a/test/fixtures/bodystmt.rb +++ b/test/fixtures/bodystmt.rb @@ -34,3 +34,23 @@ ensure foo end +% +begin +else # else +end +% +begin +ensure # ensure +end +% +begin +rescue # rescue +else # else +ensure # ensure +end +- +begin +rescue StandardError # rescue +else # else +ensure # ensure +end diff --git a/test/fixtures/else.rb b/test/fixtures/else.rb index d3675c27..e440514a 100644 --- a/test/fixtures/else.rb +++ b/test/fixtures/else.rb @@ -18,3 +18,7 @@ else bar end +% +if foo +else # bar +end From 03372d840caf2f59fd537c76a4213282a7d84808 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 11 Apr 2022 14:59:40 -0400 Subject: [PATCH 14/23] Serialize to JSON through a visitor --- lib/syntax_tree.rb | 4 + lib/syntax_tree/json_visitor.rb | 1338 +++++++++++++++++++++++ lib/syntax_tree/node.rb | 1771 +++++++++---------------------- lib/syntax_tree/visitor.rb | 4 +- test/formatting_test.rb | 39 +- test/json_visitor_test.rb | 13 + test/test_helper.rb | 58 + 7 files changed, 1930 insertions(+), 1297 deletions(-) create mode 100644 lib/syntax_tree/json_visitor.rb create mode 100644 test/json_visitor_test.rb diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 1dcfc348..dde21f18 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "json" require "pp" require "prettyprint" require "ripper" @@ -9,6 +10,9 @@ require_relative "syntax_tree/node" require_relative "syntax_tree/parser" require_relative "syntax_tree/version" +require_relative "syntax_tree/visitor" + +require_relative "syntax_tree/json_visitor" # If PrettyPrint::Align isn't defined, then we haven't gotten the updated # version of prettyprint. In that case we'll define our own. This is going to diff --git a/lib/syntax_tree/json_visitor.rb b/lib/syntax_tree/json_visitor.rb new file mode 100644 index 00000000..a557eab2 --- /dev/null +++ b/lib/syntax_tree/json_visitor.rb @@ -0,0 +1,1338 @@ +# frozen_string_literal: true + +module SyntaxTree + class JSONVisitor < Visitor + def visit_aref(node) + { + type: :aref, + collection: visit(node.collection), + index: visit(node.index), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_aref_field(node) + { + type: :aref_field, + collection: visit(node.collection), + index: visit(node.index), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_alias(node) + { + type: :alias, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_block(node) + { + type: :arg_block, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_paren(node) + { + type: :arg_paren, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_star(node) + { + type: :arg_star, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_args(node) + { + type: :args, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_args_forward(node) + visit_token(:args_forward, node) + end + + def visit_array(node) + { + type: :array, + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_aryptn(node) + { + type: :aryptn, + constant: visit(node.constant), + reqs: visit_all(node.requireds), + rest: visit(node.rest), + posts: visit_all(node.posts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assign(node) + { + type: :assign, + target: visit(node.target), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assoc(node) + { + type: :assoc, + key: visit(node.key), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assoc_splat(node) + { + type: :assoc_splat, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_BEGIN(node) + { + type: :BEGIN, + lbrace: visit(node.lbrace), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_backref(node) + visit_token(:backref, node) + end + + def visit_backtick(node) + visit_token(:backtick, node) + end + + def visit_bare_assoc_hash(node) + { + type: :bare_assoc_hash, + assocs: visit_all(node.assocs), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_begin(node) + { + type: :begin, + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_binary(node) + { + type: :binary, + left: visit(node.left), + op: node.operator, + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_block_arg(node) + { + type: :blockarg, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_block_var(node) + { + type: :block_var, + params: visit(node.params), + locals: visit_all(node.locals), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_bodystmt(node) + { + type: :bodystmt, + stmts: visit(node.statements), + rsc: visit(node.rescue_clause), + els: visit(node.else_clause), + ens: visit(node.ensure_clause), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_brace_block(node) + { + type: :brace_block, + lbrace: visit(node.lbrace), + block_var: visit(node.block_var), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_break(node) + { + type: :break, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_call(node) + { + type: :call, + receiver: visit(node.receiver), + op: visit_call_operator(node.operator), + message: node.message == :call ? :call : visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_case(node) + { + type: :case, + value: visit(node.value), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_CHAR(node) + visit_token(:CHAR, node) + end + + def visit_class(node) + { + type: :class, + constant: visit(node.constant), + superclass: visit(node.superclass), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_comma(node) + visit_token(:comma, node) + end + + def visit_command(node) + { + type: :command, + message: visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_command_call(node) + { + type: :command_call, + receiver: visit(node.receiver), + op: visit_call_operator(node.operator), + message: visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_comment(node) + { + type: :comment, + value: node.value, + inline: node.inline, + loc: visit_location(node.location) + } + end + + def visit_const(node) + visit_token(:const, node) + end + + def visit_const_path_field(node) + { + type: :const_path_field, + parent: visit(node.parent), + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_const_path_ref(node) + { + type: :const_path_ref, + parent: visit(node.parent), + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_const_ref(node) + { + type: :const_ref, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_cvar(node) + visit_token(:cvar, node) + end + + def visit_def(node) + { + type: :def, + name: visit(node.name), + params: visit(node.params), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_def_endless(node) + { + type: :def_endless, + name: visit(node.name), + paren: visit(node.paren), + stmt: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_defined(node) + visit_token(:defined, node) + end + + def visit_defs(node) + { + type: :defs, + target: visit(node.target), + op: visit(node.operator), + name: visit(node.name), + params: visit(node.params), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_do_block(node) + { + type: :do_block, + keyword: visit(node.keyword), + block_var: visit(node.block_var), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dot2(node) + { + type: :dot2, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dot3(node) + { + type: :dot3, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dyna_symbol(node) + { + type: :dyna_symbol, + parts: visit_all(node.parts), + quote: node.quote, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_END(node) + { + type: :END, + lbrace: visit(node.lbrace), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_else(node) + { + type: :else, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_elsif(node) + { + type: :elsif, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_embdoc(node) + { + type: :embdoc, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embexpr_beg(node) + { + type: :embexpr_beg, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embexpr_end(node) + { + type: :embexpr_end, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embvar(node) + { + type: :embvar, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit___end__(node) + visit_token(:__end__, node) + end + + def visit_ensure(node) + { + type: :ensure, + keyword: visit(node.keyword), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_excessed_comma(node) + visit_token(:excessed_comma, node) + end + + def visit_fcall(node) + { + type: :fcall, + value: visit(node.value), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_field(node) + { + type: :field, + parent: visit(node.parent), + op: visit_call_operator(node.operator), + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_float(node) + visit_token(:float, node) + end + + def visit_fndptn(node) + { + type: :fndptn, + constant: visit(node.constant), + left: visit(node.left), + values: visit_all(node.values), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_for(node) + { + type: :for, + index: visit(node.index), + collection: visit(node.collection), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_gvar(node) + visit_token(:gvar, node) + end + + def visit_hash(node) + { + type: :hash, + assocs: visit_all(node.assocs), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_heredoc(node) + { + type: :heredoc, + beging: visit(node.beginning), + ending: node.ending, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_heredoc_beg(node) + visit_token(:heredoc_beg, node) + end + + def visit_hshptn(node) + { + type: :hshptn, + constant: visit(node.constant), + keywords: node.keywords.map { |(name, value)| [visit(name), visit(value)] }, + kwrest: visit(node.keyword_rest), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_ident(node) + visit_token(:ident, node) + end + + def visit_if(node) + { + type: :if, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_if_mod(node) + { + type: :if_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_if_op(node) + { + type: :ifop, + pred: visit(node.predicate), + tthy: visit(node.truthy), + flsy: visit(node.falsy), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_imaginary(node) + visit_token(:imaginary, node) + end + + def visit_in(node) + { + type: :in, + pattern: visit(node.pattern), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_int(node) + visit_token(:int, node) + end + + def visit_ivar(node) + visit_token(:ivar, node) + end + + def visit_kw(node) + visit_token(:kw, node) + end + + def visit_kwrest_param(node) + { + type: :kwrest_param, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_label(node) + visit_token(:label, node) + end + + def visit_label_end(node) + visit_token(:label_end, node) + end + + def visit_lambda(node) + { + type: :lambda, + params: visit(node.params), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_lbrace(node) + visit_token(:lbrace, node) + end + + def visit_lbracket(node) + visit_token(:lbracket, node) + end + + def visit_lparen(node) + visit_token(:lparen, node) + end + + def visit_massign(node) + { + type: :massign, + target: visit(node.target), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_method_add_block(node) + { + type: :method_add_block, + call: visit(node.call), + block: visit(node.block), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mlhs(node) + { + type: :mlhs, + parts: visit_all(node.parts), + comma: node.comma, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mlhs_paren(node) + { + type: :mlhs_paren, + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_module(node) + { + type: :module, + constant: visit(node.constant), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mrhs(node) + { + type: :mrhs, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_next(node) + { + type: :next, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_not(node) + { + type: :not, + value: visit(node.statement), + paren: node.parentheses, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_op(node) + visit_token(:op, node) + end + + def visit_op_assign(node) + { + type: :opassign, + target: visit(node.target), + op: visit(node.operator), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_params(node) + { + type: :params, + reqs: visit_all(node.requireds), + opts: node.optionals.map { |(name, value)| [visit(name), visit(value)] }, + rest: visit(node.rest), + posts: visit_all(node.posts), + keywords: node.keywords.map { |(name, value)| [visit(name), visit(value || nil)] }, + kwrest: node.keyword_rest == :nil ? "nil" : visit(node.keyword_rest), + block: visit(node.block), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_paren(node) + { + type: :paren, + lparen: visit(node.lparen), + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_period(node) + visit_token(:period, node) + end + + def visit_pinned_begin(node) + { + type: :pinned_begin, + stmt: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_pinned_var_ref(node) + { + type: :pinned_var_ref, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_program(node) + { + type: :program, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qsymbols(node) + { + type: :qsymbols, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qsymbols_beg(node) + visit_token(:qsymbols_beg, node) + end + + def visit_qwords(node) + { + type: :qwords, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qwords_beg(node) + visit_token(:qwords_beg, node) + end + + def visit_rassign(node) + { + type: :rassign, + value: visit(node.value), + op: visit(node.operator), + pattern: visit(node.pattern), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rational(node) + visit_token(:rational, node) + end + + def visit_rbrace(node) + visit_token(:rbrace, node) + end + + def visit_rbracket(node) + visit_token(:rbracket, node) + end + + def visit_redo(node) + visit_token(:redo, node) + end + + def visit_regexp_beg(node) + visit_token(:regexp_beg, node) + end + + def visit_regexp_content(node) + { + type: :regexp_content, + beging: node.beginning, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_regexp_end(node) + visit_token(:regexp_end, node) + end + + def visit_regexp_literal(node) + { + type: :regexp_literal, + beging: node.beginning, + ending: node.ending, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue(node) + { + type: :rescue, + extn: visit(node.exception), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue_ex(node) + { + type: :rescue_ex, + extns: visit(node.exceptions), + var: visit(node.variable), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue_mod(node) + { + type: :rescue_mod, + stmt: visit(node.statement), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rest_param(node) + { + type: :rest_param, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_retry(node) + visit_token(:retry, node) + end + + def visit_return(node) + { + type: :return, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_return0(node) + visit_token(:return0, node) + end + + def visit_rparen(node) + visit_token(:rparen, node) + end + + def visit_sclass(node) + { + type: :sclass, + target: visit(node.target), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_statements(node) + { + type: :statements, + body: visit_all(node.body), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_concat(node) + { + type: :string_concat, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_content(node) + { + type: :string_content, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_string_dvar(node) + { + type: :string_dvar, + var: visit(node.variable), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_embexpr(node) + { + type: :string_embexpr, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_literal(node) + { + type: :string_literal, + parts: visit_all(node.parts), + quote: node.quote, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_super(node) + { + type: :super, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbeg(node) + visit_token(:symbeg, node) + end + + def visit_symbol_content(node) + { + type: :symbol_content, + value: visit(node.value), + loc: visit_location(node.location) + } + end + + def visit_symbol_literal(node) + { + type: :symbol_literal, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbols(node) + { + type: :symbols, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbols_beg(node) + visit_token(:symbols_beg, node) + end + + def visit_tlambda(node) + visit_token(:tlambda, node) + end + + def visit_tlambeg(node) + visit_token(:tlambeg, node) + end + + def visit_tstring_beg(node) + visit_token(:tstring_beg, node) + end + + def visit_tstring_content(node) + visit_token(:tstring_content, node) + end + + def visit_tstring_end(node) + visit_token(:tstring_end, node) + end + + def visit_top_const_field(node) + { + type: :top_const_field, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_top_const_ref(node) + { + type: :top_const_ref, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_unary(node) + { + type: :unary, + op: node.operator, + value: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_undef(node) + { + type: :undef, + syms: visit_all(node.symbols), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_unless(node) + { + type: :unless, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_unless_mod(node) + { + type: :unless_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_until(node) + { + type: :until, + pred: visit(node.predicate), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_until_mod(node) + { + type: :until_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_alias(node) + { + type: :var_alias, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_field(node) + { + type: :var_field, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_ref(node) + { + type: :var_ref, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_vcall(node) + { + type: :vcall, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_void_stmt(node) + { + type: :void_stmt, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_when(node) + { + type: :when, + args: visit(node.arguments), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_while(node) + { + type: :while, + pred: visit(node.predicate), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_while_mod(node) + { + type: :while_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_word(node) + { + type: :word, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_words(node) + { + type: :words, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_words_beg(node) + visit_token(:words_beg, node) + end + + def visit_xstring(node) + { + type: :xstring, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_xstring_literal(node) + { + type: :xstring_literal, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_yield(node) + { + type: :yield, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_yield0(node) + visit_token(:yield0, node) + end + + def visit_zsuper(node) + visit_token(:zsuper, node) + end + + # Explicitly undefine the default aliased method for each of the visitor + # methods so that if there is one that isn't implemented it will raise an + # error. + undef visit_child_nodes + + private + + def visit_call_operator(operator) + operator == :"::" ? :"::" : visit(operator) + end + + def visit_location(location) + [ + location.start_line, + location.start_char, + location.end_line, + location.end_char + ] + end + + def visit_token(type, node) + { + type: type, + value: node.value, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + end +end diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 24cb49ea..9081d977 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "syntax_tree/visitor" - module SyntaxTree # Represents the location of a node in the tree from the source code. class Location @@ -33,10 +31,6 @@ def to(other) ) end - def to_json(*opts) - [start_line, start_char, end_line, end_char].to_json(*opts) - end - def self.token(line:, char:, size:) new( start_line: line, @@ -83,7 +77,8 @@ def pretty_print(q) end def to_json(*opts) - raise NotImplementedError + visitor = JSONVisitor.new + visitor.visit(self).to_json(*opts) end end @@ -155,16 +150,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :BEGIN, - lbrace: lbrace, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # CHAR irepresents a single codepoint in the script encoding. @@ -220,12 +205,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :CHAR, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ENDBlock represents the use of the +END+ keyword, which hooks into the @@ -296,16 +275,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :END, - lbrace: lbrace, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # EndContent represents the use of __END__ syntax, which allows individual @@ -362,12 +331,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :__end__, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Alias represents the use of the +alias+ keyword with regular arguments (not @@ -466,16 +429,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :alias, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ARef represents when you're pulling a value out of a collection at a @@ -557,16 +510,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :aref, - collection: collection, - index: index, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ARefField represents assigning values into collections at specific indices. @@ -642,16 +585,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :aref_field, - collection: collection, - index: index, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ArgParen represents wrapping arguments to a method inside a set of @@ -718,15 +651,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :arg_paren, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Args represents a list of arguments being passed to a method call or array @@ -775,12 +699,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :args, parts: parts, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ArgBlock represents using a block operator on an expression. @@ -831,12 +749,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :arg_block, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Star represents using a splat operator on an expression. @@ -885,12 +797,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :arg_star, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ArgsForward represents forwarding all kinds of arguments onto another method @@ -951,15 +857,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :args_forward, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ArrayLiteral represents an array literal, which can optionally contain @@ -1117,12 +1014,6 @@ def pretty_print(q) end end - def to_json(*opts) - { type: :array, cnts: contents, loc: location, cmts: comments }.to_json( - *opts - ) - end - private def qwords? @@ -1308,18 +1199,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :aryptn, - constant: constant, - reqs: requireds, - rest: rest, - posts: posts, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Determins if the following value should be indented or not. @@ -1408,16 +1287,6 @@ def pretty_print(q) end end - def to_json(*opts) - { - type: :assign, - target: target, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def skip_indent? @@ -1487,16 +1356,6 @@ def pretty_print(q) end end - def to_json(*opts) - { - type: :assoc, - key: key, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def format_contents(q) @@ -1562,15 +1421,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :assoc_splat, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Backref represents a global variable referencing a matched value. It comes @@ -1619,12 +1469,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :backref, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Backtick represents the use of the ` operator. It's usually found being used @@ -1671,12 +1515,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :backtick, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # This module is responsible for formatting the assocs contained within a @@ -1793,15 +1631,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :bare_assoc_hash, - assocs: assocs, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Begin represents a begin..end chain. @@ -1861,15 +1690,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :begin, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # PinnedBegin represents a pinning a nested statement within pattern matching. @@ -1929,15 +1749,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :pinned_begin, - stmt: statement, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Binary represents any expression that involves two sub-expressions with an @@ -2031,17 +1842,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :binary, - left: left, - op: operator, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # This module will remove any breakables from the list of contents so that no @@ -2155,16 +1955,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :block_var, - params: params, - locals: locals, - loc: location, - cmts: comments - }.to_json(*opts) - end end # BlockArg represents declaring a block parameter on a method definition. @@ -2215,12 +2005,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :blockarg, name: name, loc: location, cmts: comments }.to_json( - *opts - ) - end end # bodystmt can't actually determine its bounds appropriately because it @@ -2372,18 +2156,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :bodystmt, - stmts: statements, - rsc: rescue_clause, - els: else_clause, - ens: ensure_clause, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Responsible for formatting either a BraceBlock or a DoBlock. @@ -2609,17 +2381,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :brace_block, - lbrace: lbrace, - block_var: block_var, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Formats either a Break or Next node. @@ -2722,12 +2483,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :break, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Wraps a call operator (which can be a string literal :: or an Op node or a @@ -2864,18 +2619,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :call, - receiver: receiver, - op: operator, - message: message, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Case represents the beginning of a case chain. @@ -2965,16 +2708,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :case, - value: value, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # RAssign represents a single-line pattern match. @@ -3054,17 +2787,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :rassign, - value: value, - op: operator, - pattern: pattern, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Class represents defining a class using the +class+ keyword. @@ -3193,17 +2915,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :class, - constant: constant, - superclass: superclass, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Comma represents the use of the , operator. @@ -3211,13 +2922,32 @@ class Comma < Node # [String] the comma in the string attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_comma(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("comma") + + q.breakable + q.pp(value) + end end end @@ -3290,16 +3020,6 @@ def pretty_print(q) end end - def to_json(*opts) - { - type: :command, - message: message, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def align?(node) @@ -3410,18 +3130,6 @@ def pretty_print(q) end end - def to_json(*opts) - { - type: :command_call, - receiver: receiver, - op: operator, - message: message, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end - private # This is a somewhat naive method that is attempting to sum up the width of @@ -3560,15 +3268,6 @@ def pretty_print(q) q.pp(value) end end - - def to_json(*opts) - { - type: :comment, - value: value.force_encoding("UTF-8"), - inline: inline, - loc: location - }.to_json(*opts) - end end # Const represents a literal value that _looks_ like a constant. This could @@ -3626,12 +3325,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :const, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ConstPathField represents the child node of some kind of assignment. It @@ -3695,16 +3388,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :const_path_field, - parent: parent, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ConstPathRef represents referencing a constant by a path. @@ -3766,16 +3449,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :const_path_ref, - parent: parent, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ConstRef represents the name of the constant being used in a class or module @@ -3825,15 +3498,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :const_ref, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # CVar represents the use of a class variable. @@ -3881,12 +3545,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :cvar, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Def represents defining a regular method on the current self object. @@ -3970,17 +3628,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :def, - name: name, - params: params, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # DefEndless represents defining a single-line method since Ruby 3.0+. @@ -4099,17 +3746,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :def_endless, - name: name, - paren: paren, - stmt: statement, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Defined represents the use of the +defined?+ operator. It can be used with @@ -4164,12 +3800,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :defined, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Defs represents defining a singleton method on an object. @@ -4279,19 +3909,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :defs, - target: target, - op: operator, - name: name, - params: params, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # DoBlock represents passing a block to a method call using the +do+ and +end+ @@ -4360,18 +3977,7 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :do_block, - keyword: keyword, - block_var: block_var, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end - end + end # Responsible for formatting Dot2 and Dot3 nodes. class DotFormatter @@ -4463,16 +4069,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :dot2, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Dot3 represents using the ... operator between two expressions. Usually this @@ -4539,16 +4135,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :dot3, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Responsible for providing information about quotes to be used for strings @@ -4662,16 +4248,6 @@ def pretty_print(q) end end - def to_json(*opts) - { - type: :dyna_symbol, - parts: parts, - quote: quote, - loc: location, - cmts: comments - }.to_json(*opts) - end - private # Here we determine the quotes to use for a dynamic symbol. It's bound by a @@ -4777,12 +4353,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :else, stmts: statements, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Elsif represents another clause in an +if+ or +unless+ chain. @@ -4879,17 +4449,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :elsif, - pred: predicate, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # EmbDoc represents a multi-line comment. @@ -4947,10 +4506,6 @@ def pretty_print(q) q.pp(value) end end - - def to_json(*opts) - { type: :embdoc, value: value, loc: location }.to_json(*opts) - end end # EmbExprBeg represents the beginning token for using interpolation inside of @@ -4963,13 +4518,32 @@ class EmbExprBeg < Node # [String] the #{ used in the string attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_embexpr_beg(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("embexpr_beg") + + q.breakable + q.pp(value) + end end end @@ -4983,13 +4557,32 @@ class EmbExprEnd < Node # [String] the } used in the string attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_embexpr_end(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("embexpr_end") + + q.breakable + q.pp(value) + end end end @@ -5005,13 +4598,32 @@ class EmbVar < Node # [String] the # used in the string attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_embvar(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("embvar") + + q.breakable + q.pp(value) + end end end @@ -5079,16 +4691,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :ensure, - keyword: keyword, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ExcessedComma represents a trailing comma in a list of block parameters. It @@ -5142,15 +4744,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :excessed_comma, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # FCall represents the piece of a method call that comes before any arguments @@ -5216,16 +4809,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :fcall, - value: value, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Field is always the child of an assignment. It represents assigning to a @@ -5298,17 +4881,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :field, - parent: parent, - op: operator, - name: name, - loc: location, - cmts: comments - }.to_json(*opts) - end end # FloatLiteral represents a floating point number literal. @@ -5356,12 +4928,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :float, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # FndPtn represents matching against a pattern where you find a pattern in an @@ -5454,18 +5020,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :fndptn, - constant: constant, - left: left, - values: values, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # For represents using a +for+ loop. @@ -5550,17 +5104,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :for, - index: index, - collection: collection, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # GVar represents a global variable literal. @@ -5608,12 +5151,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :gvar, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # HashLiteral represents a hash literal. @@ -5676,12 +5213,6 @@ def pretty_print(q) end end - def to_json(*opts) - { type: :hash, assocs: assocs, loc: location, cmts: comments }.to_json( - *opts - ) - end - private def format_contents(q) @@ -5790,17 +5321,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :heredoc, - beging: beginning, - ending: ending, - parts: parts, - loc: location, - cmts: comments - }.to_json(*opts) - end end # HeredocBeg represents the beginning declaration of a heredoc. @@ -5851,15 +5371,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :heredoc_beg, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # HshPtn represents matching against a hash pattern using the Ruby 2.7+ @@ -6011,17 +5522,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :hshptn, - constant: constant, - keywords: keywords, - kwrest: keyword_rest, - loc: location, - cmts: comments - }.to_json(*opts) - end end # The list of nodes that represent patterns inside of pattern matching so that @@ -6074,15 +5574,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :ident, - value: value.force_encoding("UTF-8"), - loc: location, - cmts: comments - }.to_json(*opts) - end end # If the predicate of a conditional or loop contains an assignment (in which @@ -6237,17 +5728,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :if, - pred: predicate, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # IfOp represents a ternary clause. @@ -6328,17 +5808,6 @@ def pretty_print(q) end end - def to_json(*opts) - { - type: :ifop, - pred: predicate, - tthy: truthy, - flsy: falsy, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def format_break(q) @@ -6477,16 +5946,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :if_mod, - stmt: statement, - pred: predicate, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Imaginary represents an imaginary number literal. @@ -6534,12 +5993,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :imaginary, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # In represents using the +in+ keyword within the Ruby 2.7+ pattern matching @@ -6629,17 +6082,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :in, - pattern: pattern, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Int represents an integer number literal. @@ -6695,10 +6137,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :int, value: value, loc: location, cmts: comments }.to_json(*opts) - end end # IVar represents an instance variable literal. @@ -6746,12 +6184,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :ivar, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Kw represents the use of a keyword. It can be almost anywhere in the syntax @@ -6808,10 +6240,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :kw, value: value, loc: location, cmts: comments }.to_json(*opts) - end end # KwRestParam represents defining a parameter in a method definition that @@ -6861,15 +6289,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :kwrest_param, - name: name, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Label represents the use of an identifier to associate with an object. You @@ -6927,12 +6346,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :label, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # LabelEnd represents the end of a dynamic symbol. @@ -6946,13 +6359,32 @@ class LabelEnd < Node # [String] the end of the label attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_label_end(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("label_end") + + q.breakable + q.pp(value) + end end end @@ -7044,16 +6476,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :lambda, - params: params, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # LBrace represents the use of a left brace, i.e., {. @@ -7098,12 +6520,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :lbrace, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # LBracket represents the use of a left bracket, i.e., [. @@ -7148,12 +6564,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :lbracket, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # LParen represents the use of a left parenthesis, i.e., (. @@ -7198,12 +6608,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :lparen, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # MAssign is a parent node of any kind of multiple assignment. This includes @@ -7275,16 +6679,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :massign, - target: target, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # MethodAddBlock represents a method call with a block argument. @@ -7340,17 +6734,7 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :method_add_block, - call: call, - block: block, - loc: location, - cmts: comments - }.to_json(*opts) - end - end + end # MLHS represents a list of values being destructured on the left-hand side # of a multiple assignment. @@ -7406,16 +6790,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :mlhs, - parts: parts, - comma: comma, - loc: location, - cmts: comments - }.to_json(*opts) - end end # MLHSParen represents parentheses being used to destruct values in a multiple @@ -7477,15 +6851,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :mlhs_paren, - cnts: contents, - loc: location, - cmts: comments - }.to_json(*opts) - end end # ModuleDeclaration represents defining a module using the +module+ keyword. @@ -7571,16 +6936,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :module, - constant: constant, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # MRHS represents the values that are being assigned on the right-hand side of @@ -7629,12 +6984,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :mrhs, parts: parts, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Next represents using the +next+ keyword. @@ -7695,12 +7044,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :next, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Op represents an operator literal in the source. @@ -7749,10 +7092,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :op, value: value, loc: location, cmts: comments }.to_json(*opts) - end end # OpAssign represents assigning a value to a variable or constant using an @@ -7837,17 +7176,6 @@ def pretty_print(q) end end - def to_json(*opts) - { - type: :opassign, - target: target, - op: operator, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def skip_indent? @@ -8178,21 +7506,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :params, - reqs: requireds, - opts: optionals, - rest: rest, - posts: posts, - keywords: keywords, - kwrest: keyword_rest, - block: block, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Paren represents using balanced parentheses in a couple places in a Ruby @@ -8263,16 +7576,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :paren, - lparen: lparen, - cnts: contents, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Period represents the use of the +.+ operator. It is usually found in method @@ -8318,12 +7621,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :period, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Program represents the overall syntax tree. @@ -8373,16 +7670,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :program, - stmts: statements, - comments: comments, - loc: location, - cmts: comments - }.to_json(*opts) - end end # QSymbols represents a symbol literal array without interpolation. @@ -8454,15 +7741,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :qsymbols, - elems: elements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # QSymbolsBeg represents the beginning of a symbol literal array. @@ -8476,13 +7754,32 @@ class QSymbolsBeg < Node # [String] the beginning of the array literal attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_qsymbols_beg(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("qsymbols_beg") + + q.breakable + q.pp(value) + end end end @@ -8555,12 +7852,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :qwords, elems: elements, loc: location, cmts: comments }.to_json( - *opts - ) - end end # QWordsBeg represents the beginning of a string literal array. @@ -8574,13 +7865,32 @@ class QWordsBeg < Node # [String] the beginning of the array literal attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_qwords_beg(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("qwords_beg") + + q.breakable + q.pp(value) + end end end @@ -8629,12 +7939,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :rational, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # RBrace represents the use of a right brace, i.e., +++. @@ -8642,13 +7946,32 @@ class RBrace < Node # [String] the right brace attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_rbrace(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("rbrace") + + q.breakable + q.pp(value) + end end end @@ -8657,13 +7980,32 @@ class RBracket < Node # [String] the right bracket attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_rbracket(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("rbracket") + + q.breakable + q.pp(value) + end end end @@ -8712,12 +8054,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :redo, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # RegexpContent represents the body of a regular expression. @@ -8734,15 +8070,34 @@ class RegexpContent < Node # regular expression attr_reader :parts - def accept(visitor) - visitor.visit_regexp_content(self) - end - def initialize(beginning:, parts:, location:) @beginning = beginning @parts = parts @location = location end + + def accept(visitor) + visitor.visit_regexp_content(self) + end + + def child_nodes + parts + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { beginning: beginning, parts: parts, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("regexp_content") + + q.breakable + q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } + end + end end # RegexpBeg represents the start of a regular expression literal. @@ -8758,13 +8113,32 @@ class RegexpBeg < Node # [String] the beginning of the regular expression attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_regexp_beg(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("regexp_beg") + + q.breakable + q.pp(value) + end end end @@ -8782,13 +8156,32 @@ class RegexpEnd < Node # [String] the end of the regular expression attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_regexp_end(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("regexp_end") + + q.breakable + q.pp(value) + end end end @@ -8889,17 +8282,6 @@ def pretty_print(q) end end - def to_json(*opts) - { - type: :regexp_literal, - beging: beginning, - ending: ending, - parts: parts, - loc: location, - cmts: comments - }.to_json(*opts) - end - private def include?(pattern) @@ -8991,16 +8373,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :rescue_ex, - extns: exceptions, - var: variable, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Rescue represents the use of the rescue keyword inside of a BodyStmt node. @@ -9123,17 +8495,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :rescue, - extn: exception, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # RescueMod represents the use of the modifier form of a +rescue+ clause. @@ -9205,16 +8566,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :rescue_mod, - stmt: statement, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # RestParam represents defining a parameter in a method definition that @@ -9264,12 +8615,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :rest_param, name: name, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Retry represents the use of the +retry+ keyword. @@ -9317,12 +8662,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :retry, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Return represents using the +return+ keyword with arguments. @@ -9370,12 +8709,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :return, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Return0 represents the bare +return+ keyword with no arguments. @@ -9423,12 +8756,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :return0, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # RParen represents the use of a right parenthesis, i.e., +)+. @@ -9436,13 +8763,32 @@ class RParen < Node # [String] the parenthesis attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_rparen(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("rparen") + + q.breakable + q.pp(value) + end end end @@ -9513,16 +8859,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :sclass, - target: target, - bodystmt: bodystmt, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Everything that has a block of code inside of it has a list of statements. @@ -9666,12 +9002,6 @@ def pretty_print(q) end end - def to_json(*opts) - { type: :statements, body: body, loc: location, cmts: comments }.to_json( - *opts - ) - end - private # As efficiently as possible, gather up all of the comments that have been @@ -9716,13 +9046,32 @@ class StringContent < Node # string attr_reader :parts + def initialize(parts:, location:) + @parts = parts + @location = location + end + def accept(visitor) visitor.visit_string_content(self) end - def initialize(parts:, location:) - @parts = parts - @location = location + def child_nodes + parts + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { parts: parts, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("string_content") + + q.breakable + q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } + end end end @@ -9787,16 +9136,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :string_concat, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # StringDVar represents shorthand interpolation of a variable into a string. @@ -9848,15 +9187,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :string_dvar, - var: variable, - loc: location, - cmts: comments - }.to_json(*opts) - end end # StringEmbExpr represents interpolated content. It can be contained within a @@ -9923,15 +9253,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :string_embexpr, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # StringLiteral represents a string literal. @@ -10010,16 +9331,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :string_literal, - parts: parts, - quote: quote, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Super represents using the +super+ keyword with arguments. It can optionally @@ -10077,12 +9388,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :super, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # SymBeg represents the beginning of a symbol literal. @@ -10105,14 +9410,33 @@ class SymBeg < Node # [String] the beginning of the symbol attr_reader :value - def accept(visitor) - visitor.visit_sym_beg(self) - end - def initialize(value:, location:) @value = value @location = location end + + def accept(visitor) + visitor.visit_symbeg(self) + end + + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("symbeg") + + q.breakable + q.pp(value) + end + end end # SymbolContent represents symbol contents and is always the child of a @@ -10125,13 +9449,32 @@ class SymbolContent < Node # symbol attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_symbol_content(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("symbol_content") + + q.breakable + q.pp(value) + end end end @@ -10183,15 +9526,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :symbol_literal, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Symbols represents a symbol array literal with interpolation. @@ -10263,15 +9597,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :symbols, - elems: elements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # SymbolsBeg represents the start of a symbol array literal with @@ -10286,13 +9611,32 @@ class SymbolsBeg < Node # [String] the beginning of the symbol literal array attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_symbols_beg(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("symbols_beg") + + q.breakable + q.pp(value) + end end end @@ -10305,13 +9649,32 @@ class TLambda < Node # [String] the beginning of the lambda literal attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_tlambda(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("tlambda") + + q.breakable + q.pp(value) + end end end @@ -10325,13 +9688,32 @@ class TLamBeg < Node # [String] the beginning of the body of the lambda literal attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_tlambeg(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("tlambeg") + + q.breakable + q.pp(value) + end end end @@ -10383,15 +9765,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :top_const_field, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # TopConstRef is very similar to TopConstField except that it is not involved @@ -10441,15 +9814,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :top_const_ref, - constant: constant, - loc: location, - cmts: comments - }.to_json(*opts) - end end # TStringBeg represents the beginning of a string literal. @@ -10466,13 +9830,32 @@ class TStringBeg < Node # [String] the beginning of the string attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_tstring_beg(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("tstring_beg") + + q.breakable + q.pp(value) + end end end @@ -10529,15 +9912,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :tstring_content, - value: value.force_encoding("UTF-8"), - loc: location, - cmts: comments - }.to_json(*opts) - end end # TStringEnd represents the end of a string literal. @@ -10554,13 +9928,32 @@ class TStringEnd < Node # [String] the end of the string attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_tstring_end(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("tstring_end") + + q.breakable + q.pp(value) + end end end @@ -10620,16 +10013,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :not, - value: statement, - paren: parentheses, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Unary represents a unary method being called on an expression, as in +!+ or @@ -10691,16 +10074,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :unary, - op: operator, - value: statement, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Undef represents the use of the +undef+ keyword. @@ -10777,12 +10150,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :undef, syms: symbols, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Unless represents the first clause in an +unless+ chain. @@ -10859,17 +10226,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :unless, - pred: predicate, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # UnlessMod represents the modifier form of an +unless+ statement. @@ -10929,16 +10285,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :unless_mod, - stmt: statement, - pred: predicate, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Formats an Until, UntilMod, While, or WhileMod node. @@ -11059,16 +10405,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :until, - pred: predicate, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # UntilMod represents the modifier form of a +until+ loop. @@ -11148,16 +10484,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :until_mod, - stmt: statement, - pred: predicate, - loc: location, - cmts: comments - }.to_json(*opts) - end end # VarAlias represents when you're using the +alias+ keyword with global @@ -11218,16 +10544,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :var_alias, - left: left, - right: right, - loc: location, - cmts: comments - }.to_json(*opts) - end end # VarField represents a variable that is being assigned a value. As such, it @@ -11277,12 +10593,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :var_field, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # VarRef represents a variable reference. @@ -11334,12 +10644,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :var_ref, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # PinnedVarRef represents a pinned variable reference within a pattern @@ -11395,15 +10699,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :pinned_var_ref, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end end # VCall represent any plain named object with Ruby that could be either a @@ -11452,12 +10747,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :vcall, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # VoidStmt represents an empty lexical block of code. @@ -11499,10 +10788,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :void_stmt, loc: location, cmts: comments }.to_json(*opts) - end end # When represents a +when+ clause in a +case+ chain. @@ -11614,17 +10899,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :when, - args: arguments, - stmts: statements, - cons: consequent, - loc: location, - cmts: comments - }.to_json(*opts) - end end # While represents a +while+ loop. @@ -11696,16 +10970,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :while, - pred: predicate, - stmts: statements, - loc: location, - cmts: comments - }.to_json(*opts) - end end # WhileMod represents the modifier form of a +while+ loop. @@ -11785,16 +11049,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :while_mod, - stmt: statement, - pred: predicate, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Word represents an element within a special array literal that accepts @@ -11850,12 +11104,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :word, parts: parts, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Words represents a string literal array with interpolation. @@ -11927,12 +11175,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :words, elems: elements, loc: location, cmts: comments }.to_json( - *opts - ) - end end # WordsBeg represents the beginning of a string literal array with @@ -11947,13 +11189,32 @@ class WordsBeg < Node # [String] the start of the word literal array attr_reader :value + def initialize(value:, location:) + @value = value + @location = location + end + def accept(visitor) visitor.visit_words_beg(self) end - def initialize(value:, location:) - @value = value - @location = location + def child_nodes + [] + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { value: value, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("words_beg") + + q.breakable + q.pp(value) + end end end @@ -11966,13 +11227,32 @@ class XString < Node # xstring attr_reader :parts + def initialize(parts:, location:) + @parts = parts + @location = location + end + def accept(visitor) visitor.visit_xstring(self) end - def initialize(parts:, location:) - @parts = parts - @location = location + def child_nodes + parts + end + + alias deconstruct child_nodes + + def deconstruct_keys(keys) + { parts: parts, location: location } + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("xstring") + + q.breakable + q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } + end end end @@ -12024,15 +11304,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { - type: :xstring_literal, - parts: parts, - loc: location, - cmts: comments - }.to_json(*opts) - end end # Yield represents using the +yield+ keyword with arguments. @@ -12094,12 +11365,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :yield, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) - end end # Yield0 represents the bare +yield+ keyword with no arguments. @@ -12147,12 +11412,6 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :yield0, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end # ZSuper represents the bare +super+ keyword with no arguments. @@ -12200,11 +11459,5 @@ def pretty_print(q) q.pp(Comment::List.new(comments)) end end - - def to_json(*opts) - { type: :zsuper, value: value, loc: location, cmts: comments }.to_json( - *opts - ) - end end end diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index 27561cdf..c14dbffa 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -58,7 +58,7 @@ def visit(node) end def visit_all(nodes) - nodes.each { |node| visit(node) } + nodes.map { |node| visit(node) } end def visit_child_nodes(node) @@ -444,7 +444,7 @@ def visit_child_nodes(node) alias visit_super visit_child_nodes # Visit a SymBeg node. - alias visit_sym_beg visit_child_nodes + alias visit_symbeg visit_child_nodes # Visit a SymbolContent node. alias visit_symbol_content visit_child_nodes diff --git a/test/formatting_test.rb b/test/formatting_test.rb index 4b649052..25059658 100644 --- a/test/formatting_test.rb +++ b/test/formatting_test.rb @@ -4,42 +4,9 @@ module SyntaxTree class FormattingTest < Minitest::Test - FIXTURES_3_0_0 = %w[ - command_def_endless - def_endless - fndptn - rassign - rassign_rocket - ] - - FIXTURES_3_1_0 = %w[ - pinned_begin - var_field_rassign - ] - - fixtures = Dir[File.join(__dir__, "fixtures", "*.rb")].map { |filepath| File.basename(filepath, ".rb") } - fixtures -= FIXTURES_3_1_0 if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.1.0") - fixtures -= FIXTURES_3_0_0 if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0.0") - - delimiter = /%(?: # (.+?))?\n/ - fixtures.each do |fixture| - filepath = File.join(__dir__, "fixtures", "#{fixture}.rb") - - File.readlines(filepath).slice_before(delimiter).each_with_index do |source, index| - comment = source.shift.match(delimiter)[1] - original, expected = source.join.split("-\n") - - # If there's a comment starting with >= that starts after the % that - # delineates the test, then we're going to check if the version - # satisfies that constraint. - if comment&.start_with?(">=") - version = Gem::Version.new(comment.split[1]) - next if Gem::Version.new(RUBY_VERSION) < version - end - - define_method(:"test_formatting_#{fixture}_#{index}") do - assert_equal(expected || original, SyntaxTree.format(original)) - end + Fixtures.each_fixture do |fixture| + define_method(:"test_formatted_#{fixture.name}") do + assert_equal(fixture.formatted, SyntaxTree.format(fixture.source)) end end end diff --git a/test/json_visitor_test.rb b/test/json_visitor_test.rb new file mode 100644 index 00000000..917aca71 --- /dev/null +++ b/test/json_visitor_test.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SyntaxTree + class JSONVisitorTest < Minitest::Test + Fixtures.each_fixture do |fixture| + define_method(:"test_json_#{fixture.name}") do + refute_includes(SyntaxTree.format(fixture.source).to_json, "#<") + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 8512253c..09c4dd0a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -9,3 +9,61 @@ require "json" require "pp" require "minitest/autorun" + +# There are a bunch of fixtures defined in test/fixtures. They exercise every +# possible combination of syntax that leads to variations in the types of nodes. +# They are used for testing various parts of Syntax Tree, including formatting, +# serialization, and parsing. This module provides a single each_fixture method +# that can be used to drive tests on each fixture. +module Fixtures + FIXTURES_3_0_0 = %w[ + command_def_endless + def_endless + fndptn + rassign + rassign_rocket + ] + + FIXTURES_3_1_0 = %w[ + pinned_begin + var_field_rassign + ] + + Fixture = Struct.new(:name, :source, :formatted, keyword_init: true) + + def self.each_fixture + ruby_version = Gem::Version.new(RUBY_VERSION) + + # First, get a list of the basenames of all of the fixture files. + fixtures = + Dir[File.expand_path("fixtures/*.rb", __dir__)].map do |filepath| + File.basename(filepath, ".rb") + end + + # Next, subtract out any fixtures that aren't supported by the current Ruby + # version. + fixtures -= FIXTURES_3_1_0 if ruby_version < Gem::Version.new("3.1.0") + fixtures -= FIXTURES_3_0_0 if ruby_version < Gem::Version.new("3.0.0") + + delimiter = /%(?: # (.+?))?\n/ + fixtures.each do |fixture| + filepath = File.expand_path("fixtures/#{fixture}.rb", __dir__) + + # For each fixture in the fixture file yield a Fixture object. + File.readlines(filepath).slice_before(delimiter).each_with_index do |source, index| + comment = source.shift.match(delimiter)[1] + source, formatted = source.join.split("-\n") + + # If there's a comment starting with >= that starts after the % that + # delineates the test, then we're going to check if the version + # satisfies that constraint. + if comment&.start_with?(">=") + next if ruby_version < Gem::Version.new(comment.split[1]) + end + + name = :"#{fixture}_#{index}" + yield Fixture.new(name: name, source: source, formatted: formatted || source) + end + end + end +end From a4e54edeb8b486524490561198ec02664b29dc79 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 11 Apr 2022 21:50:32 -0400 Subject: [PATCH 15/23] Alphabetize --- lib/syntax_tree/json_visitor.rb | 28 +++++------ lib/syntax_tree/visitor.rb | 84 ++++++++++++++++----------------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/lib/syntax_tree/json_visitor.rb b/lib/syntax_tree/json_visitor.rb index a557eab2..89d745cd 100644 --- a/lib/syntax_tree/json_visitor.rb +++ b/lib/syntax_tree/json_visitor.rb @@ -122,16 +122,6 @@ def visit_assoc_splat(node) } end - def visit_BEGIN(node) - { - type: :BEGIN, - lbrace: visit(node.lbrace), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - def visit_backref(node) visit_token(:backref, node) end @@ -149,6 +139,16 @@ def visit_bare_assoc_hash(node) } end + def visit_BEGIN(node) + { + type: :BEGIN, + lbrace: visit(node.lbrace), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + def visit_begin(node) { type: :begin, @@ -471,10 +471,6 @@ def visit_embvar(node) } end - def visit___end__(node) - visit_token(:__end__, node) - end - def visit_ensure(node) { type: :ensure, @@ -1306,6 +1302,10 @@ def visit_zsuper(node) visit_token(:zsuper, node) end + def visit___end__(node) + visit_token(:__end__, node) + end + # Explicitly undefine the default aliased method for each of the visitor # methods so that if there is one that isn't implemented it will raise an # error. diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index c14dbffa..0ea66fb6 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -104,9 +104,6 @@ def visit_child_nodes(node) # Visit an AssocSplat node. alias visit_assoc_splat visit_child_nodes - # Visit a BEGINBlock node. - alias visit_BEGIN visit_child_nodes - # Visit a Backref node. alias visit_backref visit_child_nodes @@ -116,6 +113,9 @@ def visit_child_nodes(node) # Visit a BareAssocHash node. alias visit_bare_assoc_hash visit_child_nodes + # Visit a BEGINBlock node. + alias visit_BEGIN visit_child_nodes + # Visit a Begin node. alias visit_begin visit_child_nodes @@ -137,18 +137,15 @@ def visit_child_nodes(node) # Visit a Break node. alias visit_break visit_child_nodes - # Visit a CHAR node. - alias visit_CHAR visit_child_nodes - - # Visit a CVar node. - alias visit_cvar visit_child_nodes - # Visit a Call node. alias visit_call visit_child_nodes # Visit a Case node. alias visit_case visit_child_nodes + # Visit a CHAR node. + alias visit_CHAR visit_child_nodes + # Visit a ClassDeclaration node. alias visit_class visit_child_nodes @@ -176,6 +173,9 @@ def visit_child_nodes(node) # Visit a ConstRef node. alias visit_const_ref visit_child_nodes + # Visit a CVar node. + alias visit_cvar visit_child_nodes + # Visit a Def node. alias visit_def visit_child_nodes @@ -221,9 +221,6 @@ def visit_child_nodes(node) # Visit an EmbVar node. alias visit_embvar visit_child_nodes - # Visit an EndContent node. - alias visit___end__ visit_child_nodes - # Visit an Ensure node. alias visit_ensure visit_child_nodes @@ -260,9 +257,6 @@ def visit_child_nodes(node) # Visit a HshPtn node. alias visit_hshptn visit_child_nodes - # Visit an IVar node. - alias visit_ivar visit_child_nodes - # Visit an Ident node. alias visit_ident visit_child_nodes @@ -284,21 +278,15 @@ def visit_child_nodes(node) # Visit an Int node. alias visit_int visit_child_nodes + # Visit an IVar node. + alias visit_ivar visit_child_nodes + # Visit a Kw node. alias visit_kw visit_child_nodes # Visit a KwRestParam node. alias visit_kwrest_param visit_child_nodes - # Visit a LBrace node. - alias visit_lbrace visit_child_nodes - - # Visit a LBracket node. - alias visit_lbracket visit_child_nodes - - # Visit a LParen node. - alias visit_lparen visit_child_nodes - # Visit a Label node. alias visit_label visit_child_nodes @@ -308,24 +296,33 @@ def visit_child_nodes(node) # Visit a Lambda node. alias visit_lambda visit_child_nodes + # Visit a LBrace node. + alias visit_lbrace visit_child_nodes + + # Visit a LBracket node. + alias visit_lbracket visit_child_nodes + + # Visit a LParen node. + alias visit_lparen visit_child_nodes + # Visit a MAssign node. alias visit_massign visit_child_nodes + # Visit a MethodAddBlock node. + alias visit_method_add_block visit_child_nodes + # Visit a MLHS node. alias visit_mlhs visit_child_nodes # Visit a MLHSParen node. alias visit_mlhs_paren visit_child_nodes - # Visit a MRHS node. - alias visit_mrhs visit_child_nodes - - # Visit a MethodAddBlock node. - alias visit_method_add_block visit_child_nodes - # Visit a ModuleDeclaration node. alias visit_module visit_child_nodes + # Visit a MRHS node. + alias visit_mrhs visit_child_nodes + # Visit a Next node. alias visit_next visit_child_nodes @@ -371,18 +368,15 @@ def visit_child_nodes(node) # Visit a RAssign node. alias visit_rassign visit_child_nodes + # Visit a RationalLiteral node. + alias visit_rational visit_child_nodes + # Visit a RBrace node. alias visit_rbrace visit_child_nodes # Visit a RBracket node. alias visit_rbracket visit_child_nodes - # Visit a RParen node. - alias visit_rparen visit_child_nodes - - # Visit a RationalLiteral node. - alias visit_rational visit_child_nodes - # Visit a Redo node. alias visit_redo visit_child_nodes @@ -419,6 +413,9 @@ def visit_child_nodes(node) # Visit a Return0 node. alias visit_return0 visit_child_nodes + # Visit a RParen node. + alias visit_rparen visit_child_nodes + # Visit a SClass node. alias visit_sclass visit_child_nodes @@ -458,12 +455,12 @@ def visit_child_nodes(node) # Visit a SymbolsBeg node. alias visit_symbols_beg visit_child_nodes - # Visit a TLamBeg node. - alias visit_tlambeg visit_child_nodes - # Visit a TLambda node. alias visit_tlambda visit_child_nodes + # Visit a TLamBeg node. + alias visit_tlambeg visit_child_nodes + # Visit a TStringBeg node. alias visit_tstring_beg visit_child_nodes @@ -497,9 +494,6 @@ def visit_child_nodes(node) # Visit an UntilMod node. alias visit_until_mod visit_child_nodes - # Visit a VCall node. - alias visit_vcall visit_child_nodes - # Visit a VarAlias node. alias visit_var_alias visit_child_nodes @@ -509,6 +503,9 @@ def visit_child_nodes(node) # Visit a VarRef node. alias visit_var_ref visit_child_nodes + # Visit a VCall node. + alias visit_vcall visit_child_nodes + # Visit a VoidStmt node. alias visit_void_stmt visit_child_nodes @@ -544,5 +541,8 @@ def visit_child_nodes(node) # Visit a ZSuper node. alias visit_zsuper visit_child_nodes + + # Visit an EndContent node. + alias visit___end__ visit_child_nodes end end From 35a5b1a956dc4b3ef48a7e2fe27d163ff4161542 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 11 Apr 2022 21:55:34 -0400 Subject: [PATCH 16/23] Remove missing hash value from inlay hints --- lib/syntax_tree/language_server/inlay_hints.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/syntax_tree/language_server/inlay_hints.rb b/lib/syntax_tree/language_server/inlay_hints.rb index 5e43439c..0bed2a80 100644 --- a/lib/syntax_tree/language_server/inlay_hints.rb +++ b/lib/syntax_tree/language_server/inlay_hints.rb @@ -27,20 +27,6 @@ def bare_rescue(location) after[location.start_char + "rescue".length] << " StandardError" end - # Adds the implicitly referenced value (local variable or method call) - # that is added into a hash when the value of a key-value pair is omitted. - # For example, - # - # { value: } - # - # becomes - # - # { value: value } - # - def missing_hash_value(key, location) - after[location.end_char] << " #{key}" - end - # Adds implicit parentheses around certain expressions to make it clear # which subexpression will be evaluated first. For example, # @@ -69,8 +55,6 @@ def self.find(program) case [parent_node, child_node] in _, Rescue[exception: nil, location:] inlay_hints.bare_rescue(location) - in _, Assoc[key: Label[value: key], value: nil, location:] - inlay_hints.missing_hash_value(key[0...-1], location) in Assign | Binary | IfOp | OpAssign, IfOp[location:] inlay_hints.precedence_parentheses(location) in Assign | OpAssign, Binary[location:] From baa5144074fa4124b4f2a62bebedaa78ff70f422 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 17:44:54 +0000 Subject: [PATCH 17/23] Bump parser from 3.1.1.0 to 3.1.2.0 Bumps [parser](https://github.com/whitequark/parser) from 3.1.1.0 to 3.1.2.0. - [Release notes](https://github.com/whitequark/parser/releases) - [Changelog](https://github.com/whitequark/parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/whitequark/parser/compare/v3.1.1.0...v3.1.2.0) --- updated-dependencies: - dependency-name: parser dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index b489b9ec..989aeb21 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,7 +10,7 @@ GEM benchmark-ips (2.10.0) docile (1.4.0) minitest (5.15.0) - parser (3.1.1.0) + parser (3.1.2.0) ast (~> 2.4.1) rake (13.0.6) ruby_parser (3.19.1) From ce0cc1eaefb6b045690a0834ae9498c5ec36a379 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 12 Apr 2022 14:11:54 -0400 Subject: [PATCH 18/23] Documentation --- README.md | 291 +++++++++++++++++++++++++++++++++++++---- bin/setup | 6 - lib/syntax_tree/cli.rb | 80 ++++++----- 3 files changed, 311 insertions(+), 66 deletions(-) delete mode 100755 bin/setup diff --git a/README.md b/README.md index 470826d2..0dc90057 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,50 @@ [![Build Status](https://github.com/ruby-syntax-tree/syntax_tree/actions/workflows/main.yml/badge.svg)](https://github.com/ruby-syntax-tree/syntax_tree/actions/workflows/main.yml) [![Gem Version](https://img.shields.io/gem/v/syntax_tree.svg)](https://rubygems.org/gems/syntax_tree) -A fast Ruby parser and formatter with only standard library dependencies. +Syntax Tree is a suite of tools built on top of the internal CRuby parser. It provides the ability to generate a syntax tree from source, as well as the tools necessary to inspect and manipulate that syntax tree. It can be used to build formatters, linters, language servers, and more. + +It is built with only standard library dependencies. It additionally ships with a plugin system so that you can build your own syntax trees from other languages and incorporate these tools. + +- [Installation](#installation) +- [CLI](#cli) + - [ast](#ast) + - [check](#check) + - [format](#format) + - [write](#write) +- [Library](#library) + - [SyntaxTree.read(filepath)](#syntaxtreereadfilepath) + - [SyntaxTree.parse(source)](#syntaxtreeparsesource) + - [SyntaxTree.format(source)](#syntaxtreeformatsource) +- [Nodes](#nodes) + - [child_nodes](#child_nodes) + - [Pattern matching](#pattern-matching) + - [pretty_print(q)](#pretty_printq) + - [to_json(*opts)](#to_jsonopts) + - [format(q)](#formatq) +- [Visitor](#visitor) + - [visit_method](#visit_method) +- [Language server](#language-server) + - [textDocument/formatting](#textdocumentformatting) + - [textDocument/inlayHints](#textdocumentinlayhints) + - [syntaxTree/visualizing](#syntaxtreevisualizing) +- [Contributing](#contributing) +- [License](#license) ## Installation -Add this line to your application's Gemfile: +Syntax Tree is both a command-line interface and a library. If you're only looking to use the command-line interface, then we recommend installing the gem globally, as in: + +```sh +gem install syntax_tree +``` + +To run the CLI with the gem installed globally, you would run: + +```sh +stree version +``` + +If you're planning on using Syntax Tree as a library within your own project, we recommend installing it as part of your gem bundle. First, add this line to your application's Gemfile: ```ruby gem "syntax_tree" @@ -19,52 +58,252 @@ gem "syntax_tree" And then execute: - $ bundle install +```sh +bundle install +``` -Or install it yourself as: +To run the CLI with the gem installed in your gem bundle, you would run: - $ gem install syntax_tree +```sh +bundle exec stree version +``` -## Usage +## CLI -From code: +Syntax Tree ships with the `stree` CLI, which can be used to inspect and manipulate Ruby code. Below are listed all of the commands built into the CLI that you can use. Note that for all commands that operate on files, you can also pass in content through STDIN. -```ruby -require "syntax_tree" +### ast -pp SyntaxTree.parse(source) # print out the AST -puts SyntaxTree.format(source) # format the AST +This command will print out a textual representation of the syntax tree associated with each of the files it finds. To execute, run: + +```sh +stree ast path/to/file.rb ``` -From the CLI: +For a file that contains `1 + 1`, you will receive: + +``` +(program (statements (binary (int "1") + (int "1")))) +``` + +### check + +This command is meant to be used in the context of a continuous integration or git hook. It checks each file given to make sure that it matches the expected format. It can be used to ensure unformatted content never makes it into a codebase. ```sh -$ stree ast program.rb -(program - (statements - ... +stree check path/to/file.rb +``` + +For a file that matches the expected format, you will receive: + +``` +All files matched expected format. +``` + +If there are files with unformatted code, you will receive: + ``` +[warn] path/to/file.rb +The listed files did not match the expected format. +``` + +### format -or +This command will output the formatted version of each of the listed files. Importantly, it will not write that content back to the source files. It is meant to display the formatted version only. ```sh -$ stree format program.rb -class MyClass - ... +stree format path/to/file.rb +``` + +For a file that contains `1 + 1`, you will receive: + +```ruby +1 + 1 ``` -or +### write + +This command will format the listed files and write that formatted version back to the source files. Note that this overwrites the original content, to be sure to be using a version control system. ```sh -$ stree write program.rb -program.rb 1ms +stree write path/to/file.rb ``` -## Development +This will list every file that is being formatted. It will output light gray if the file already matches the expected format. It will output in regular color if it does not. + +``` +path/to/file.rb 0ms +``` + +## Library + +Syntax Tree can be used as a library to access the syntax tree underlying Ruby source code. + +### SyntaxTree.read(filepath) + +This function takes a filepath and returns a string associated with the content of that file. It is similar in functionality to `File.read`, except htat it takes into account Ruby-level file encoding (through magic comments at the top of the file). + +### SyntaxTree.parse(source) + +This function takes an input string containing Ruby code and returns the syntax tree associated with it. The top-level node is always a `SyntaxTree::Program`, which contains a list of top-level expression nodes. + +### SyntaxTree.format(source) + +This function takes an input string containing Ruby code, parses it into its underlying syntax tree, and formats it back out to a string. + +## Nodes + +There are many different node types in the syntax tree. They are meant to be treated as immutable structs containing links to child nodes with minimal logic contained within their implementation. However, for the most part they all respond to a certain set of APIs, listed below. + +### child_nodes + +One of the easiest ways to descend the tree is to use the `child_nodes` function. It is implemented on every node type (leaf nodes return an empty array). If the goal is to simply walk through the tree, this is the easiest way to go. + +```ruby +program = SyntaxTree.parse("1 + 1") +program.child_nodes.first.child_nodes.first +# => (binary (int "1") :+ (int "1")) +``` + +### Pattern matching + +Pattern matching is another way to descend the tree which is more specific than using `child_nodes`. Using Ruby's built-in pattern matching, you can extract the same information but be as specific about your constraints as you like. For example, with minimal constraints: + +```ruby +program = SyntaxTree.parse("1 + 1") +program => { statements: { body: [binary] } } +binary +# => (binary (int "1") :+ (int "1")) +``` + +Or, with more constraints on the types to ensure we're getting exactly what we expect: + +```ruby +program = SyntaxTree.parse("1 + 1") +program => SyntaxTree::Program[statements: SyntaxTree::Statements[body: [SyntaxTree::Binary => binary]]] +binary +# => (binary (int "1") :+ (int "1")) +``` + +### pretty_print(q) + +Every node responds to the `pretty_print` Ruby interface, which makes it usable by the `pp` library. You _can_ use this API manually, but it's mostly there for compatibility and not meant to be directly invoked. For example: + +```ruby +pp SyntaxTree.parse("1 + 1") +# (program (statements (binary (int "1") + (int "1")))) +``` + +### to_json(*opts) + +Every node responds to the `to_json` Ruby interface, which makes it usable by the `json` library. Much like `pretty_print`, you could use this API manually, but it's mostly used by `JSON` to dump the nodes to a serialized format. For example: + +```ruby +program = SyntaxTree.parse("1 + 1") +program => { statements: { body: [{ left: }] } } +puts JSON.dump(left) +# {"type":"int","value":"1","loc":[1,0,1,1],"cmts":[]} +``` + +### format(q) + +Every node responds to `format`, which formats the content nicely. The API mirrors that used by the `pretty_print` gem in that it accepts a formatter object and calls methods on it to generate its own internal representation of the text that will be outputted. Because of this, it's easier to not use this API directly and instead to call `SyntaxTree.format`. You _can_ however use this directly if you create the formatter yourself, as in: + +```ruby +source = "1+1" +program = SyntaxTree.parse(source) +program => { statements: { body: [binary] } } + +formatter = SyntaxTree::Formatter.new(source, []) +binary.format(formatter) + +formatter.flush +formatter.output.join +# => "1 + 1" +``` + +## Visitor + +If you want to operate over a set of nodes in the tree but don't want to walk the tree manually, the `Visitor` class makes it easy. `SyntaxTree::Visitor` is an implementation of the double dispatch visitor pattern. It works by the user defining visit methods that process nodes in the tree, which then call back to other visit methods to continue the descent. This is easier shown in code. + +Let's say, for instance, that you wanted to find every place in source where you have an arithmetic problem between two integers (this is pretty contrived, but it's just for illustration). You could define a visitor that only explicitly visits the `SyntaxTree::Binary` node, as in: + +```ruby +class ArithmeticVisitor < SyntaxTree::Visitor + def visit_binary(node) + if node in { left: SyntaxTree::Int, operator: :+ | :- | :* | :/, right: SyntaxTree::Int } + puts "The result is: #{node.left.value.to_i.public_send(node.operator, node.right.value.to_i)}" + end + end +end + +visitor = ArithmeticVisitor.new +visitor.visit(SyntaxTree.parse("1 + 1")) +# The result is: 2 +``` + +With visitors, you only define handlers for the nodes that you need. You can find the names of the methods that you will need to define within the base visitor, as they're all aliased to the default behavior (visiting the child nodes). Note that when you define a handler for a node, you have to tell Syntax Tree how to walk further. In the example above, we don't need to go any further because we already know the child nodes are `SyntaxTree::Int`, so they can't possibly contain more `SyntaxTree::Binary` nodes. In other circumstances you may not know though, so you can either: + +* call `super` (which will do the default and visit all child nodes) +* call `visit_child_nodes` manually +* call `visit(child)` with each child that you want to visit +* call nothing if you're sure you don't want to descend further + +### visit_method + +When you're creating a visitor, it's very easy to accidentally mistype a visit method. Unfortunately, there's no way to tell Ruby to explicitly override a parent method, so it would then be easy to define a method that never gets called. To mitigate this risk, there's `Visitor.visit_method(name)`. This method accepts a symbol that is checked against the list of known visit methods. If it's not in the list, then an error will be raised. It's meant to be used like: + +```ruby +class ArithmeticVisitor < SyntaxTree::Visitor + visit_method def visit_binary(node) + # ... + end +end +``` + +This will only be checked once when the file is first required. If there is a typo in your method name (or the method no longer exists for whatever reason), you will receive an error like so: + +``` +~/syntax_tree/lib/syntax_tree/visitor.rb:46:in `visit_method': Invalid visit method: visit_binar (SyntaxTree::Visitor::VisitMethodError) +Did you mean? visit_binary + visit_in + visit_ivar + from (irb):2:in `' + from (irb):1:in `
' + from bin/console:8:in `
' +``` + +## Language server + +Syntax Tree additionally ships with a language server conforming to the [language server protocol](https://microsoft.github.io/language-server-protocol/). It can be invoked through the CLI by running: + +```sh +stree lsp +``` + +By default, the language server is relatively minimal, mostly meant to provide a registered formatter for the Ruby language. However there are a couple of additional niceties baked in. There are related projects that configure and use this language server within IDEs. For example, to use this code with VSCode, see [ruby-syntax-tree/vscode-syntax-tree](https://github.com/ruby-syntax-tree/vscode-syntax-tree). + +### textDocument/formatting + +As mentioned above, the language server responds to formatting requests with the formatted document. It typically responds on the order of tens of milliseconds, so it should be fast enough for any IDE. + +### textDocument/inlayHints + +The language server also responds to the relatively new inlay hints request. This request allows the language server to define additional information that should exist in the source code as helpful hints to the developer. In our case we use it to display things like implicit parentheses. For example, if you had the following code: + +```ruby +1 + 2 * 3 +``` + +Implicity, the `2 * 3` is going to be executed first because the `*` operator has higher precedence than the `+` operator. However, to ease mental overhead, our language server includes small parentheses to make this explicit, as in: + +```ruby +1 + ₍2 * 3₎ +``` -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +### syntaxTree/visualizing -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). +The language server additionally includes this custom request to return a textual representation of the syntax tree underlying the source code of a file. Language server clients can use this to (for example) open an additional tab with this information displayed. ## Contributing diff --git a/bin/setup b/bin/setup deleted file mode 100755 index cf4ad25e..00000000 --- a/bin/setup +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' -set -vx - -bundle install diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index d6259f3d..1bf09cf7 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -124,7 +124,7 @@ def run(handler, filepath, source) start = Time.now formatted = handler.format(source) - File.write(filepath, formatted) + File.write(filepath, formatted) if filepath != :stdin color = source == formatted ? Color.gray(filepath) : filepath delta = ((Time.now - start) * 1000).round @@ -191,11 +191,6 @@ def run(argv) return 0 end - if arguments.empty? - warn(HELP) - return 1 - end - action = case name when "a", "ast" @@ -215,6 +210,13 @@ def run(argv) return 1 end + # If we're not reading from stdin and the user didn't supply and + # filepaths to be read, then we exit with the usage message. + if STDIN.tty? && arguments.empty? + warn(HELP) + return 1 + end + # If there are any plugins specified on the command line, then load them # by requiring them here. We do this by transforming something like # @@ -224,40 +226,34 @@ def run(argv) # # require "syntax_tree/haml" # - if arguments.first.start_with?("--plugins=") + if arguments.first&.start_with?("--plugins=") plugins = arguments.shift[/^--plugins=(.*)$/, 1] plugins.split(",").each { |plugin| require "syntax_tree/#{plugin}" } end + # Track whether or not there are any errors from any of the files that + # we take action on so that we can properly clean up and exit. errored = false - arguments.each do |pattern| - Dir.glob(pattern).each do |filepath| - next unless File.file?(filepath) - - handler = HANDLERS[File.extname(filepath)] - source = handler.read(filepath) - - begin - action.run(handler, filepath, source) - rescue Parser::ParseError => error - warn("Error: #{error.message}") - - if error.lineno - highlight_error(error, source) - else - warn(error.message) - warn(error.backtrace) - end - - errored = true - rescue Check::UnformattedError, Debug::NonIdempotentFormatError - errored = true - rescue => error - warn(error.message) - warn(error.backtrace) - errored = true - end + + each_file(arguments) do |handler, filepath, source| + action.run(handler, filepath, source) + rescue Parser::ParseError => error + warn("Error: #{error.message}") + + if error.lineno + highlight_error(error, source) + else + warn(error.message) + warn(error.backtrace) end + + errored = true + rescue Check::UnformattedError, Debug::NonIdempotentFormatError + errored = true + rescue => error + warn(error.message) + warn(error.backtrace) + errored = true end if errored @@ -271,6 +267,22 @@ def run(argv) private + def each_file(arguments) + if STDIN.tty? + arguments.each do |pattern| + Dir.glob(pattern).each do |filepath| + next unless File.file?(filepath) + + handler = HANDLERS[File.extname(filepath)] + source = handler.read(filepath) + yield handler, filepath, source + end + end + else + yield HANDLERS[".rb"], :stdin, STDIN.read + end + end + # Highlights a snippet from a source and parse error. def highlight_error(error, source) lines = source.lines From 2960034d903eeb2672a34e1e91cbfaeb20267223 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 12 Apr 2022 11:07:30 -0400 Subject: [PATCH 19/23] Pretty print within a visitor --- README.md | 2 + lib/syntax_tree.rb | 4 +- lib/syntax_tree/json_visitor.rb | 1338 ----------- lib/syntax_tree/node.rb | 2140 +---------------- lib/syntax_tree/visitor.rb | 16 +- lib/syntax_tree/visitor/json_visitor.rb | 1335 ++++++++++ .../visitor/pretty_print_visitor.rb | 1213 ++++++++++ test/pretty_print_visitor_test.rb | 19 + 8 files changed, 2596 insertions(+), 3471 deletions(-) delete mode 100644 lib/syntax_tree/json_visitor.rb create mode 100644 lib/syntax_tree/visitor/json_visitor.rb create mode 100644 lib/syntax_tree/visitor/pretty_print_visitor.rb create mode 100644 test/pretty_print_visitor_test.rb diff --git a/README.md b/README.md index 0dc90057..32c13b77 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,8 @@ With visitors, you only define handlers for the nodes that you need. You can fin * call `visit(child)` with each child that you want to visit * call nothing if you're sure you don't want to descend further +There are a couple of visitors that ship with Syntax Tree that can be used as examples. They live in the [lib/syntax_tree/visitor](lib/syntax_tree/visitor) directory. + ### visit_method When you're creating a visitor, it's very easy to accidentally mistype a visit method. Unfortunately, there's no way to tell Ruby to explicitly override a parent method, so it would then be easy to define a method that never gets called. To mitigate this risk, there's `Visitor.visit_method(name)`. This method accepts a symbol that is checked against the list of known visit methods. If it's not in the list, then an error will be raised. It's meant to be used like: diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index dde21f18..f1df71c5 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -11,8 +11,8 @@ require_relative "syntax_tree/parser" require_relative "syntax_tree/version" require_relative "syntax_tree/visitor" - -require_relative "syntax_tree/json_visitor" +require_relative "syntax_tree/visitor/json_visitor" +require_relative "syntax_tree/visitor/pretty_print_visitor" # If PrettyPrint::Align isn't defined, then we haven't gotten the updated # version of prettyprint. In that case we'll define our own. This is going to diff --git a/lib/syntax_tree/json_visitor.rb b/lib/syntax_tree/json_visitor.rb deleted file mode 100644 index 89d745cd..00000000 --- a/lib/syntax_tree/json_visitor.rb +++ /dev/null @@ -1,1338 +0,0 @@ -# frozen_string_literal: true - -module SyntaxTree - class JSONVisitor < Visitor - def visit_aref(node) - { - type: :aref, - collection: visit(node.collection), - index: visit(node.index), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_aref_field(node) - { - type: :aref_field, - collection: visit(node.collection), - index: visit(node.index), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_alias(node) - { - type: :alias, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_arg_block(node) - { - type: :arg_block, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_arg_paren(node) - { - type: :arg_paren, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_arg_star(node) - { - type: :arg_star, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_args(node) - { - type: :args, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_args_forward(node) - visit_token(:args_forward, node) - end - - def visit_array(node) - { - type: :array, - cnts: visit(node.contents), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_aryptn(node) - { - type: :aryptn, - constant: visit(node.constant), - reqs: visit_all(node.requireds), - rest: visit(node.rest), - posts: visit_all(node.posts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_assign(node) - { - type: :assign, - target: visit(node.target), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_assoc(node) - { - type: :assoc, - key: visit(node.key), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_assoc_splat(node) - { - type: :assoc_splat, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_backref(node) - visit_token(:backref, node) - end - - def visit_backtick(node) - visit_token(:backtick, node) - end - - def visit_bare_assoc_hash(node) - { - type: :bare_assoc_hash, - assocs: visit_all(node.assocs), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_BEGIN(node) - { - type: :BEGIN, - lbrace: visit(node.lbrace), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_begin(node) - { - type: :begin, - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_binary(node) - { - type: :binary, - left: visit(node.left), - op: node.operator, - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_block_arg(node) - { - type: :blockarg, - name: visit(node.name), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_block_var(node) - { - type: :block_var, - params: visit(node.params), - locals: visit_all(node.locals), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_bodystmt(node) - { - type: :bodystmt, - stmts: visit(node.statements), - rsc: visit(node.rescue_clause), - els: visit(node.else_clause), - ens: visit(node.ensure_clause), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_brace_block(node) - { - type: :brace_block, - lbrace: visit(node.lbrace), - block_var: visit(node.block_var), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_break(node) - { - type: :break, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_call(node) - { - type: :call, - receiver: visit(node.receiver), - op: visit_call_operator(node.operator), - message: node.message == :call ? :call : visit(node.message), - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_case(node) - { - type: :case, - value: visit(node.value), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_CHAR(node) - visit_token(:CHAR, node) - end - - def visit_class(node) - { - type: :class, - constant: visit(node.constant), - superclass: visit(node.superclass), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_comma(node) - visit_token(:comma, node) - end - - def visit_command(node) - { - type: :command, - message: visit(node.message), - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_command_call(node) - { - type: :command_call, - receiver: visit(node.receiver), - op: visit_call_operator(node.operator), - message: visit(node.message), - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_comment(node) - { - type: :comment, - value: node.value, - inline: node.inline, - loc: visit_location(node.location) - } - end - - def visit_const(node) - visit_token(:const, node) - end - - def visit_const_path_field(node) - { - type: :const_path_field, - parent: visit(node.parent), - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_const_path_ref(node) - { - type: :const_path_ref, - parent: visit(node.parent), - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_const_ref(node) - { - type: :const_ref, - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_cvar(node) - visit_token(:cvar, node) - end - - def visit_def(node) - { - type: :def, - name: visit(node.name), - params: visit(node.params), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_def_endless(node) - { - type: :def_endless, - name: visit(node.name), - paren: visit(node.paren), - stmt: visit(node.statement), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_defined(node) - visit_token(:defined, node) - end - - def visit_defs(node) - { - type: :defs, - target: visit(node.target), - op: visit(node.operator), - name: visit(node.name), - params: visit(node.params), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_do_block(node) - { - type: :do_block, - keyword: visit(node.keyword), - block_var: visit(node.block_var), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_dot2(node) - { - type: :dot2, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_dot3(node) - { - type: :dot3, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_dyna_symbol(node) - { - type: :dyna_symbol, - parts: visit_all(node.parts), - quote: node.quote, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_END(node) - { - type: :END, - lbrace: visit(node.lbrace), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_else(node) - { - type: :else, - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_elsif(node) - { - type: :elsif, - pred: visit(node.predicate), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_embdoc(node) - { - type: :embdoc, - value: node.value, - loc: visit_location(node.location) - } - end - - def visit_embexpr_beg(node) - { - type: :embexpr_beg, - value: node.value, - loc: visit_location(node.location) - } - end - - def visit_embexpr_end(node) - { - type: :embexpr_end, - value: node.value, - loc: visit_location(node.location) - } - end - - def visit_embvar(node) - { - type: :embvar, - value: node.value, - loc: visit_location(node.location) - } - end - - def visit_ensure(node) - { - type: :ensure, - keyword: visit(node.keyword), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_excessed_comma(node) - visit_token(:excessed_comma, node) - end - - def visit_fcall(node) - { - type: :fcall, - value: visit(node.value), - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_field(node) - { - type: :field, - parent: visit(node.parent), - op: visit_call_operator(node.operator), - name: visit(node.name), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_float(node) - visit_token(:float, node) - end - - def visit_fndptn(node) - { - type: :fndptn, - constant: visit(node.constant), - left: visit(node.left), - values: visit_all(node.values), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_for(node) - { - type: :for, - index: visit(node.index), - collection: visit(node.collection), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_gvar(node) - visit_token(:gvar, node) - end - - def visit_hash(node) - { - type: :hash, - assocs: visit_all(node.assocs), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_heredoc(node) - { - type: :heredoc, - beging: visit(node.beginning), - ending: node.ending, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_heredoc_beg(node) - visit_token(:heredoc_beg, node) - end - - def visit_hshptn(node) - { - type: :hshptn, - constant: visit(node.constant), - keywords: node.keywords.map { |(name, value)| [visit(name), visit(value)] }, - kwrest: visit(node.keyword_rest), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_ident(node) - visit_token(:ident, node) - end - - def visit_if(node) - { - type: :if, - pred: visit(node.predicate), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_if_mod(node) - { - type: :if_mod, - stmt: visit(node.statement), - pred: visit(node.predicate), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_if_op(node) - { - type: :ifop, - pred: visit(node.predicate), - tthy: visit(node.truthy), - flsy: visit(node.falsy), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_imaginary(node) - visit_token(:imaginary, node) - end - - def visit_in(node) - { - type: :in, - pattern: visit(node.pattern), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_int(node) - visit_token(:int, node) - end - - def visit_ivar(node) - visit_token(:ivar, node) - end - - def visit_kw(node) - visit_token(:kw, node) - end - - def visit_kwrest_param(node) - { - type: :kwrest_param, - name: visit(node.name), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_label(node) - visit_token(:label, node) - end - - def visit_label_end(node) - visit_token(:label_end, node) - end - - def visit_lambda(node) - { - type: :lambda, - params: visit(node.params), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_lbrace(node) - visit_token(:lbrace, node) - end - - def visit_lbracket(node) - visit_token(:lbracket, node) - end - - def visit_lparen(node) - visit_token(:lparen, node) - end - - def visit_massign(node) - { - type: :massign, - target: visit(node.target), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_method_add_block(node) - { - type: :method_add_block, - call: visit(node.call), - block: visit(node.block), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_mlhs(node) - { - type: :mlhs, - parts: visit_all(node.parts), - comma: node.comma, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_mlhs_paren(node) - { - type: :mlhs_paren, - cnts: visit(node.contents), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_module(node) - { - type: :module, - constant: visit(node.constant), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_mrhs(node) - { - type: :mrhs, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_next(node) - { - type: :next, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_not(node) - { - type: :not, - value: visit(node.statement), - paren: node.parentheses, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_op(node) - visit_token(:op, node) - end - - def visit_op_assign(node) - { - type: :opassign, - target: visit(node.target), - op: visit(node.operator), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_params(node) - { - type: :params, - reqs: visit_all(node.requireds), - opts: node.optionals.map { |(name, value)| [visit(name), visit(value)] }, - rest: visit(node.rest), - posts: visit_all(node.posts), - keywords: node.keywords.map { |(name, value)| [visit(name), visit(value || nil)] }, - kwrest: node.keyword_rest == :nil ? "nil" : visit(node.keyword_rest), - block: visit(node.block), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_paren(node) - { - type: :paren, - lparen: visit(node.lparen), - cnts: visit(node.contents), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_period(node) - visit_token(:period, node) - end - - def visit_pinned_begin(node) - { - type: :pinned_begin, - stmt: visit(node.statement), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_pinned_var_ref(node) - { - type: :pinned_var_ref, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_program(node) - { - type: :program, - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_qsymbols(node) - { - type: :qsymbols, - elems: visit_all(node.elements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_qsymbols_beg(node) - visit_token(:qsymbols_beg, node) - end - - def visit_qwords(node) - { - type: :qwords, - elems: visit_all(node.elements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_qwords_beg(node) - visit_token(:qwords_beg, node) - end - - def visit_rassign(node) - { - type: :rassign, - value: visit(node.value), - op: visit(node.operator), - pattern: visit(node.pattern), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rational(node) - visit_token(:rational, node) - end - - def visit_rbrace(node) - visit_token(:rbrace, node) - end - - def visit_rbracket(node) - visit_token(:rbracket, node) - end - - def visit_redo(node) - visit_token(:redo, node) - end - - def visit_regexp_beg(node) - visit_token(:regexp_beg, node) - end - - def visit_regexp_content(node) - { - type: :regexp_content, - beging: node.beginning, - parts: visit_all(node.parts), - loc: visit_location(node.location) - } - end - - def visit_regexp_end(node) - visit_token(:regexp_end, node) - end - - def visit_regexp_literal(node) - { - type: :regexp_literal, - beging: node.beginning, - ending: node.ending, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rescue(node) - { - type: :rescue, - extn: visit(node.exception), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rescue_ex(node) - { - type: :rescue_ex, - extns: visit(node.exceptions), - var: visit(node.variable), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rescue_mod(node) - { - type: :rescue_mod, - stmt: visit(node.statement), - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_rest_param(node) - { - type: :rest_param, - name: visit(node.name), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_retry(node) - visit_token(:retry, node) - end - - def visit_return(node) - { - type: :return, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_return0(node) - visit_token(:return0, node) - end - - def visit_rparen(node) - visit_token(:rparen, node) - end - - def visit_sclass(node) - { - type: :sclass, - target: visit(node.target), - bodystmt: visit(node.bodystmt), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_statements(node) - { - type: :statements, - body: visit_all(node.body), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_string_concat(node) - { - type: :string_concat, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_string_content(node) - { - type: :string_content, - parts: visit_all(node.parts), - loc: visit_location(node.location) - } - end - - def visit_string_dvar(node) - { - type: :string_dvar, - var: visit(node.variable), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_string_embexpr(node) - { - type: :string_embexpr, - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_string_literal(node) - { - type: :string_literal, - parts: visit_all(node.parts), - quote: node.quote, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_super(node) - { - type: :super, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_symbeg(node) - visit_token(:symbeg, node) - end - - def visit_symbol_content(node) - { - type: :symbol_content, - value: visit(node.value), - loc: visit_location(node.location) - } - end - - def visit_symbol_literal(node) - { - type: :symbol_literal, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_symbols(node) - { - type: :symbols, - elems: visit_all(node.elements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_symbols_beg(node) - visit_token(:symbols_beg, node) - end - - def visit_tlambda(node) - visit_token(:tlambda, node) - end - - def visit_tlambeg(node) - visit_token(:tlambeg, node) - end - - def visit_tstring_beg(node) - visit_token(:tstring_beg, node) - end - - def visit_tstring_content(node) - visit_token(:tstring_content, node) - end - - def visit_tstring_end(node) - visit_token(:tstring_end, node) - end - - def visit_top_const_field(node) - { - type: :top_const_field, - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_top_const_ref(node) - { - type: :top_const_ref, - constant: visit(node.constant), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_unary(node) - { - type: :unary, - op: node.operator, - value: visit(node.statement), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_undef(node) - { - type: :undef, - syms: visit_all(node.symbols), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_unless(node) - { - type: :unless, - pred: visit(node.predicate), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_unless_mod(node) - { - type: :unless_mod, - stmt: visit(node.statement), - pred: visit(node.predicate), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_until(node) - { - type: :until, - pred: visit(node.predicate), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_until_mod(node) - { - type: :until_mod, - stmt: visit(node.statement), - pred: visit(node.predicate), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_var_alias(node) - { - type: :var_alias, - left: visit(node.left), - right: visit(node.right), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_var_field(node) - { - type: :var_field, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_var_ref(node) - { - type: :var_ref, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_vcall(node) - { - type: :vcall, - value: visit(node.value), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_void_stmt(node) - { - type: :void_stmt, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_when(node) - { - type: :when, - args: visit(node.arguments), - stmts: visit(node.statements), - cons: visit(node.consequent), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_while(node) - { - type: :while, - pred: visit(node.predicate), - stmts: visit(node.statements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_while_mod(node) - { - type: :while_mod, - stmt: visit(node.statement), - pred: visit(node.predicate), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_word(node) - { - type: :word, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_words(node) - { - type: :words, - elems: visit_all(node.elements), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_words_beg(node) - visit_token(:words_beg, node) - end - - def visit_xstring(node) - { - type: :xstring, - parts: visit_all(node.parts), - loc: visit_location(node.location) - } - end - - def visit_xstring_literal(node) - { - type: :xstring_literal, - parts: visit_all(node.parts), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_yield(node) - { - type: :yield, - args: visit(node.arguments), - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - - def visit_yield0(node) - visit_token(:yield0, node) - end - - def visit_zsuper(node) - visit_token(:zsuper, node) - end - - def visit___end__(node) - visit_token(:__end__, node) - end - - # Explicitly undefine the default aliased method for each of the visitor - # methods so that if there is one that isn't implemented it will raise an - # error. - undef visit_child_nodes - - private - - def visit_call_operator(operator) - operator == :"::" ? :"::" : visit(operator) - end - - def visit_location(location) - [ - location.start_line, - location.start_char, - location.end_line, - location.end_char - ] - end - - def visit_token(type, node) - { - type: type, - value: node.value, - loc: visit_location(node.location), - cmts: visit_all(node.comments) - } - end - end -end diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 9081d977..8b8801aa 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -73,11 +73,12 @@ def format(q) end def pretty_print(q) - raise NotImplementedError + visitor = Visitor::PrettyPrintVisitor.new(q) + visitor.visit(self) end def to_json(*opts) - visitor = JSONVisitor.new + visitor = Visitor::JSONVisitor.new visitor.visit(self).to_json(*opts) end end @@ -139,17 +140,6 @@ def format(q) q.text("}") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("BEGIN") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # CHAR irepresents a single codepoint in the script encoding. @@ -194,17 +184,6 @@ def format(q) q.text(q.quote) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("CHAR") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # ENDBlock represents the use of the +END+ keyword, which hooks into the @@ -264,17 +243,6 @@ def format(q) q.text("}") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("END") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # EndContent represents the use of __END__ syntax, which allows individual @@ -320,17 +288,6 @@ def format(q) separator = -> { q.breakable(indent: false, force: true) } q.seplist(value.split(/\r?\n/, -1), separator) { |line| q.text(line) } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("__end__") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # Alias represents the use of the +alias+ keyword with regular arguments (not @@ -415,20 +372,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("alias") - - q.breakable - q.pp(left) - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end end # ARef represents when you're pulling a value out of a collection at a @@ -496,20 +439,6 @@ def format(q) q.text("]") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("aref") - - q.breakable - q.pp(collection) - - q.breakable - q.pp(index) - - q.pp(Comment::List.new(comments)) - end - end end # ARefField represents assigning values into collections at specific indices. @@ -571,20 +500,6 @@ def format(q) q.text("]") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("aref_field") - - q.breakable - q.pp(collection) - - q.breakable - q.pp(index) - - q.pp(Comment::List.new(comments)) - end - end end # ArgParen represents wrapping arguments to a method inside a set of @@ -640,17 +555,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("arg_paren") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end end # Args represents a list of arguments being passed to a method call or array @@ -688,17 +592,6 @@ def deconstruct_keys(keys) def format(q) q.seplist(parts) { |part| q.format(part) } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("args") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end end # ArgBlock represents using a block operator on an expression. @@ -736,19 +629,6 @@ def format(q) q.text("&") q.format(value) if value end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("arg_block") - - if value - q.breakable - q.pp(value) - end - - q.pp(Comment::List.new(comments)) - end - end end # Star represents using a splat operator on an expression. @@ -786,17 +666,6 @@ def format(q) q.text("*") q.format(value) if value end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("arg_star") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # ArgsForward represents forwarding all kinds of arguments onto another method @@ -846,17 +715,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("args_forward") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # ArrayLiteral represents an array literal, which can optionally contain @@ -1003,17 +861,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("array") - - q.breakable - q.pp(contents) - - q.pp(Comment::List.new(comments)) - end - end - private def qwords? @@ -1169,36 +1016,6 @@ def format(q) q.group { q.seplist(parts) { |part| q.format(part) } } end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("aryptn") - - if constant - q.breakable - q.pp(constant) - end - - if requireds.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(requireds) { |required| q.pp(required) } - end - end - - if rest - q.breakable - q.pp(rest) - end - - if posts.any? - q.breakable - q.group(2, "(", ")") { q.seplist(posts) { |post| q.pp(post) } } - end - - q.pp(Comment::List.new(comments)) - end - end end # Determins if the following value should be indented or not. @@ -1273,20 +1090,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("assign") - - q.breakable - q.pp(target) - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - private def skip_indent? @@ -1340,22 +1143,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("assoc") - - q.breakable - q.pp(key) - - if value - q.breakable - q.pp(value) - end - - q.pp(Comment::List.new(comments)) - end - end - private def format_contents(q) @@ -1410,17 +1197,6 @@ def format(q) q.text("**") q.format(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("assoc_splat") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # Backref represents a global variable referencing a matched value. It comes @@ -1458,17 +1234,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("backref") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # Backtick represents the use of the ` operator. It's usually found being used @@ -1504,17 +1269,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("backtick") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # This module is responsible for formatting the assocs contained within a @@ -1620,17 +1374,6 @@ def format(q) def format_key(q, key) (@key_formatter ||= HashKeyFormatter.for(self)).format_key(q, key) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("bare_assoc_hash") - - q.breakable - q.group(2, "(", ")") { q.seplist(assocs) { |assoc| q.pp(assoc) } } - - q.pp(Comment::List.new(comments)) - end - end end # Begin represents a begin..end chain. @@ -1679,17 +1422,6 @@ def format(q) q.breakable(force: true) q.text("end") end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("begin") - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end end # PinnedBegin represents a pinning a nested statement within pattern matching. @@ -1738,17 +1470,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("pinned_begin") - - q.breakable - q.pp(statement) - - q.pp(Comment::List.new(comments)) - end - end end # Binary represents any expression that involves two sub-expressions with an @@ -1825,23 +1546,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("binary") - - q.breakable - q.pp(left) - - q.breakable - q.text(operator) - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end end # This module will remove any breakables from the list of contents so that no @@ -1939,22 +1643,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("block_var") - - q.breakable - q.pp(params) - - if locals.any? - q.breakable - q.group(2, "(", ")") { q.seplist(locals) { |local| q.pp(local) } } - end - - q.pp(Comment::List.new(comments)) - end - end end # BlockArg represents declaring a block parameter on a method definition. @@ -1975,7 +1663,7 @@ def initialize(name:, location:, comments: []) end def accept(visitor) - visitor.visit_block_arg(self) + visitor.visit_blockarg(self) end def child_nodes @@ -1992,19 +1680,6 @@ def format(q) q.text("&") q.format(name) if name end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("blockarg") - - if name - q.breakable - q.pp(name) - end - - q.pp(Comment::List.new(comments)) - end - end end # bodystmt can't actually determine its bounds appropriately because it @@ -2130,42 +1805,16 @@ def format(q) end end end + end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("bodystmt") + # Responsible for formatting either a BraceBlock or a DoBlock. + class BlockFormatter + class BlockOpenFormatter + # [String] the actual output that should be printed + attr_reader :text - q.breakable - q.pp(statements) - - if rescue_clause - q.breakable - q.pp(rescue_clause) - end - - if else_clause - q.breakable - q.pp(else_clause) - end - - if ensure_clause - q.breakable - q.pp(ensure_clause) - end - - q.pp(Comment::List.new(comments)) - end - end - end - - # Responsible for formatting either a BraceBlock or a DoBlock. - class BlockFormatter - class BlockOpenFormatter - # [String] the actual output that should be printed - attr_reader :text - - # [LBrace | Keyword] the node that is being represented - attr_reader :node + # [LBrace | Keyword] the node that is being represented + attr_reader :node def initialize(text, node) @text = text @@ -2365,22 +2014,6 @@ def deconstruct_keys(keys) def format(q) BlockFormatter.new(self, lbrace, "}", statements).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("brace_block") - - if block_var - q.breakable - q.pp(block_var) - end - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # Formats either a Break or Next node. @@ -2472,17 +2105,6 @@ def deconstruct_keys(keys) def format(q) FlowControlFormatter.new("break", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("break") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end end # Wraps a call operator (which can be a string literal :: or an Op node or a @@ -2597,28 +2219,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("call") - - q.breakable - q.pp(receiver) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(message) - - if arguments - q.breakable - q.pp(arguments) - end - - q.pp(Comment::List.new(comments)) - end - end end # Case represents the beginning of a case chain. @@ -2689,25 +2289,6 @@ def format(q) q.text("end") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("case") - - q.breakable - q.pp(keyword) - - if value - q.breakable - q.pp(value) - end - - q.breakable - q.pp(consequent) - - q.pp(Comment::List.new(comments)) - end - end end # RAssign represents a single-line pattern match. @@ -2770,23 +2351,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rassign") - - q.breakable - q.pp(value) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(pattern) - - q.pp(Comment::List.new(comments)) - end - end end # Class represents defining a class using the +class+ keyword. @@ -2896,25 +2460,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("class") - - q.breakable - q.pp(constant) - - if superclass - q.breakable - q.pp(superclass) - end - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end end # Comma represents the use of the , operator. @@ -2940,15 +2485,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("comma") - - q.breakable - q.pp(value) - end - end end # Command represents a method call with arguments and no parentheses. Note @@ -3006,20 +2542,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("command") - - q.breakable - q.pp(message) - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end - private def align?(node) @@ -3108,28 +2630,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("command_call") - - q.breakable - q.pp(receiver) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(message) - - if arguments - q.breakable - q.pp(arguments) - end - - q.pp(Comment::List.new(comments)) - end - end - private # This is a somewhat naive method that is attempting to sum up the width of @@ -3185,22 +2685,6 @@ def argument_alignment(q, doc) # # comment # class Comment < Node - class List - # [Array[ Comment ]] the list of comments this list represents - attr_reader :comments - - def initialize(comments) - @comments = comments - end - - def pretty_print(q) - return if comments.empty? - - q.breakable - q.group(2, "(", ")") { q.seplist(comments) { |comment| q.pp(comment) } } - end - end - # [String] the contents of the comment attr_reader :value @@ -3259,15 +2743,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("comment") - - q.breakable - q.pp(value) - end - end end # Const represents a literal value that _looks_ like a constant. This could @@ -3314,17 +2789,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("const") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # ConstPathField represents the child node of some kind of assignment. It @@ -3374,20 +2838,6 @@ def format(q) q.text("::") q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("const_path_field") - - q.breakable - q.pp(parent) - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end end # ConstPathRef represents referencing a constant by a path. @@ -3435,20 +2885,6 @@ def format(q) q.text("::") q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("const_path_ref") - - q.breakable - q.pp(parent) - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end end # ConstRef represents the name of the constant being used in a class or module @@ -3487,17 +2923,6 @@ def deconstruct_keys(keys) def format(q) q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("const_ref") - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end end # CVar represents the use of a class variable. @@ -3534,17 +2959,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("cvar") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # Def represents defining a regular method on the current self object. @@ -3611,23 +3025,6 @@ def format(q) q.text("end") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("def") - - q.breakable - q.pp(name) - - q.breakable - q.pp(params) - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end end # DefEndless represents defining a single-line method since Ruby 3.0+. @@ -3719,33 +3116,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("def_endless") - - if target - q.breakable - q.pp(target) - - q.breakable - q.pp(operator) - end - - q.breakable - q.pp(name) - - if paren - q.breakable - q.pp(paren) - end - - q.breakable - q.pp(statement) - - q.pp(Comment::List.new(comments)) - end - end end # Defined represents the use of the +defined?+ operator. It can be used with @@ -3789,17 +3159,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("defined") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # Defs represents defining a singleton method on an object. @@ -3886,29 +3245,6 @@ def format(q) q.text("end") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("defs") - - q.breakable - q.pp(target) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(name) - - q.breakable - q.pp(params) - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end end # DoBlock represents passing a block to a method call using the +do+ and +end+ @@ -3961,23 +3297,7 @@ def deconstruct_keys(keys) def format(q) BlockFormatter.new(self, keyword, "end", bodystmt).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("do_block") - - if block_var - q.breakable - q.pp(block_var) - end - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end - end + end # Responsible for formatting Dot2 and Dot3 nodes. class DotFormatter @@ -4051,24 +3371,6 @@ def deconstruct_keys(keys) def format(q) DotFormatter.new("..", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("dot2") - - if left - q.breakable - q.pp(left) - end - - if right - q.breakable - q.pp(right) - end - - q.pp(Comment::List.new(comments)) - end - end end # Dot3 represents using the ... operator between two expressions. Usually this @@ -4117,24 +3419,6 @@ def deconstruct_keys(keys) def format(q) DotFormatter.new("...", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("dot3") - - if left - q.breakable - q.pp(left) - end - - if right - q.breakable - q.pp(right) - end - - q.pp(Comment::List.new(comments)) - end - end end # Responsible for providing information about quotes to be used for strings @@ -4237,17 +3521,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("dyna_symbol") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - private # Here we determine the quotes to use for a dynamic symbol. It's bound by a @@ -4342,17 +3615,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("else") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # Elsif represents another clause in an +if+ or +unless+ chain. @@ -4430,25 +3692,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("elsif") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end end # EmbDoc represents a multi-line comment. @@ -4497,15 +3740,6 @@ def format(q) q.trim q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("embdoc") - - q.breakable - q.pp(value) - end - end end # EmbExprBeg represents the beginning token for using interpolation inside of @@ -4536,15 +3770,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("embexpr_beg") - - q.breakable - q.pp(value) - end - end end # EmbExprEnd represents the ending token for using interpolation inside of a @@ -4575,15 +3800,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("embexpr_end") - - q.breakable - q.pp(value) - end - end end # EmbVar represents the use of shorthand interpolation for an instance, class, @@ -4616,15 +3832,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("embvar") - - q.breakable - q.pp(value) - end - end end # Ensure represents the use of the +ensure+ keyword and its subsequent @@ -4680,17 +3887,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("ensure") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # ExcessedComma represents a trailing comma in a list of block parameters. It @@ -4733,17 +3929,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("excessed_comma") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # FCall represents the piece of a method call that comes before any arguments @@ -4793,22 +3978,6 @@ def format(q) q.format(value) q.format(arguments) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("fcall") - - q.breakable - q.pp(value) - - if arguments - q.breakable - q.pp(arguments) - end - - q.pp(Comment::List.new(comments)) - end - end end # Field is always the child of an assignment. It represents assigning to a @@ -4864,23 +4033,6 @@ def format(q) q.format(name) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("field") - - q.breakable - q.pp(parent) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(name) - - q.pp(Comment::List.new(comments)) - end - end end # FloatLiteral represents a floating point number literal. @@ -4917,17 +4069,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("float") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # FndPtn represents matching against a pattern where you find a pattern in an @@ -4998,28 +4139,6 @@ def format(q) q.format(right) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("fndptn") - - if constant - q.breakable - q.pp(constant) - end - - q.breakable - q.pp(left) - - q.breakable - q.group(2, "(", ")") { q.seplist(values) { |value| q.pp(value) } } - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end end # For represents using a +for+ loop. @@ -5087,23 +4206,6 @@ def format(q) q.text("end") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("for") - - q.breakable - q.pp(index) - - q.breakable - q.pp(collection) - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # GVar represents a global variable literal. @@ -5140,17 +4242,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("gvar") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # HashLiteral represents a hash literal. @@ -5200,19 +4291,6 @@ def format_key(q, key) (@key_formatter ||= HashKeyFormatter.for(self)).format_key(q, key) end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("hash") - - if assocs.any? - q.breakable - q.group(2, "(", ")") { q.seplist(assocs) { |assoc| q.pp(assoc) } } - end - - q.pp(Comment::List.new(comments)) - end - end - private def format_contents(q) @@ -5310,17 +4388,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("heredoc") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end end # HeredocBeg represents the beginning declaration of a heredoc. @@ -5360,17 +4427,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("heredoc_beg") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # HshPtn represents matching against a hash pattern using the Ruby 2.7+ @@ -5488,40 +4544,6 @@ def format(q) contents.call end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("hshptn") - - if constant - q.breakable - q.pp(constant) - end - - if keywords.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(keywords) do |(key, value)| - q.group(2, "(", ")") do - q.pp(key) - - if value - q.breakable - q.pp(value) - end - end - end - end - end - - if keyword_rest - q.breakable - q.pp(keyword_rest) - end - - q.pp(Comment::List.new(comments)) - end - end end # The list of nodes that represent patterns inside of pattern matching so that @@ -5563,17 +4585,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("ident") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # If the predicate of a conditional or loop contains an assignment (in which @@ -5709,25 +4720,6 @@ def deconstruct_keys(keys) def format(q) ConditionalFormatter.new("if", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("if") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end end # IfOp represents a ternary clause. @@ -5791,24 +4783,7 @@ def format(q) q.group { q.if_break { format_break(q) }.if_flat { format_flat(q) } } end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("ifop") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(truthy) - - q.breakable - q.pp(falsy) - - q.pp(Comment::List.new(comments)) - end - end - - private + private def format_break(q) Parentheses.break(q) do @@ -5932,20 +4907,6 @@ def deconstruct_keys(keys) def format(q) ConditionalModFormatter.new("if", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("if_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(predicate) - - q.pp(Comment::List.new(comments)) - end - end end # Imaginary represents an imaginary number literal. @@ -5982,17 +4943,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("imaginary") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # In represents using the +in+ keyword within the Ruby 2.7+ pattern matching @@ -6063,25 +5013,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("in") - - q.breakable - q.pp(pattern) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end end # Int represents an integer number literal. @@ -6126,17 +5057,6 @@ def format(q) q.text(value) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("int") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # IVar represents an instance variable literal. @@ -6173,17 +5093,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("ivar") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # Kw represents the use of a keyword. It can be almost anywhere in the syntax @@ -6229,17 +5138,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("kw") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # KwRestParam represents defining a parameter in a method definition that @@ -6278,17 +5176,6 @@ def format(q) q.text("**") q.format(name) if name end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("kwrest_param") - - q.breakable - q.pp(name) - - q.pp(Comment::List.new(comments)) - end - end end # Label represents the use of an identifier to associate with an object. You @@ -6334,18 +5221,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("label") - - q.breakable - q.text(":") - q.text(value[0...-1]) - - q.pp(Comment::List.new(comments)) - end - end end # LabelEnd represents the end of a dynamic symbol. @@ -6377,15 +5252,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("label_end") - - q.breakable - q.pp(value) - end - end end # Lambda represents using a lambda literal (not the lambda method call). @@ -6462,20 +5328,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("lambda") - - q.breakable - q.pp(params) - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # LBrace represents the use of a left brace, i.e., {. @@ -6509,17 +5361,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("lbrace") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # LBracket represents the use of a left bracket, i.e., [. @@ -6553,17 +5394,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("lbracket") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # LParen represents the use of a left parenthesis, i.e., (. @@ -6597,17 +5427,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("lparen") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # MAssign is a parent node of any kind of multiple assignment. This includes @@ -6665,20 +5484,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("massign") - - q.breakable - q.pp(target) - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # MethodAddBlock represents a method call with a block argument. @@ -6720,20 +5525,6 @@ def format(q) q.format(call) q.format(block) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("method_add_block") - - q.breakable - q.pp(call) - - q.breakable - q.pp(block) - - q.pp(Comment::List.new(comments)) - end - end end # MLHS represents a list of values being destructured on the left-hand side @@ -6779,17 +5570,6 @@ def format(q) q.seplist(parts) { |part| q.format(part) } q.text(",") if comma end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("mlhs") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end end # MLHSParen represents parentheses being used to destruct values in a multiple @@ -6840,17 +5620,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("mlhs_paren") - - q.breakable - q.pp(contents) - - q.pp(Comment::List.new(comments)) - end - end end # ModuleDeclaration represents defining a module using the +module+ keyword. @@ -6922,20 +5691,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("module") - - q.breakable - q.pp(constant) - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end end # MRHS represents the values that are being assigned on the right-hand side of @@ -6973,17 +5728,6 @@ def deconstruct_keys(keys) def format(q) q.seplist(parts) { |part| q.format(part) } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("mrhs") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end end # Next represents using the +next+ keyword. @@ -7033,17 +5777,6 @@ def deconstruct_keys(keys) def format(q) FlowControlFormatter.new("next", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("next") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end end # Op represents an operator literal in the source. @@ -7081,17 +5814,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("op") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # OpAssign represents assigning a value to a variable or constant using an @@ -7122,7 +5844,7 @@ def initialize(target:, operator:, value:, location:, comments: []) end def accept(visitor) - visitor.visit_op_assign(self) + visitor.visit_opassign(self) end def child_nodes @@ -7159,23 +5881,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("opassign") - - q.breakable - q.pp(target) - - q.breakable - q.pp(operator) - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - private def skip_indent? @@ -7442,70 +6147,6 @@ def format(q) q.nest(0, &contents) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("params") - - if requireds.any? - q.breakable - q.group(2, "(", ")") { q.seplist(requireds) { |name| q.pp(name) } } - end - - if optionals.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(optionals) do |(name, default)| - q.pp(name) - q.text("=") - q.group(2) do - q.breakable("") - q.pp(default) - end - end - end - end - - if rest - q.breakable - q.pp(rest) - end - - if posts.any? - q.breakable - q.group(2, "(", ")") { q.seplist(posts) { |value| q.pp(value) } } - end - - if keywords.any? - q.breakable - q.group(2, "(", ")") do - q.seplist(keywords) do |(name, default)| - q.pp(name) - - if default - q.text("=") - q.group(2) do - q.breakable("") - q.pp(default) - end - end - end - end - end - - if keyword_rest - q.breakable - q.pp(keyword_rest) - end - - if block - q.breakable - q.pp(block) - end - - q.pp(Comment::List.new(comments)) - end - end end # Paren represents using balanced parentheses in a couple places in a Ruby @@ -7565,17 +6206,6 @@ def format(q) q.text(")") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("paren") - - q.breakable - q.pp(contents) - - q.pp(Comment::List.new(comments)) - end - end end # Period represents the use of the +.+ operator. It is usually found in method @@ -7610,18 +6240,7 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("period") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - end + end # Program represents the overall syntax tree. class Program < Node @@ -7659,17 +6278,6 @@ def format(q) # replicate the text exactly so we will just let it be. q.breakable(force: true) unless statements.body.last.is_a?(EndContent) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("program") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # QSymbols represents a symbol literal array without interpolation. @@ -7730,17 +6338,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("qsymbols") - - q.breakable - q.group(2, "(", ")") { q.seplist(elements) { |element| q.pp(element) } } - - q.pp(Comment::List.new(comments)) - end - end end # QSymbolsBeg represents the beginning of a symbol literal array. @@ -7841,17 +6438,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("qwords") - - q.breakable - q.group(2, "(", ")") { q.seplist(elements) { |element| q.pp(element) } } - - q.pp(Comment::List.new(comments)) - end - end end # QWordsBeg represents the beginning of a string literal array. @@ -7883,15 +6469,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("qwords_beg") - - q.breakable - q.pp(value) - end - end end # RationalLiteral represents the use of a rational number literal. @@ -7928,17 +6505,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rational") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # RBrace represents the use of a right brace, i.e., +++. @@ -7964,15 +6530,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rbrace") - - q.breakable - q.pp(value) - end - end end # RBracket represents the use of a right bracket, i.e., +]+. @@ -7998,15 +6555,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rbracket") - - q.breakable - q.pp(value) - end - end end # Redo represents the use of the +redo+ keyword. @@ -8043,17 +6591,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("redo") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # RegexpContent represents the body of a regular expression. @@ -8089,15 +6626,6 @@ def child_nodes def deconstruct_keys(keys) { beginning: beginning, parts: parts, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("regexp_content") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - end - end end # RegexpBeg represents the start of a regular expression literal. @@ -8131,15 +6659,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("regexp_beg") - - q.breakable - q.pp(value) - end - end end # RegexpEnd represents the end of a regular expression literal. @@ -8174,15 +6693,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("regexp_end") - - q.breakable - q.pp(value) - end - end end # RegexpLiteral represents a regular expression literal. @@ -8271,17 +6781,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("regexp_literal") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end - private def include?(pattern) @@ -8359,20 +6858,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rescue_ex") - - q.breakable - q.pp(exceptions) - - q.breakable - q.pp(variable) - - q.pp(Comment::List.new(comments)) - end - end end # Rescue represents the use of the rescue keyword inside of a BodyStmt node. @@ -8474,27 +6959,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rescue") - - if exception - q.breakable - q.pp(exception) - end - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end end # RescueMod represents the use of the modifier form of a +rescue+ clause. @@ -8552,20 +7016,6 @@ def format(q) q.breakable(force: true) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rescue_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # RestParam represents defining a parameter in a method definition that @@ -8604,17 +7054,6 @@ def format(q) q.text("*") q.format(name) if name end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rest_param") - - q.breakable - q.pp(name) - - q.pp(Comment::List.new(comments)) - end - end end # Retry represents the use of the +retry+ keyword. @@ -8651,17 +7090,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("retry") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # Return represents using the +return+ keyword with arguments. @@ -8698,17 +7126,6 @@ def deconstruct_keys(keys) def format(q) FlowControlFormatter.new("return", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("return") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end end # Return0 represents the bare +return+ keyword with no arguments. @@ -8745,17 +7162,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("return0") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # RParen represents the use of a right parenthesis, i.e., +)+. @@ -8781,15 +7187,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("rparen") - - q.breakable - q.pp(value) - end - end end # SClass represents a block of statements that should be evaluated within the @@ -8845,20 +7242,6 @@ def format(q) q.breakable(force: true) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("sclass") - - q.breakable - q.pp(target) - - q.breakable - q.pp(bodystmt) - - q.pp(Comment::List.new(comments)) - end - end end # Everything that has a block of code inside of it has a list of statements. @@ -8991,17 +7374,6 @@ def format(q) end end - def pretty_print(q) - q.group(2, "(", ")") do - q.text("statements") - - q.breakable - q.seplist(body) { |statement| q.pp(statement) } - - q.pp(Comment::List.new(comments)) - end - end - private # As efficiently as possible, gather up all of the comments that have been @@ -9064,15 +7436,6 @@ def child_nodes def deconstruct_keys(keys) { parts: parts, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_content") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - end - end end # StringConcat represents concatenating two strings together using a backward @@ -9122,20 +7485,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_concat") - - q.breakable - q.pp(left) - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end end # StringDVar represents shorthand interpolation of a variable into a string. @@ -9176,17 +7525,6 @@ def format(q) q.format(variable) q.text("}") end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_dvar") - - q.breakable - q.pp(variable) - - q.pp(Comment::List.new(comments)) - end - end end # StringEmbExpr represents interpolated content. It can be contained within a @@ -9242,17 +7580,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_embexpr") - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # StringLiteral represents a string literal. @@ -9320,17 +7647,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("string_literal") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end end # Super represents using the +super+ keyword with arguments. It can optionally @@ -9377,17 +7693,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("super") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end end # SymBeg represents the beginning of a symbol literal. @@ -9428,16 +7733,7 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("symbeg") - - q.breakable - q.pp(value) - end - end - end + end # SymbolContent represents symbol contents and is always the child of a # SymbolLiteral node. @@ -9467,15 +7763,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("symbol_content") - - q.breakable - q.pp(value) - end - end end # SymbolLiteral represents a symbol in the system with no interpolation @@ -9515,17 +7802,6 @@ def format(q) q.text(":") q.format(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("symbol_literal") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # Symbols represents a symbol array literal with interpolation. @@ -9586,17 +7862,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("symbols") - - q.breakable - q.group(2, "(", ")") { q.seplist(elements) { |element| q.pp(element) } } - - q.pp(Comment::List.new(comments)) - end - end end # SymbolsBeg represents the start of a symbol array literal with @@ -9629,15 +7894,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("symbols_beg") - - q.breakable - q.pp(value) - end - end end # TLambda represents the beginning of a lambda literal. @@ -9667,15 +7923,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("tlambda") - - q.breakable - q.pp(value) - end - end end # TLamBeg represents the beginning of the body of a lambda literal using @@ -9706,15 +7953,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("tlambeg") - - q.breakable - q.pp(value) - end - end end # TopConstField is always the child node of some kind of assignment. It @@ -9754,17 +7992,6 @@ def format(q) q.text("::") q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("top_const_field") - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end end # TopConstRef is very similar to TopConstField except that it is not involved @@ -9803,17 +8030,6 @@ def format(q) q.text("::") q.format(constant) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("top_const_ref") - - q.breakable - q.pp(constant) - - q.pp(Comment::List.new(comments)) - end - end end # TStringBeg represents the beginning of a string literal. @@ -9848,15 +8064,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("tstring_beg") - - q.breakable - q.pp(value) - end - end end # TStringContent represents plain characters inside of an entity that accepts @@ -9901,17 +8108,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("tstring_content") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # TStringEnd represents the end of a string literal. @@ -9946,15 +8142,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("tstring_end") - - q.breakable - q.pp(value) - end - end end # Not represents the unary +not+ method being called on an expression. @@ -10002,17 +8189,6 @@ def format(q) q.format(statement) q.text(")") if parentheses end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("not") - - q.breakable - q.pp(statement) - - q.pp(Comment::List.new(comments)) - end - end end # Unary represents a unary method being called on an expression, as in +!+ or @@ -10060,20 +8236,6 @@ def format(q) q.text(operator) q.format(statement) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("unary") - - q.breakable - q.pp(operator) - - q.breakable - q.pp(statement) - - q.pp(Comment::List.new(comments)) - end - end end # Undef represents the use of the +undef+ keyword. @@ -10139,17 +8301,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("undef") - - q.breakable - q.group(2, "(", ")") { q.seplist(symbols) { |symbol| q.pp(symbol) } } - - q.pp(Comment::List.new(comments)) - end - end end # Unless represents the first clause in an +unless+ chain. @@ -10207,25 +8358,6 @@ def deconstruct_keys(keys) def format(q) ConditionalFormatter.new("unless", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("unless") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end end # UnlessMod represents the modifier form of an +unless+ statement. @@ -10271,20 +8403,6 @@ def deconstruct_keys(keys) def format(q) ConditionalModFormatter.new("unless", self).format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("unless_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(predicate) - - q.pp(Comment::List.new(comments)) - end - end end # Formats an Until, UntilMod, While, or WhileMod node. @@ -10391,20 +8509,6 @@ def format(q) LoopFormatter.new("until", self, statements).format(q) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("until") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # UntilMod represents the modifier form of a +until+ loop. @@ -10470,20 +8574,6 @@ def format(q) LoopFormatter.new("until", self, statement).format(q) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("until_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(predicate) - - q.pp(Comment::List.new(comments)) - end - end end # VarAlias represents when you're using the +alias+ keyword with global @@ -10530,20 +8620,6 @@ def format(q) q.text(" ") q.format(right) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("var_alias") - - q.breakable - q.pp(left) - - q.breakable - q.pp(right) - - q.pp(Comment::List.new(comments)) - end - end end # VarField represents a variable that is being assigned a value. As such, it @@ -10582,17 +8658,6 @@ def deconstruct_keys(keys) def format(q) q.format(value) if value end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("var_field") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # VarRef represents a variable reference. @@ -10633,17 +8698,6 @@ def deconstruct_keys(keys) def format(q) q.format(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("var_ref") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # PinnedVarRef represents a pinned variable reference within a pattern @@ -10688,17 +8742,6 @@ def format(q) q.format(value) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("pinned_var_ref") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # VCall represent any plain named object with Ruby that could be either a @@ -10736,17 +8779,6 @@ def deconstruct_keys(keys) def format(q) q.format(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("vcall") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # VoidStmt represents an empty lexical block of code. @@ -10781,13 +8813,6 @@ def deconstruct_keys(keys) def format(q) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("void_stmt") - q.pp(Comment::List.new(comments)) - end - end end # When represents a +when+ clause in a +case+ chain. @@ -10880,25 +8905,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("when") - - q.breakable - q.pp(arguments) - - q.breakable - q.pp(statements) - - if consequent - q.breakable - q.pp(consequent) - end - - q.pp(Comment::List.new(comments)) - end - end end # While represents a +while+ loop. @@ -10956,20 +8962,6 @@ def format(q) LoopFormatter.new("while", self, statements).format(q) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("while") - - q.breakable - q.pp(predicate) - - q.breakable - q.pp(statements) - - q.pp(Comment::List.new(comments)) - end - end end # WhileMod represents the modifier form of a +while+ loop. @@ -11035,20 +9027,6 @@ def format(q) LoopFormatter.new("while", self, statement).format(q) end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("while_mod") - - q.breakable - q.pp(statement) - - q.breakable - q.pp(predicate) - - q.pp(Comment::List.new(comments)) - end - end end # Word represents an element within a special array literal that accepts @@ -11093,17 +9071,6 @@ def deconstruct_keys(keys) def format(q) q.format_each(parts) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("word") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end end # Words represents a string literal array with interpolation. @@ -11164,17 +9131,6 @@ def format(q) q.breakable("") end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("words") - - q.breakable - q.group(2, "(", ")") { q.seplist(elements) { |element| q.pp(element) } } - - q.pp(Comment::List.new(comments)) - end - end end # WordsBeg represents the beginning of a string literal array with @@ -11207,15 +9163,6 @@ def child_nodes def deconstruct_keys(keys) { value: value, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("words_beg") - - q.breakable - q.pp(value) - end - end end # XString represents the contents of an XStringLiteral. @@ -11245,15 +9192,6 @@ def child_nodes def deconstruct_keys(keys) { parts: parts, location: location } end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("xstring") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - end - end end # XStringLiteral represents a string that gets executed. @@ -11293,17 +9231,6 @@ def format(q) q.format_each(parts) q.text("`") end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("xstring_literal") - - q.breakable - q.group(2, "(", ")") { q.seplist(parts) { |part| q.pp(part) } } - - q.pp(Comment::List.new(comments)) - end - end end # Yield represents using the +yield+ keyword with arguments. @@ -11354,17 +9281,6 @@ def format(q) end end end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("yield") - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end end # Yield0 represents the bare +yield+ keyword with no arguments. @@ -11401,17 +9317,6 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("yield0") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end # ZSuper represents the bare +super+ keyword with no arguments. @@ -11448,16 +9353,5 @@ def deconstruct_keys(keys) def format(q) q.text(value) end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("zsuper") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end end end diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index 0ea66fb6..63227d03 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -123,7 +123,7 @@ def visit_child_nodes(node) alias visit_binary visit_child_nodes # Visit a BlockArg node. - alias visit_block_arg visit_child_nodes + alias visit_blockarg visit_child_nodes # Visit a BlockVar node. alias visit_block_var visit_child_nodes @@ -333,7 +333,7 @@ def visit_child_nodes(node) alias visit_op visit_child_nodes # Visit an OpAssign node. - alias visit_op_assign visit_child_nodes + alias visit_opassign visit_child_nodes # Visit a Params node. alias visit_params visit_child_nodes @@ -461,6 +461,12 @@ def visit_child_nodes(node) # Visit a TLamBeg node. alias visit_tlambeg visit_child_nodes + # Visit a TopConstField node. + alias visit_top_const_field visit_child_nodes + + # Visit a TopConstRef node. + alias visit_top_const_ref visit_child_nodes + # Visit a TStringBeg node. alias visit_tstring_beg visit_child_nodes @@ -470,12 +476,6 @@ def visit_child_nodes(node) # Visit a TStringEnd node. alias visit_tstring_end visit_child_nodes - # Visit a TopConstField node. - alias visit_top_const_field visit_child_nodes - - # Visit a TopConstRef node. - alias visit_top_const_ref visit_child_nodes - # Visit an Unary node. alias visit_unary visit_child_nodes diff --git a/lib/syntax_tree/visitor/json_visitor.rb b/lib/syntax_tree/visitor/json_visitor.rb new file mode 100644 index 00000000..9f0c8f94 --- /dev/null +++ b/lib/syntax_tree/visitor/json_visitor.rb @@ -0,0 +1,1335 @@ + # frozen_string_literal: true + +module SyntaxTree + class Visitor + class JSONVisitor < Visitor + def visit_aref(node) + { + type: :aref, + collection: visit(node.collection), + index: visit(node.index), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_aref_field(node) + { + type: :aref_field, + collection: visit(node.collection), + index: visit(node.index), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_alias(node) + { + type: :alias, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_block(node) + { + type: :arg_block, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_paren(node) + { + type: :arg_paren, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_arg_star(node) + { + type: :arg_star, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_args(node) + { + type: :args, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_args_forward(node) + visit_token(:args_forward, node) + end + + def visit_array(node) + { + type: :array, + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_aryptn(node) + { + type: :aryptn, + constant: visit(node.constant), + reqs: visit_all(node.requireds), + rest: visit(node.rest), + posts: visit_all(node.posts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assign(node) + { + type: :assign, + target: visit(node.target), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assoc(node) + { + type: :assoc, + key: visit(node.key), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_assoc_splat(node) + { + type: :assoc_splat, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_backref(node) + visit_token(:backref, node) + end + + def visit_backtick(node) + visit_token(:backtick, node) + end + + def visit_bare_assoc_hash(node) + { + type: :bare_assoc_hash, + assocs: visit_all(node.assocs), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_BEGIN(node) + { + type: :BEGIN, + lbrace: visit(node.lbrace), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_begin(node) + { + type: :begin, + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_binary(node) + { + type: :binary, + left: visit(node.left), + op: node.operator, + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_blockarg(node) + { + type: :blockarg, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_block_var(node) + { + type: :block_var, + params: visit(node.params), + locals: visit_all(node.locals), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_bodystmt(node) + { + type: :bodystmt, + stmts: visit(node.statements), + rsc: visit(node.rescue_clause), + els: visit(node.else_clause), + ens: visit(node.ensure_clause), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_brace_block(node) + { + type: :brace_block, + lbrace: visit(node.lbrace), + block_var: visit(node.block_var), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_break(node) + { + type: :break, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_call(node) + { + type: :call, + receiver: visit(node.receiver), + op: visit_call_operator(node.operator), + message: node.message == :call ? :call : visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_case(node) + { + type: :case, + value: visit(node.value), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_CHAR(node) + visit_token(:CHAR, node) + end + + def visit_class(node) + { + type: :class, + constant: visit(node.constant), + superclass: visit(node.superclass), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_comma(node) + visit_token(:comma, node) + end + + def visit_command(node) + { + type: :command, + message: visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_command_call(node) + { + type: :command_call, + receiver: visit(node.receiver), + op: visit_call_operator(node.operator), + message: visit(node.message), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_comment(node) + { + type: :comment, + value: node.value, + inline: node.inline, + loc: visit_location(node.location) + } + end + + def visit_const(node) + visit_token(:const, node) + end + + def visit_const_path_field(node) + { + type: :const_path_field, + parent: visit(node.parent), + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_const_path_ref(node) + { + type: :const_path_ref, + parent: visit(node.parent), + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_const_ref(node) + { + type: :const_ref, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_cvar(node) + visit_token(:cvar, node) + end + + def visit_def(node) + { + type: :def, + name: visit(node.name), + params: visit(node.params), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_def_endless(node) + { + type: :def_endless, + name: visit(node.name), + paren: visit(node.paren), + stmt: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_defined(node) + visit_token(:defined, node) + end + + def visit_defs(node) + { + type: :defs, + target: visit(node.target), + op: visit(node.operator), + name: visit(node.name), + params: visit(node.params), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_do_block(node) + { + type: :do_block, + keyword: visit(node.keyword), + block_var: visit(node.block_var), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dot2(node) + { + type: :dot2, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dot3(node) + { + type: :dot3, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_dyna_symbol(node) + { + type: :dyna_symbol, + parts: visit_all(node.parts), + quote: node.quote, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_END(node) + { + type: :END, + lbrace: visit(node.lbrace), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_else(node) + { + type: :else, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_elsif(node) + { + type: :elsif, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_embdoc(node) + { + type: :embdoc, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embexpr_beg(node) + { + type: :embexpr_beg, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embexpr_end(node) + { + type: :embexpr_end, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_embvar(node) + { + type: :embvar, + value: node.value, + loc: visit_location(node.location) + } + end + + def visit_ensure(node) + { + type: :ensure, + keyword: visit(node.keyword), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_excessed_comma(node) + visit_token(:excessed_comma, node) + end + + def visit_fcall(node) + { + type: :fcall, + value: visit(node.value), + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_field(node) + { + type: :field, + parent: visit(node.parent), + op: visit_call_operator(node.operator), + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_float(node) + visit_token(:float, node) + end + + def visit_fndptn(node) + { + type: :fndptn, + constant: visit(node.constant), + left: visit(node.left), + values: visit_all(node.values), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_for(node) + { + type: :for, + index: visit(node.index), + collection: visit(node.collection), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_gvar(node) + visit_token(:gvar, node) + end + + def visit_hash(node) + { + type: :hash, + assocs: visit_all(node.assocs), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_heredoc(node) + { + type: :heredoc, + beging: visit(node.beginning), + ending: node.ending, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_heredoc_beg(node) + visit_token(:heredoc_beg, node) + end + + def visit_hshptn(node) + { + type: :hshptn, + constant: visit(node.constant), + keywords: node.keywords.map { |(name, value)| [visit(name), visit(value)] }, + kwrest: visit(node.keyword_rest), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_ident(node) + visit_token(:ident, node) + end + + def visit_if(node) + { + type: :if, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_if_mod(node) + { + type: :if_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_if_op(node) + { + type: :ifop, + pred: visit(node.predicate), + tthy: visit(node.truthy), + flsy: visit(node.falsy), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_imaginary(node) + visit_token(:imaginary, node) + end + + def visit_in(node) + { + type: :in, + pattern: visit(node.pattern), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_int(node) + visit_token(:int, node) + end + + def visit_ivar(node) + visit_token(:ivar, node) + end + + def visit_kw(node) + visit_token(:kw, node) + end + + def visit_kwrest_param(node) + { + type: :kwrest_param, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_label(node) + visit_token(:label, node) + end + + def visit_label_end(node) + visit_token(:label_end, node) + end + + def visit_lambda(node) + { + type: :lambda, + params: visit(node.params), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_lbrace(node) + visit_token(:lbrace, node) + end + + def visit_lbracket(node) + visit_token(:lbracket, node) + end + + def visit_lparen(node) + visit_token(:lparen, node) + end + + def visit_massign(node) + { + type: :massign, + target: visit(node.target), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_method_add_block(node) + { + type: :method_add_block, + call: visit(node.call), + block: visit(node.block), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mlhs(node) + { + type: :mlhs, + parts: visit_all(node.parts), + comma: node.comma, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mlhs_paren(node) + { + type: :mlhs_paren, + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_module(node) + { + type: :module, + constant: visit(node.constant), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_mrhs(node) + { + type: :mrhs, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_next(node) + { + type: :next, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_not(node) + { + type: :not, + value: visit(node.statement), + paren: node.parentheses, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_op(node) + visit_token(:op, node) + end + + def visit_opassign(node) + { + type: :opassign, + target: visit(node.target), + op: visit(node.operator), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_params(node) + { + type: :params, + reqs: visit_all(node.requireds), + opts: node.optionals.map { |(name, value)| [visit(name), visit(value)] }, + rest: visit(node.rest), + posts: visit_all(node.posts), + keywords: node.keywords.map { |(name, value)| [visit(name), visit(value || nil)] }, + kwrest: node.keyword_rest == :nil ? "nil" : visit(node.keyword_rest), + block: visit(node.block), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_paren(node) + { + type: :paren, + lparen: visit(node.lparen), + cnts: visit(node.contents), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_period(node) + visit_token(:period, node) + end + + def visit_pinned_begin(node) + { + type: :pinned_begin, + stmt: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_pinned_var_ref(node) + { + type: :pinned_var_ref, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_program(node) + { + type: :program, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qsymbols(node) + { + type: :qsymbols, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qsymbols_beg(node) + visit_token(:qsymbols_beg, node) + end + + def visit_qwords(node) + { + type: :qwords, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_qwords_beg(node) + visit_token(:qwords_beg, node) + end + + def visit_rassign(node) + { + type: :rassign, + value: visit(node.value), + op: visit(node.operator), + pattern: visit(node.pattern), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rational(node) + visit_token(:rational, node) + end + + def visit_rbrace(node) + visit_token(:rbrace, node) + end + + def visit_rbracket(node) + visit_token(:rbracket, node) + end + + def visit_redo(node) + visit_token(:redo, node) + end + + def visit_regexp_beg(node) + visit_token(:regexp_beg, node) + end + + def visit_regexp_content(node) + { + type: :regexp_content, + beging: node.beginning, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_regexp_end(node) + visit_token(:regexp_end, node) + end + + def visit_regexp_literal(node) + { + type: :regexp_literal, + beging: node.beginning, + ending: node.ending, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue(node) + { + type: :rescue, + extn: visit(node.exception), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue_ex(node) + { + type: :rescue_ex, + extns: visit(node.exceptions), + var: visit(node.variable), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rescue_mod(node) + { + type: :rescue_mod, + stmt: visit(node.statement), + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_rest_param(node) + { + type: :rest_param, + name: visit(node.name), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_retry(node) + visit_token(:retry, node) + end + + def visit_return(node) + { + type: :return, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_return0(node) + visit_token(:return0, node) + end + + def visit_rparen(node) + visit_token(:rparen, node) + end + + def visit_sclass(node) + { + type: :sclass, + target: visit(node.target), + bodystmt: visit(node.bodystmt), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_statements(node) + { + type: :statements, + body: visit_all(node.body), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_concat(node) + { + type: :string_concat, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_content(node) + { + type: :string_content, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_string_dvar(node) + { + type: :string_dvar, + var: visit(node.variable), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_embexpr(node) + { + type: :string_embexpr, + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_string_literal(node) + { + type: :string_literal, + parts: visit_all(node.parts), + quote: node.quote, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_super(node) + { + type: :super, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbeg(node) + visit_token(:symbeg, node) + end + + def visit_symbol_content(node) + { + type: :symbol_content, + value: visit(node.value), + loc: visit_location(node.location) + } + end + + def visit_symbol_literal(node) + { + type: :symbol_literal, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbols(node) + { + type: :symbols, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_symbols_beg(node) + visit_token(:symbols_beg, node) + end + + def visit_tlambda(node) + visit_token(:tlambda, node) + end + + def visit_tlambeg(node) + visit_token(:tlambeg, node) + end + + def visit_top_const_field(node) + { + type: :top_const_field, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_top_const_ref(node) + { + type: :top_const_ref, + constant: visit(node.constant), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_tstring_beg(node) + visit_token(:tstring_beg, node) + end + + def visit_tstring_content(node) + visit_token(:tstring_content, node) + end + + def visit_tstring_end(node) + visit_token(:tstring_end, node) + end + + def visit_unary(node) + { + type: :unary, + op: node.operator, + value: visit(node.statement), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_undef(node) + { + type: :undef, + syms: visit_all(node.symbols), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_unless(node) + { + type: :unless, + pred: visit(node.predicate), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_unless_mod(node) + { + type: :unless_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_until(node) + { + type: :until, + pred: visit(node.predicate), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_until_mod(node) + { + type: :until_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_alias(node) + { + type: :var_alias, + left: visit(node.left), + right: visit(node.right), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_field(node) + { + type: :var_field, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_var_ref(node) + { + type: :var_ref, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_vcall(node) + { + type: :vcall, + value: visit(node.value), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_void_stmt(node) + { + type: :void_stmt, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_when(node) + { + type: :when, + args: visit(node.arguments), + stmts: visit(node.statements), + cons: visit(node.consequent), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_while(node) + { + type: :while, + pred: visit(node.predicate), + stmts: visit(node.statements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_while_mod(node) + { + type: :while_mod, + stmt: visit(node.statement), + pred: visit(node.predicate), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_word(node) + { + type: :word, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_words(node) + { + type: :words, + elems: visit_all(node.elements), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_words_beg(node) + visit_token(:words_beg, node) + end + + def visit_xstring(node) + { + type: :xstring, + parts: visit_all(node.parts), + loc: visit_location(node.location) + } + end + + def visit_xstring_literal(node) + { + type: :xstring_literal, + parts: visit_all(node.parts), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_yield(node) + { + type: :yield, + args: visit(node.arguments), + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + + def visit_yield0(node) + visit_token(:yield0, node) + end + + def visit_zsuper(node) + visit_token(:zsuper, node) + end + + def visit___end__(node) + visit_token(:__end__, node) + end + + private + + def visit_call_operator(operator) + operator == :"::" ? :"::" : visit(operator) + end + + def visit_location(location) + [ + location.start_line, + location.start_char, + location.end_line, + location.end_char + ] + end + + def visit_token(type, node) + { + type: type, + value: node.value, + loc: visit_location(node.location), + cmts: visit_all(node.comments) + } + end + end + end +end diff --git a/lib/syntax_tree/visitor/pretty_print_visitor.rb b/lib/syntax_tree/visitor/pretty_print_visitor.rb new file mode 100644 index 00000000..40420a15 --- /dev/null +++ b/lib/syntax_tree/visitor/pretty_print_visitor.rb @@ -0,0 +1,1213 @@ + # frozen_string_literal: true + +module SyntaxTree + class Visitor + class PrettyPrintVisitor < Visitor + attr_reader :q + + def initialize(q) + @q = q + end + + def visit_aref(node) + node("aref") do + field("collection", node.collection) + field("index", node.index) + comments(node) + end + end + + def visit_aref_field(node) + node("aref_field") do + field("collection", node.collection) + field("index", node.index) + comments(node) + end + end + + def visit_alias(node) + node("alias") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_arg_block(node) + node("arg_block") do + field("value", node.value) if node.value + comments(node) + end + end + + def visit_arg_paren(node) + node("arg_paren") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_arg_star(node) + node("arg_star") do + field("value", node.value) + comments(node) + end + end + + def visit_args(node) + node("args") do + list("parts", node.parts) + comments(node) + end + end + + def visit_args_forward(node) + visit_token("args_forward", node) + end + + def visit_array(node) + node("array") do + field("contents", node.contents) + comments(node) + end + end + + def visit_aryptn(node) + node("aryptn") do + field("constant", node.constant) if node.constant + list("requireds", node.requireds) if node.requireds.any? + field("rest", node.rest) if node.rest + list("posts", node.posts) if node.posts.any? + comments(node) + end + end + + def visit_assign(node) + node("assign") do + field("target", node.target) + field("value", node.value) + comments(node) + end + end + + def visit_assoc(node) + node("assoc") do + field("key", node.key) + field("value", node.value) if node.value + comments(node) + end + end + + def visit_assoc_splat(node) + node("assoc_splat") do + field("value", node.value) + comments(node) + end + end + + def visit_backref(node) + visit_token("backref", node) + end + + def visit_backtick(node) + visit_token("backtick", node) + end + + def visit_bare_assoc_hash(node) + node("bare_assoc_hash") do + list("assocs", node.assocs) + comments(node) + end + end + + def visit_BEGIN(node) + node("BEGIN") do + field("statements", node.statements) + comments(node) + end + end + + def visit_begin(node) + node("begin") do + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_binary(node) + node("binary") do + field("left", node.left) + text("operator", node.operator) + field("right", node.right) + comments(node) + end + end + + def visit_blockarg(node) + node("blockarg") do + field("name", node.name) if node.name + comments(node) + end + end + + def visit_block_var(node) + node("block_var") do + field("params", node.params) + list("locals", node.locals) if node.locals.any? + comments(node) + end + end + + def visit_bodystmt(node) + node("bodystmt") do + field("statements", node.statements) + field("rescue_clause", node.rescue_clause) if node.rescue_clause + field("else_clause", node.else_clause) if node.else_clause + field("ensure_clause", node.ensure_clause) if node.ensure_clause + comments(node) + end + end + + def visit_brace_block(node) + node("brace_block") do + field("block_var", node.block_var) if node.block_var + field("statements", node.statements) + comments(node) + end + end + + def visit_break(node) + node("break") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_call(node) + node("call") do + field("receiver", node.receiver) + field("operator", node.operator) + field("message", node.message) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_case(node) + node("case") do + field("keyword", node.keyword) + field("value", node.value) if node.value + field("consequent", node.consequent) + comments(node) + end + end + + def visit_CHAR(node) + visit_token("CHAR", node) + end + + def visit_class(node) + node("class") do + field("constant", node.constant) + field("superclass", node.superclass) if node.superclass + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_comma(node) + node("comma") do + field("value", node) + end + end + + def visit_command(node) + node("command") do + field("message", node.message) + field("arguments", node.arguments) + comments(node) + end + end + + def visit_command_call(node) + node("command_call") do + field("receiver", node.receiver) + field("operator", node.operator) + field("message", node.message) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_comment(node) + node("comment") do + field("value", node.value) + end + end + + def visit_const(node) + visit_token("const", node) + end + + def visit_const_path_field(node) + node("const_path_field") do + field("parent", node.parent) + field("constant", node.constant) + comments(node) + end + end + + def visit_const_path_ref(node) + node("const_path_ref") do + field("parent", node.parent) + field("constant", node.constant) + comments(node) + end + end + + def visit_const_ref(node) + node("const_ref") do + field("constant", node.constant) + comments(node) + end + end + + def visit_cvar(node) + visit_token("cvar", node) + end + + def visit_def(node) + node("def") do + field("name", node.name) + field("params", node.params) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_def_endless(node) + node("def_endless") do + if node.target + field("target", node.target) + field("operator", node.operator) + end + + field("name", node.name) + field("paren", node.paren) if node.paren + field("statement", node.statement) + comments(node) + end + end + + def visit_defined(node) + node("defined") do + field("value", node.value) + comments(node) + end + end + + def visit_defs(node) + node("defs") do + field("target", node.target) + field("operator", node.operator) + field("name", node.name) + field("params", node.params) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_do_block(node) + node("do_block") do + field("block_var", node.block_var) if node.block_var + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_dot2(node) + node("dot2") do + field("left", node.left) if node.left + field("right", node.right) if node.right + comments(node) + end + end + + def visit_dot3(node) + node("dot3") do + field("left", node.left) if node.left + field("right", node.right) if node.right + comments(node) + end + end + + def visit_dyna_symbol(node) + node("dyna_symbol") do + list("parts", node.parts) + comments(node) + end + end + + def visit_END(node) + node("END") do + field("statements", node.statements) + comments(node) + end + end + + def visit_else(node) + node("else") do + field("statements", node.statements) + comments(node) + end + end + + def visit_elsif(node) + node("elsif") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_embdoc(node) + node("embdoc") do + field("value", node.value) + end + end + + def visit_embexpr_beg(node) + node("embexpr_beg") do + field("value", node.value) + end + end + + def visit_embexpr_end(node) + node("embexpr_end") do + field("value", node.value) + end + end + + def visit_embvar(node) + node("embvar") do + field("value", node.value) + end + end + + def visit_ensure(node) + node("ensure") do + field("statements", node.statements) + comments(node) + end + end + + def visit_excessed_comma(node) + visit_token("excessed_comma", node) + end + + def visit_fcall(node) + node("fcall") do + field("value", node.value) + field("arguments", node.arguments) if node.arguments + comments(node) + end + end + + def visit_field(node) + node("field") do + field("parent", node.parent) + field("operator", node.operator) + field("name", node.name) + comments(node) + end + end + + def visit_float(node) + visit_token("float", node) + end + + def visit_fndptn(node) + node("fndptn") do + field("constant", node.constant) if node.constant + field("left", node.left) + list("values", node.values) + field("right", node.right) + comments(node) + end + end + + def visit_for(node) + node("for") do + field("index", node.index) + field("collection", node.collection) + field("statements", node.statements) + comments(node) + end + end + + def visit_gvar(node) + visit_token("gvar", node) + end + + def visit_hash(node) + node("hash") do + list("assocs", node.assocs) if node.assocs.any? + comments(node) + end + end + + def visit_heredoc(node) + node("heredoc") do + list("parts", node.parts) + comments(node) + end + end + + def visit_heredoc_beg(node) + visit_token("heredoc_beg", node) + end + + def visit_hshptn(node) + node("hshptn") do + field("constant", node.constant) if node.constant + + if node.keywords.any? + q.breakable + q.group(2, "(", ")") do + q.seplist(node.keywords) do |(key, value)| + q.group(2, "(", ")") do + key.pretty_print(q) + + if value + q.breakable + value.pretty_print(q) + end + end + end + end + end + + field("keyword_rest", node.keyword_rest) if node.keyword_rest + comments(node) + end + end + + def visit_ident(node) + visit_token("ident", node) + end + + def visit_if(node) + node("if") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_if_mod(node) + node("if_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_if_op(node) + node("ifop") do + field("predicate", node.predicate) + field("truthy", node.truthy) + field("falsy", node.falsy) + comments(node) + end + end + + def visit_imaginary(node) + visit_token("imaginary", node) + end + + def visit_in(node) + node("in") do + field("pattern", node.pattern) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_int(node) + visit_token("int", node) + end + + def visit_ivar(node) + visit_token("ivar", node) + end + + def visit_kw(node) + visit_token("kw", node) + end + + def visit_kwrest_param(node) + node("kwrest_param") do + field("name", node.name) + comments(node) + end + end + + def visit_label(node) + node("label") do + q.breakable + q.text(":") + q.text(node.value[0...-1]) + comments(node) + end + end + + def visit_label_end(node) + node("label_end") do + field("value", node.value) + end + end + + def visit_lambda(node) + node("lambda") do + field("params", node.params) + field("statements", node.statements) + comments(node) + end + end + + def visit_lbrace(node) + visit_token("lbrace", node) + end + + def visit_lbracket(node) + visit_token("lbracket", node) + end + + def visit_lparen(node) + visit_token("lparen", node) + end + + def visit_massign(node) + node("massign") do + field("target", node.target) + field("value", node.value) + comments(node) + end + end + + def visit_method_add_block(node) + node("method_add_block") do + field("call", node.call) + field("block", node.block) + comments(node) + end + end + + def visit_mlhs(node) + node("mlhs") do + list("parts", node.parts) + comments(node) + end + end + + def visit_mlhs_paren(node) + node("mlhs_paren") do + field("contents", node.contents) + comments(node) + end + end + + def visit_module(node) + node("module") do + field("constant", node.constant) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_mrhs(node) + node("mrhs") do + list("parts", node.parts) + comments(node) + end + end + + def visit_next(node) + node("next") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_not(node) + node("not") do + field("statement", node.statement) + comments(node) + end + end + + def visit_op(node) + visit_token("op", node) + end + + def visit_opassign(node) + node("opassign") do + field("target", node.target) + field("operator", node.operator) + field("value", node.value) + comments(node) + end + end + + def visit_params(node) + node("params") do + list("requireds", node.requireds) if node.requireds.any? + + if node.optionals.any? + q.breakable + q.group(2, "(", ")") do + q.seplist(node.optionals) do |(name, default)| + name.pretty_print(q) + q.text("=") + q.group(2) do + q.breakable("") + default.pretty_print(q) + end + end + end + end + + field("rest", node.rest) if node.rest + list("posts", node.posts) if node.posts.any? + + if node.keywords.any? + q.breakable + q.group(2, "(", ")") do + q.seplist(node.keywords) do |(name, default)| + name.pretty_print(q) + + if default + q.text("=") + q.group(2) do + q.breakable("") + default.pretty_print(q) + end + end + end + end + end + + field("keyword_rest", node.keyword_rest) if node.keyword_rest + field("block", node.block) if node.block + comments(node) + end + end + + def visit_paren(node) + node("paren") do + field("contents", node.contents) + comments(node) + end + end + + def visit_period(node) + visit_token("period", node) + end + + def visit_pinned_begin(node) + node("pinned_begin") do + field("statement", node.statement) + comments(node) + end + end + + def visit_pinned_var_ref(node) + node("pinned_var_ref") do + field("value", node.value) + comments(node) + end + end + + def visit_program(node) + node("program") do + field("statements", node.statements) + comments(node) + end + end + + def visit_qsymbols(node) + node("qsymbols") do + list("elements", node.elements) + comments(node) + end + end + + def visit_qsymbols_beg(node) + node("qsymbols_beg") do + field("value", node.value) + end + end + + def visit_qwords(node) + node("qwords") do + list("elements", node.elements) + comments(node) + end + end + + def visit_qwords_beg(node) + node("qwords_beg") do + field("value", node.value) + end + end + + def visit_rassign(node) + node("rassign") do + field("value", node.value) + field("operator", node.operator) + field("pattern", node.pattern) + comments(node) + end + end + + def visit_rational(node) + visit_token("rational", node) + end + + def visit_rbrace(node) + node("rbrace") do + field("value", node.value) + end + end + + def visit_rbracket(node) + node("rbracket") do + field("value", node.value) + end + end + + def visit_redo(node) + visit_token("redo", node) + end + + def visit_regexp_beg(node) + node("regexp_beg") do + field("value", node.value) + end + end + + def visit_regexp_content(node) + node("regexp_content") do + list("parts", node.parts) + end + end + + def visit_regexp_end(node) + node("regexp_end") do + field("value", node.value) + end + end + + def visit_regexp_literal(node) + node("regexp_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_rescue(node) + node("rescue") do + field("exception", node.exception) if node.exception + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_rescue_ex(node) + node("rescue_ex") do + field("exceptions", node.exceptions) + field("variable", node.variable) + comments(node) + end + end + + def visit_rescue_mod(node) + node("rescue_mod") do + field("statement", node.statement) + field("value", node.value) + comments(node) + end + end + + def visit_rest_param(node) + node("rest_param") do + field("name", node.name) + comments(node) + end + end + + def visit_retry(node) + visit_token("retry", node) + end + + def visit_return(node) + node("return") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_return0(node) + visit_token("return0", node) + end + + def visit_rparen(node) + node("rparen") do + field("value", node.value) + end + end + + def visit_sclass(node) + node("sclass") do + field("target", node.target) + field("bodystmt", node.bodystmt) + comments(node) + end + end + + def visit_statements(node) + node("statements") do + list("body", node.body) + comments(node) + end + end + + def visit_string_concat(node) + node("string_concat") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_string_content(node) + node("string_content") do + list("parts", node.parts) + end + end + + def visit_string_dvar(node) + node("string_dvar") do + field("variable", node.variable) + comments(node) + end + end + + def visit_string_embexpr(node) + node("string_embexpr") do + field("statements", node.statements) + comments(node) + end + end + + def visit_string_literal(node) + node("string_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_super(node) + node("super") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_symbeg(node) + node("symbeg") do + field("value", node.value) + end + end + + def visit_symbol_content(node) + node("symbol_content") do + field("value", node.value) + end + end + + def visit_symbol_literal(node) + node("symbol_literal") do + field("value", node.value) + comments(node) + end + end + + def visit_symbols(node) + node("symbols") do + list("elements", node.elements) + comments(node) + end + end + + def visit_symbols_beg(node) + node("symbols_beg") do + field("value", node.value) + end + end + + def visit_tlambda(node) + node("tlambda") do + field("value", node.value) + end + end + + def visit_tlambeg(node) + node("tlambeg") do + field("value", node.value) + end + end + + def visit_top_const_field(node) + node("top_const_field") do + field("constant", node.constant) + comments(node) + end + end + + def visit_top_const_ref(node) + node("top_const_ref") do + field("constant", node.constant) + comments(node) + end + end + + def visit_tstring_beg(node) + node("tstring_beg") do + field("value", node.value) + end + end + + def visit_tstring_content(node) + visit_token("tstring_content", node) + end + + def visit_tstring_end(node) + node("tstring_end") do + field("value", node.value) + end + end + + def visit_unary(node) + node("unary") do + field("operator", node.operator) + field("statement", node.statement) + comments(node) + end + end + + def visit_undef(node) + node("undef") do + list("symbols", node.symbols) + comments(node) + end + end + + def visit_unless(node) + node("unless") do + field("predicate", node.predicate) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_unless_mod(node) + node("unless_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_until(node) + node("until") do + field("predicate", node.predicate) + field("statements", node.statements) + comments(node) + end + end + + def visit_until_mod(node) + node("until_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_var_alias(node) + node("var_alias") do + field("left", node.left) + field("right", node.right) + comments(node) + end + end + + def visit_var_field(node) + node("var_field") do + field("value", node.value) + comments(node) + end + end + + def visit_var_ref(node) + node("var_ref") do + field("value", node.value) + comments(node) + end + end + + def visit_vcall(node) + node("vcall") do + field("value", node.value) + comments(node) + end + end + + def visit_void_stmt(node) + node("void_stmt") do + comments(node) + end + end + + def visit_when(node) + node("when") do + field("arguments", node.arguments) + field("statements", node.statements) + field("consequent", node.consequent) if node.consequent + comments(node) + end + end + + def visit_while(node) + node("while") do + field("predicate", node.predicate) + field("statements", node.statements) + comments(node) + end + end + + def visit_while_mod(node) + node("while_mod") do + field("statement", node.statement) + field("predicate", node.predicate) + comments(node) + end + end + + def visit_word(node) + node("word") do + list("parts", node.parts) + comments(node) + end + end + + def visit_words(node) + node("words") do + list("elements", node.elements) + comments(node) + end + end + + def visit_words_beg(node) + node("words_beg") do + field("value", node.value) + end + end + + def visit_xstring(node) + node("xstring") do + list("parts", node.parts) + end + end + + def visit_xstring_literal(node) + node("xstring_literal") do + list("parts", node.parts) + comments(node) + end + end + + def visit_yield(node) + node("yield") do + field("arguments", node.arguments) + comments(node) + end + end + + def visit_yield0(node) + visit_token("yield0", node) + end + + def visit_zsuper(node) + visit_token("zsuper", node) + end + + def visit___end__(node) + visit_token("__end__", node) + end + + private + + def comments(node) + return if node.comments.empty? + + q.breakable + q.group(2, "(", ")") do + q.seplist(node.comments) { |comment| comment.pretty_print(q) } + end + end + + def field(_name, value) + q.breakable + + # I don't entirely know why this is necessary, but in Ruby 2.7 there is + # an issue with calling q.pp on strings that somehow involves inspect + # keys. I'm purposefully avoiding the inspect key stuff here because I + # know the tree does not contain any cycles. + value.is_a?(String) ? q.text(value.inspect) : value.pretty_print(q) + end + + def list(_name, values) + q.breakable + q.group(2, "(", ")") do + q.seplist(values) { |value| value.pretty_print(q) } + end + end + + def node(type) + q.group(2, "(", ")") do + q.text(type) + yield + end + end + + def text(_name, value) + q.breakable + q.text(value) + end + + def visit_token(type, node) + node(type) do + field("value", node.value) + comments(node) + end + end + end + end +end diff --git a/test/pretty_print_visitor_test.rb b/test/pretty_print_visitor_test.rb new file mode 100644 index 00000000..8ca7cdf8 --- /dev/null +++ b/test/pretty_print_visitor_test.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SyntaxTree + class PrettyPrintVisitorTest < Minitest::Test + Fixtures.each_fixture do |fixture| + define_method(:"test_pretty_print_#{fixture.name}") do + formatter = PP.new([]) + + program = SyntaxTree.parse(fixture.source) + program.pretty_print(formatter) + + formatter.flush + refute_includes(formatter.output.join, "#<") + end + end + end +end From c2125304986a5e6706b93b3e13ae061e058c95b1 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Tue, 12 Apr 2022 16:27:09 -0400 Subject: [PATCH 20/23] Add column position to Location --- lib/syntax_tree/node.rb | 61 ++++--- lib/syntax_tree/parser.rb | 331 ++++++++++++++++++++++++++------------ test/node_test.rb | 19 ++- 3 files changed, 281 insertions(+), 130 deletions(-) diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 9081d977..f940499a 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -3,13 +3,15 @@ module SyntaxTree # Represents the location of a node in the tree from the source code. class Location - attr_reader :start_line, :start_char, :end_line, :end_char + attr_reader :start_line, :start_char, :start_column, :end_line, :end_char, :end_column - def initialize(start_line:, start_char:, end_line:, end_char:) + def initialize(start_line:, start_char:, start_column:, end_line:, end_char:, end_column:) @start_line = start_line @start_char = start_char + @start_column = start_column @end_line = end_line @end_char = end_char + @end_column = end_column end def lines @@ -26,22 +28,26 @@ def to(other) Location.new( start_line: start_line, start_char: start_char, + start_column: start_column, end_line: [end_line, other.end_line].max, - end_char: other.end_char + end_char: other.end_char, + end_column: other.end_column ) end - def self.token(line:, char:, size:) + def self.token(line:, char:, column:, size:) new( start_line: line, start_char: char, + start_column: column, end_line: line, - end_char: char + size + end_char: char + size, + end_column: column + size ) end - def self.fixed(line:, char:) - new(start_line: line, start_char: char, end_line: line, end_char: char) + def self.fixed(line:, char:, column:) + new(start_line: line, start_char: char, start_column: column, end_line: line, end_char: char, end_column: column) end end @@ -2047,13 +2053,15 @@ def initialize( @comments = comments end - def bind(start_char, end_char) + def bind(start_char, start_column, end_char, end_column) @location = Location.new( start_line: location.start_line, start_char: start_char, + start_column: start_column, end_line: location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) parts = [rescue_clause, else_clause, ensure_clause] @@ -2062,14 +2070,17 @@ def bind(start_char, end_char) consequent = parts.compact.first statements.bind( start_char, - consequent ? consequent.location.start_char : end_char + start_column, + consequent ? consequent.location.start_char : end_char, + consequent ? consequent.location.start_column : end_column ) # Next we're going to determine the rescue clause if there is one if rescue_clause consequent = parts.drop(1).compact.first rescue_clause.bind_end( - consequent ? consequent.location.start_char : end_char + consequent ? consequent.location.start_char : end_char, + consequent ? consequent.location.start_column : end_column ) end end @@ -8413,20 +8424,22 @@ def initialize( @comments = comments end - def bind_end(end_char) + def bind_end(end_char, end_column) @location = Location.new( start_line: location.start_line, start_char: location.start_char, + start_column: location.start_column, end_line: location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) if consequent - consequent.bind_end(end_char) - statements.bind_end(consequent.location.start_char) + consequent.bind_end(end_char, end_column) + statements.bind_end(consequent.location.start_char, consequent.location.start_column) else - statements.bind_end(end_char) + statements.bind_end(end_char, end_column) end end @@ -8885,13 +8898,15 @@ def initialize(parser, body:, location:, comments: []) @comments = comments end - def bind(start_char, end_char) + def bind(start_char, start_column, end_char, end_column) @location = Location.new( start_line: location.start_line, start_char: start_char, + start_column: start_column, end_line: location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) if body[0].is_a?(VoidStmt) @@ -8900,8 +8915,10 @@ def bind(start_char, end_char) Location.new( start_line: location.start_line, start_char: start_char, + start_column: start_column, end_line: location.end_line, - end_char: start_char + end_char: start_char, + end_column: end_column ) body[0] = VoidStmt.new(location: location) @@ -8910,13 +8927,15 @@ def bind(start_char, end_char) attach_comments(start_char, end_char) end - def bind_end(end_char) + def bind_end(end_char, end_column) @location = Location.new( start_line: location.start_line, start_char: location.start_char, + start_column: location.start_column, end_line: location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) end diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index 5bd89dc2..6bc27d5c 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -163,6 +163,12 @@ def char_pos line_counts[lineno - 1][column] end + # This represents the current column we're in relative to the beginning of + # the current line. + def current_column + column - line_counts[lineno - 1].start + end + # As we build up a list of tokens, we'll periodically need to go backwards # and find the ones that we've already hit in order to determine the # location information for nodes that use them. For example, if you have a @@ -251,10 +257,13 @@ def find_next_statement_start(position) def on_BEGIN(statements) lbrace = find_token(LBrace) rbrace = find_token(RBrace) + start_char = find_next_statement_start(lbrace.location.end_char) statements.bind( - find_next_statement_start(lbrace.location.end_char), - rbrace.location.start_char + start_char, + start_char - line_counts[lbrace.location.start_line - 1].start, + rbrace.location.start_char, + rbrace.location.start_column, ) keyword = find_token(Kw, "BEGIN") @@ -271,7 +280,7 @@ def on_BEGIN(statements) def on_CHAR(value) CHAR.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -280,10 +289,13 @@ def on_CHAR(value) def on_END(statements) lbrace = find_token(LBrace) rbrace = find_token(RBrace) + start_char = find_next_statement_start(lbrace.location.end_char) statements.bind( - find_next_statement_start(lbrace.location.end_char), - rbrace.location.start_char + start_char, + start_char - line_counts[lbrace.location.start_line - 1].start, + rbrace.location.start_char, + rbrace.location.start_column ) keyword = find_token(Kw, "END") @@ -301,7 +313,7 @@ def on___end__(value) @__end__ = EndContent.new( value: source[(char_pos + value.length)..-1], - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -465,7 +477,7 @@ def on_args_forward # :call-seq: # on_args_new: () -> Args def on_args_new - Args.new(parts: [], location: Location.fixed(line: lineno, char: char_pos)) + Args.new(parts: [], location: Location.fixed(line: lineno, column: current_column, char: char_pos)) end # :call-seq: @@ -551,7 +563,7 @@ def on_assoc_splat(value) def on_backref(value) Backref.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -561,7 +573,7 @@ def on_backtick(value) node = Backtick.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -592,15 +604,20 @@ def on_begin(bodystmt) PinnedBegin.new(statement: bodystmt, location: location) else keyword = find_token(Kw, "begin") - end_char = + end_location = if bodystmt.rescue_clause || bodystmt.ensure_clause || bodystmt.else_clause - bodystmt.location.end_char + bodystmt.location else - find_token(Kw, "end").location.end_char + find_token(Kw, "end").location end - bodystmt.bind(keyword.location.end_char, end_char) + bodystmt.bind( + keyword.location.end_char, + keyword.location.end_column, + end_location.end_char, + end_location.end_column + ) location = keyword.location.to(bodystmt.location) Begin.new(bodystmt: bodystmt, location: location) @@ -685,7 +702,7 @@ def on_bodystmt(statements, rescue_clause, else_clause, ensure_clause) else_keyword: else_clause && find_token(Kw, "else"), else_clause: else_clause, ensure_clause: ensure_clause, - location: Location.fixed(line: lineno, char: char_pos) + location: Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -697,18 +714,24 @@ def on_bodystmt(statements, rescue_clause, else_clause, ensure_clause) def on_brace_block(block_var, statements) lbrace = find_token(LBrace) rbrace = find_token(RBrace) + location = (block_var || lbrace).location + start_char = find_next_statement_start(location.end_char) statements.bind( - find_next_statement_start((block_var || lbrace).location.end_char), - rbrace.location.start_char + start_char, + start_char - line_counts[location.start_line - 1].start, + rbrace.location.start_char, + rbrace.location.start_column ) location = Location.new( start_line: lbrace.location.start_line, start_char: lbrace.location.start_char, + start_column: lbrace.location.start_column, end_line: [rbrace.location.end_line, statements.location.end_line].max, - end_char: rbrace.location.end_char + end_char: rbrace.location.end_char, + end_column: rbrace.location.end_column ) BraceBlock.new( @@ -782,10 +805,14 @@ def on_case(value, consequent) def on_class(constant, superclass, bodystmt) beginning = find_token(Kw, "class") ending = find_token(Kw, "end") + location = (superclass || constant).location + start_char = find_next_statement_start(location.end_char) bodystmt.bind( - find_next_statement_start((superclass || constant).location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) ClassDeclaration.new( @@ -802,7 +829,7 @@ def on_comma(value) node = Comma.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -847,7 +874,7 @@ def on_comment(value) value: value.chomp, inline: value.strip != lines[line - 1].strip, location: - Location.token(line: line, char: char_pos, size: value.size - 1) + Location.token(line: line, char: char_pos, column: current_column, size: value.size - 1) ) @comments << comment @@ -859,7 +886,7 @@ def on_comment(value) def on_const(value) Const.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -894,7 +921,7 @@ def on_const_ref(constant) def on_cvar(value) CVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -918,12 +945,15 @@ def on_def(name, params, bodystmt) # location information if params.is_a?(Params) && params.empty? end_char = name.location.end_char + end_column = name.location.end_column location = Location.new( start_line: params.location.start_line, start_char: end_char, + start_column: end_column, end_line: params.location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) params = Params.new(location: location) @@ -933,9 +963,13 @@ def on_def(name, params, bodystmt) if ending tokens.delete(ending) + start_char = find_next_statement_start(params.location.end_char) + bodystmt.bind( - find_next_statement_start(params.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[params.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) Def.new( @@ -993,12 +1027,15 @@ def on_defs(target, operator, name, params, bodystmt) # location information if params.is_a?(Params) && params.empty? end_char = name.location.end_char + end_column = name.location.end_column location = Location.new( start_line: params.location.start_line, start_char: end_char, + start_column: end_column, end_line: params.location.end_line, - end_char: end_char + end_char: end_char, + end_column: end_column ) params = Params.new(location: location) @@ -1009,9 +1046,13 @@ def on_defs(target, operator, name, params, bodystmt) if ending tokens.delete(ending) + start_char = find_next_statement_start(params.location.end_char) + bodystmt.bind( - find_next_statement_start(params.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[params.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) Defs.new( @@ -1043,10 +1084,14 @@ def on_defs(target, operator, name, params, bodystmt) def on_do_block(block_var, bodystmt) beginning = find_token(Kw, "do") ending = find_token(Kw, "end") + location = (block_var || beginning).location + start_char = find_next_statement_start(location.end_char) bodystmt.bind( - find_next_statement_start((block_var || beginning).location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) DoBlock.new( @@ -1128,10 +1173,13 @@ def on_else(statements) node = tokens[index] ending = node.value == "end" ? tokens.delete_at(index) : node + start_char = find_next_statement_start(keyword.location.end_char) statements.bind( - find_next_statement_start(keyword.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[keyword.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) Else.new( @@ -1151,7 +1199,12 @@ def on_elsif(predicate, statements, consequent) beginning = find_token(Kw, "elsif") ending = consequent || find_token(Kw, "end") - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) Elsif.new( predicate: predicate, @@ -1174,7 +1227,7 @@ def on_embdoc_beg(value) @embdoc = EmbDoc.new( value: value, - location: Location.fixed(line: lineno, char: char_pos) + location: Location.fixed(line: lineno, column: current_column, char: char_pos) ) end @@ -1189,8 +1242,10 @@ def on_embdoc_end(value) Location.new( start_line: location.start_line, start_char: location.start_char, + start_column: location.start_column, end_line: lineno, - end_char: char_pos + value.length - 1 + end_char: char_pos + value.length - 1, + end_column: current_column + value.length - 1 ) ) @@ -1206,7 +1261,7 @@ def on_embexpr_beg(value) node = EmbExprBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1219,7 +1274,7 @@ def on_embexpr_end(value) node = EmbExprEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1232,7 +1287,7 @@ def on_embvar(value) node = EmbVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1247,9 +1302,12 @@ def on_ensure(statements) # We don't want to consume the :@kw event, because that would break # def..ensure..end chains. ending = find_token(Kw, "end", consume: false) + start_char = find_next_statement_start(keyword.location.end_char) statements.bind( - find_next_statement_start(keyword.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[keyword.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) Ensure.new( @@ -1296,7 +1354,7 @@ def on_field(parent, operator, name) def on_float(value) FloatLiteral.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1341,7 +1399,9 @@ def on_for(index, collection, statements) statements.bind( (keyword || collection).location.end_char, - ending.location.start_char + (keyword || collection).location.end_column, + ending.location.start_char, + ending.location.start_column ) if index.is_a?(MLHS) @@ -1362,7 +1422,7 @@ def on_for(index, collection, statements) def on_gvar(value) GVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1383,7 +1443,7 @@ def on_hash(assocs) # on_heredoc_beg: (String value) -> HeredocBeg def on_heredoc_beg(value) location = - Location.token(line: lineno, char: char_pos, size: value.size + 1) + Location.token(line: lineno, char: char_pos, column: current_column, size: value.size + 1) # Here we're going to artificially create an extra node type so that if # there are comments after the declaration of a heredoc, they get printed. @@ -1419,8 +1479,10 @@ def on_heredoc_end(value) Location.new( start_line: heredoc.location.start_line, start_char: heredoc.location.start_char, + start_column: heredoc.location.start_column, end_line: lineno, - end_char: char_pos + end_char: char_pos, + end_column: current_column, ) ) end @@ -1447,7 +1509,7 @@ def on_hshptn(constant, keywords, keyword_rest) def on_ident(value) Ident.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1461,7 +1523,12 @@ def on_if(predicate, statements, consequent) beginning = find_token(Kw, "if") ending = consequent || find_token(Kw, "end") - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) If.new( predicate: predicate, @@ -1507,7 +1574,7 @@ def on_if_mod(predicate, statement) def on_imaginary(value) Imaginary.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1531,9 +1598,12 @@ def on_in(pattern, statements, consequent) statements_start = token end + start_char = find_next_statement_start(statements_start.location.end_char) statements.bind( - find_next_statement_start(statements_start.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[statements_start.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) In.new( @@ -1549,7 +1619,7 @@ def on_in(pattern, statements, consequent) def on_int(value) Int.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1558,7 +1628,7 @@ def on_int(value) def on_ivar(value) IVar.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1568,7 +1638,7 @@ def on_kw(value) node = Kw.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1589,7 +1659,7 @@ def on_kwrest_param(name) def on_label(value) Label.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -1599,7 +1669,7 @@ def on_label_end(value) node = LabelEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1625,7 +1695,12 @@ def on_lambda(params, statements) closing = find_token(Kw, "end") end - statements.bind(opening.location.end_char, closing.location.start_char) + statements.bind( + opening.location.end_char, + opening.location.end_column, + closing.location.start_char, + closing.location.start_column + ) Lambda.new( params: params, @@ -1640,7 +1715,7 @@ def on_lbrace(value) node = LBrace.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1653,7 +1728,7 @@ def on_lbracket(value) node = LBracket.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1666,7 +1741,7 @@ def on_lparen(value) node = LParen.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1765,7 +1840,7 @@ def on_mlhs_add_star(mlhs, part) # :call-seq: # on_mlhs_new: () -> MLHS def on_mlhs_new - MLHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos)) + MLHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column)) end # :call-seq: @@ -1791,10 +1866,13 @@ def on_mlhs_paren(contents) def on_module(constant, bodystmt) beginning = find_token(Kw, "module") ending = find_token(Kw, "end") + start_char = find_next_statement_start(constant.location.end_char) bodystmt.bind( - find_next_statement_start(constant.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[constant.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) ModuleDeclaration.new( @@ -1807,7 +1885,7 @@ def on_module(constant, bodystmt) # :call-seq: # on_mrhs_new: () -> MRHS def on_mrhs_new - MRHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos)) + MRHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column)) end # :call-seq: @@ -1876,7 +1954,7 @@ def on_op(value) node = Op.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -1935,7 +2013,7 @@ def on_params( if parts.any? parts[0].location.to(parts[-1].location) else - Location.fixed(line: lineno, char: char_pos) + Location.fixed(line: lineno, char: char_pos, column: current_column) end Params.new( @@ -1958,12 +2036,15 @@ def on_paren(contents) if contents && contents.is_a?(Params) location = contents.location + start_char = find_next_statement_start(lparen.location.end_char) location = Location.new( start_line: location.start_line, - start_char: find_next_statement_start(lparen.location.end_char), + start_char: start_char, + start_column: start_char - line_counts[lparen.location.start_line - 1].start, end_line: location.end_line, - end_char: rparen.location.start_char + end_char: rparen.location.start_char, + end_column: rparen.location.start_column ) contents = @@ -2001,23 +2082,26 @@ def on_parse_error(error, *) def on_period(value) Period.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end # :call-seq: # on_program: (Statements statements) -> Program def on_program(statements) + last_column = source.length - line_counts[lines.length - 1].start location = Location.new( start_line: 1, start_char: 0, + start_column: 0, end_line: lines.length, - end_char: source.length + end_char: source.length, + end_column: last_column ) statements.body << @__end__ if @__end__ - statements.bind(0, source.length) + statements.bind(0, 0, source.length, last_column) program = Program.new(statements: statements, location: location) attach_comments(program, @comments) @@ -2130,7 +2214,7 @@ def on_qsymbols_beg(value) node = QSymbolsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2165,7 +2249,7 @@ def on_qwords_beg(value) node = QWordsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2185,7 +2269,7 @@ def on_qwords_new def on_rational(value) RationalLiteral.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -2195,7 +2279,7 @@ def on_rbrace(value) node = RBrace.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2208,7 +2292,7 @@ def on_rbracket(value) node = RBracket.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2242,7 +2326,7 @@ def on_regexp_beg(value) node = RegexpBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2254,7 +2338,7 @@ def on_regexp_beg(value) def on_regexp_end(value) RegexpEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -2296,9 +2380,12 @@ def on_rescue(exceptions, variable, statements, consequent) exceptions = exceptions[0] if exceptions.is_a?(Array) last_node = variable || exceptions || keyword + start_char = find_next_statement_start(last_node.location.end_char) statements.bind( - find_next_statement_start(last_node.location.end_char), - char_pos + start_char, + start_char - line_counts[last_node.location.start_line - 1].start, + char_pos, + current_column ) # We add an additional inner node here that ripper doesn't provide so that @@ -2313,8 +2400,10 @@ def on_rescue(exceptions, variable, statements, consequent) Location.new( start_line: keyword.location.start_line, start_char: keyword.location.end_char + 1, + start_column: keyword.location.end_column + 1, end_line: last_node.location.end_line, - end_char: last_node.location.end_char + end_char: last_node.location.end_char, + end_column: last_node.location.end_column ) ) end @@ -2328,8 +2417,10 @@ def on_rescue(exceptions, variable, statements, consequent) Location.new( start_line: keyword.location.start_line, start_char: keyword.location.start_char, + start_column: keyword.location.start_column, end_line: lineno, - end_char: char_pos + end_char: char_pos, + end_column: current_column ) ) end @@ -2388,7 +2479,7 @@ def on_rparen(value) node = RParen.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2400,10 +2491,13 @@ def on_rparen(value) def on_sclass(target, bodystmt) beginning = find_token(Kw, "class") ending = find_token(Kw, "end") + start_char = find_next_statement_start(target.location.end_char) bodystmt.bind( - find_next_statement_start(target.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[target.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) SClass.new( @@ -2442,7 +2536,7 @@ def on_stmts_new Statements.new( self, body: [], - location: Location.fixed(line: lineno, char: char_pos) + location: Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -2476,7 +2570,7 @@ def on_string_concat(left, right) def on_string_content StringContent.new( parts: [], - location: Location.fixed(line: lineno, char: char_pos) + location: Location.fixed(line: lineno, char: char_pos, column: current_column) ) end @@ -2499,18 +2593,22 @@ def on_string_embexpr(statements) statements.bind( embexpr_beg.location.end_char, - embexpr_end.location.start_char + embexpr_beg.location.end_column, + embexpr_end.location.start_char, + embexpr_end.location.start_column ) location = Location.new( start_line: embexpr_beg.location.start_line, start_char: embexpr_beg.location.start_char, + start_column: embexpr_beg.location.start_column, end_line: [ embexpr_end.location.end_line, statements.location.end_line ].max, - end_char: embexpr_end.location.end_char + end_char: embexpr_end.location.end_char, + end_column: embexpr_end.location.end_column ) StringEmbExpr.new(statements: statements, location: location) @@ -2538,11 +2636,13 @@ def on_string_literal(string) Location.new( start_line: tstring_beg.location.start_line, start_char: tstring_beg.location.start_char, + start_column: tstring_beg.location.start_column, end_line: [ tstring_end.location.end_line, string.location.end_line ].max, - end_char: tstring_end.location.end_char + end_char: tstring_end.location.end_char, + end_column: tstring_end.location.end_column ) StringLiteral.new( @@ -2571,7 +2671,7 @@ def on_symbeg(value) node = SymBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2625,7 +2725,7 @@ def on_symbols_beg(value) node = SymbolsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2650,7 +2750,7 @@ def on_tlambda(value) node = TLambda.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2663,7 +2763,7 @@ def on_tlambeg(value) node = TLamBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2698,7 +2798,7 @@ def on_tstring_beg(value) node = TStringBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2710,7 +2810,7 @@ def on_tstring_beg(value) def on_tstring_content(value) TStringContent.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) end @@ -2720,7 +2820,7 @@ def on_tstring_end(value) node = TStringEnd.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node @@ -2794,7 +2894,12 @@ def on_unless(predicate, statements, consequent) beginning = find_token(Kw, "unless") ending = consequent || find_token(Kw, "end") - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) Unless.new( predicate: predicate, @@ -2831,7 +2936,12 @@ def on_until(predicate, statements) end # Update the Statements location information - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) Until.new( predicate: predicate, @@ -2875,7 +2985,7 @@ def on_var_field(value) else # You can hit this pattern if you're assigning to a splat using # pattern matching syntax in Ruby 2.7+ - Location.fixed(line: lineno, char: char_pos) + Location.fixed(line: lineno, char: char_pos, column: current_column) end VarField.new(value: value, location: location) @@ -2903,7 +3013,7 @@ def on_vcall(ident) # :call-seq: # on_void_stmt: () -> VoidStmt def on_void_stmt - VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos)) + VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos, column: current_column)) end # :call-seq: @@ -2922,9 +3032,13 @@ def on_when(arguments, statements, consequent) statements_start = token end + start_char = find_next_statement_start(statements_start.location.end_char) + statements.bind( - find_next_statement_start(statements_start.location.end_char), - ending.location.start_char + start_char, + start_char - line_counts[statements_start.location.start_line - 1].start, + ending.location.start_char, + ending.location.start_column ) When.new( @@ -2950,7 +3064,12 @@ def on_while(predicate, statements) end # Update the Statements location information - statements.bind(predicate.location.end_char, ending.location.start_char) + statements.bind( + predicate.location.end_char, + predicate.location.end_column, + ending.location.start_char, + ending.location.start_column + ) While.new( predicate: predicate, @@ -2986,7 +3105,7 @@ def on_word_add(word, part) # :call-seq: # on_word_new: () -> Word def on_word_new - Word.new(parts: [], location: Location.fixed(line: lineno, char: char_pos)) + Word.new(parts: [], location: Location.fixed(line: lineno, char: char_pos, column: current_column)) end # :call-seq: @@ -3005,7 +3124,7 @@ def on_words_beg(value) node = WordsBeg.new( value: value, - location: Location.token(line: lineno, char: char_pos, size: value.size) + location: Location.token(line: lineno, char: char_pos, column: current_column, size: value.size) ) tokens << node diff --git a/test/node_test.rb b/test/node_test.rb index 9c29f79d..4831081b 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -999,14 +999,26 @@ def test_zsuper assert_node(ZSuper, "zsuper", "super") end + def test_column_positions + source = <<~SOURCE + puts 'Hello' + puts 'Goodbye' + SOURCE + + at = location(lines: 2..2, chars: 13..27, columns: 0..14) + assert_node(Command, "command", source, at: at) + end + private - def location(lines: 1..1, chars: 0..0) + def location(lines: 1..1, chars: 0..0, columns: 0..0) Location.new( start_line: lines.begin, start_char: chars.begin, + start_column: columns.begin, end_line: lines.end, - end_char: chars.end + end_char: chars.end, + end_column: columns.end ) end @@ -1014,7 +1026,8 @@ def assert_node(kind, type, source, at: nil) at ||= location( lines: 1..[1, source.count("\n")].max, - chars: 0..source.chomp.size + chars: 0..source.chomp.size, + columns: 0..source.chomp.size ) # Parse the example, get the outputted parse tree, and assert that it was From 48ce3753499f3ad510c0234539c6c23f4ecfc784 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Tue, 12 Apr 2022 16:44:43 -0400 Subject: [PATCH 21/23] Handle multibyte strings --- lib/syntax_tree/parser.rb | 3 ++- test/node_test.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index 6bc27d5c..60923b57 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -166,7 +166,8 @@ def char_pos # This represents the current column we're in relative to the beginning of # the current line. def current_column - column - line_counts[lineno - 1].start + line = line_counts[lineno - 1] + line[column].to_i - line.start end # As we build up a list of tokens, we'll periodically need to go backwards diff --git a/test/node_test.rb b/test/node_test.rb index 4831081b..e412d648 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -1009,6 +1009,16 @@ def test_column_positions assert_node(Command, "command", source, at: at) end + def test_multibyte_column_positions + source = <<~SOURCE + puts "Congrats" + puts "🎉 🎉" + SOURCE + + at = location(lines: 2..2, chars: 16..26, columns: 0..10) + assert_node(Command, "command", source, at: at) + end + private def location(lines: 1..1, chars: 0..0, columns: 0..0) From cdf6a9e3d9b5b7f8247e80a83be04faeba90b078 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 12 Apr 2022 20:59:54 -0400 Subject: [PATCH 22/23] Better documentation From c2d8f3c9aac4bb1d1df559a98a8524792ba29d09 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 12 Apr 2022 21:43:28 -0400 Subject: [PATCH 23/23] Bump to version 2.1.0 --- CHANGELOG.md | 18 +++++++++++++++++- lib/syntax_tree/version.rb | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afeb8899..189509c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [2.1.0] - 2022-04-12 + +### Added + +- The `SyntaxTree::Visitor` class now implements the visitor pattern for Ruby nodes. +- The `SyntaxTree::Visitor.visit_method(name)` method. +- Support for Ruby 2.7. +- Support for comments on `rescue` and `else` keywords. +- `SyntaxTree::Location` now additionally has `start_column` and `end_column`. +- The CLI now accepts content over STDIN for the `ast`, `check`, `debug`, `doc`, `format`, and `write` commands. + +### Removed + +- The missing hash value inlay hints have been removed. + ## [2.0.1] - 2022-03-31 ### Changed @@ -128,7 +143,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - 🎉 Initial release! 🎉 -[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.1...HEAD +[unreleased]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.1.0...HEAD +[2.1.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.1...v2.1.0 [2.0.1]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v2.0.0...v2.0.1 [2.0.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.2.0...v2.0.0 [1.2.0]: https://github.com/ruby-syntax-tree/syntax_tree/compare/v1.1.1...v1.2.0 diff --git a/lib/syntax_tree/version.rb b/lib/syntax_tree/version.rb index fde7d633..f3bc7b04 100644 --- a/lib/syntax_tree/version.rb +++ b/lib/syntax_tree/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SyntaxTree - VERSION = "2.0.1" + VERSION = "2.1.0" end