diff --git a/CHANGELOG.md b/CHANGELOG.md index 960bb0e9..12e7999f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Fixed the AST output by adding blocks to `Command` and `CommandCall` nodes in the `FieldVisitor`. - Fixed the location of lambda local variables (e.g., `->(; a) {}`). +- The `Elsif` node has been folded into `IfNode`. A `keyword` field has been added to `IfNode` specifying if the node represents an `if` or `elsif`. ## [6.0.1] - 2023-02-26 diff --git a/lib/syntax_tree/dsl.rb b/lib/syntax_tree/dsl.rb index 4506aa04..b7f04ef1 100644 --- a/lib/syntax_tree/dsl.rb +++ b/lib/syntax_tree/dsl.rb @@ -347,16 +347,6 @@ def Else(keyword, statements) ) end - # Create a new Elsif node. - def Elsif(predicate, statements, consequent) - Elsif.new( - predicate: predicate, - statements: statements, - consequent: consequent, - location: Location.default - ) - end - # Create a new EmbDoc node. def EmbDoc(value) EmbDoc.new(value: value, location: Location.default) @@ -478,8 +468,9 @@ def Ident(value) end # Create a new IfNode node. - def IfNode(predicate, statements, consequent) + def IfNode(keyword, predicate, statements, consequent) IfNode.new( + keyword: keyword, predicate: predicate, statements: statements, consequent: consequent, diff --git a/lib/syntax_tree/field_visitor.rb b/lib/syntax_tree/field_visitor.rb index f5607c67..c3f6866b 100644 --- a/lib/syntax_tree/field_visitor.rb +++ b/lib/syntax_tree/field_visitor.rb @@ -353,15 +353,6 @@ def visit_else(node) end end - def visit_elsif(node) - 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(node, "embdoc") { field("value", node.value) } end diff --git a/lib/syntax_tree/mutation_visitor.rb b/lib/syntax_tree/mutation_visitor.rb index 0b4b9357..d3a2eb17 100644 --- a/lib/syntax_tree/mutation_visitor.rb +++ b/lib/syntax_tree/mutation_visitor.rb @@ -322,14 +322,6 @@ def visit_else(node) ) end - # Visit a Elsif node. - def visit_elsif(node) - node.copy( - statements: visit(node.statements), - consequent: visit(node.consequent) - ) - end - # Visit a EmbDoc node. def visit_embdoc(node) node.copy diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index c4bc1495..a972d343 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -4835,95 +4835,6 @@ def ===(other) end end - # Elsif represents another clause in an +if+ or +unless+ chain. - # - # if variable - # elsif other_variable - # end - # - class Elsif < Node - # [Node] the expression to be checked - attr_reader :predicate - - # [Statements] the expressions to be executed - attr_reader :statements - - # [nil | Elsif | Else] the next clause in the chain - attr_reader :consequent - - # [Array[ Comment | EmbDoc ]] the comments attached to this node - attr_reader :comments - - def initialize(predicate:, statements:, consequent:, location:) - @predicate = predicate - @statements = statements - @consequent = consequent - @location = location - @comments = [] - end - - def accept(visitor) - visitor.visit_elsif(self) - end - - def child_nodes - [predicate, statements, consequent] - end - - def copy(predicate: nil, statements: nil, consequent: nil, location: nil) - node = - Elsif.new( - predicate: predicate || self.predicate, - statements: statements || self.statements, - consequent: consequent || self.consequent, - location: location || self.location - ) - - node.comments.concat(comments.map(&:copy)) - node - end - - alias deconstruct child_nodes - - def deconstruct_keys(_keys) - { - predicate: predicate, - statements: statements, - consequent: consequent, - location: location, - comments: comments - } - end - - def format(q) - q.group do - q.group do - q.text("elsif ") - q.nest("elsif".length - 1) { q.format(predicate) } - end - - unless statements.empty? - q.indent do - q.breakable_force - q.format(statements) - end - end - - if consequent - q.group do - q.breakable_force - q.format(consequent) - end - end - end - end - - def ===(other) - other.is_a?(Elsif) && predicate === other.predicate && - statements === other.statements && consequent === other.consequent - end - end - # EmbDoc represents a multi-line comment. # # =begin @@ -6439,25 +6350,29 @@ def contains_conditional? end end - # If represents the first clause in an +if+ chain. + # If an +if+ or +elsif+ clause in an +if+ chain. # # if predicate # end # class IfNode < Node + # [Kw] the opening keyword of the conditional statement + attr_reader :keyword + # [Node] the expression to be checked attr_reader :predicate # [Statements] the expressions to be executed attr_reader :statements - # [nil | Elsif | Else] the next clause in the chain + # [nil | IfNode | Else] the next clause in the chain attr_reader :consequent # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(predicate:, statements:, consequent:, location:) + def initialize(keyword:, predicate:, statements:, consequent:, location:) + @keyword = keyword @predicate = predicate @statements = statements @consequent = consequent @@ -6473,9 +6388,16 @@ def child_nodes [predicate, statements, consequent] end - def copy(predicate: nil, statements: nil, consequent: nil, location: nil) + def copy( + keyword: nil, + predicate: nil, + statements: nil, + consequent: nil, + location: nil + ) node = IfNode.new( + keyword: keyword || self.keyword, predicate: predicate || self.predicate, statements: statements || self.statements, consequent: consequent || self.consequent, @@ -6494,17 +6416,42 @@ def deconstruct_keys(_keys) statements: statements, consequent: consequent, location: location, + keyword: keyword, comments: comments } end def format(q) - ConditionalFormatter.new("if", self).format(q) + if keyword.value == "elsif" + q.group do + q.group do + q.text("elsif ") + q.nest("elsif".length - 1) { q.format(predicate) } + end + + unless statements.empty? + q.indent do + q.breakable_force + q.format(statements) + end + end + + if consequent + q.group do + q.breakable_force + q.format(consequent) + end + end + end + else + ConditionalFormatter.new(keyword.value, self).format(q) + end end def ===(other) other.is_a?(IfNode) && predicate === other.predicate && - statements === other.statements && consequent === other.consequent + statements === other.statements && consequent === other.consequent && + keyword === other.keyword end # Checks if the node was originally found in the modifier form. @@ -11295,7 +11242,7 @@ class UnlessNode < Node # [Statements] the expressions to be executed attr_reader :statements - # [nil | Elsif | Else] the next clause in the chain + # [nil | IfNode | Else] the next clause in the chain attr_reader :consequent # [Array[ Comment | EmbDoc ]] the comments attached to this node diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index ed0de408..dd3be38a 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -1575,7 +1575,8 @@ def on_elsif(predicate, statements, consequent) ending.location.start_column ) - Elsif.new( + IfNode.new( + keyword: beginning, predicate: predicate, statements: statements, consequent: consequent, @@ -2062,6 +2063,7 @@ def on_if(predicate, statements, consequent) ) IfNode.new( + keyword: beginning, predicate: predicate, statements: statements, consequent: consequent, @@ -2083,9 +2085,10 @@ def on_ifop(predicate, truthy, falsy) # :call-seq: # on_if_mod: (untyped predicate, untyped statement) -> IfNode def on_if_mod(predicate, statement) - consume_keyword(:if) + beginning = consume_keyword(:if) IfNode.new( + keyword: beginning, predicate: predicate, statements: Statements.new(body: [statement], location: statement.location), diff --git a/lib/syntax_tree/translation/parser.rb b/lib/syntax_tree/translation/parser.rb index 8be4fc79..eb987bfd 100644 --- a/lib/syntax_tree/translation/parser.rb +++ b/lib/syntax_tree/translation/parser.rb @@ -1037,50 +1037,6 @@ def visit_else(node) end end - # Visit an Elsif node. - def visit_elsif(node) - begin_start = node.predicate.end_char - begin_end = - if node.statements.empty? - node.statements.end_char - else - node.statements.body.first.start_char - end - - begin_token = - if buffer.source[begin_start...begin_end].include?("then") - srange_find(begin_start, begin_end, "then") - elsif buffer.source[begin_start...begin_end].include?(";") - srange_find(begin_start, begin_end, ";") - end - - else_token = - case node.consequent - when Elsif - srange_length(node.consequent.start_char, 5) - when Else - srange_length(node.consequent.start_char, 4) - end - - expression = srange(node.start_char, node.statements.end_char - 1) - - s( - :if, - [ - visit(node.predicate), - visit(node.statements), - visit(node.consequent) - ], - smap_condition( - srange_length(node.start_char, 5), - begin_token, - else_token, - nil, - expression - ) - ) - end - # Visit an ENDBlock node. def visit_END(node) s( @@ -1361,7 +1317,7 @@ def visit_if(node) else_token = case node.consequent - when Elsif + when IfNode srange_length(node.consequent.start_char, 5) when Else srange_length(node.consequent.start_char, 4) diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb index eb57acd2..5371d636 100644 --- a/lib/syntax_tree/visitor.rb +++ b/lib/syntax_tree/visitor.rb @@ -131,9 +131,6 @@ class Visitor < BasicVisitor # 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 diff --git a/lib/syntax_tree/yarv/compiler.rb b/lib/syntax_tree/yarv/compiler.rb index bd20bc19..39a7eb45 100644 --- a/lib/syntax_tree/yarv/compiler.rb +++ b/lib/syntax_tree/yarv/compiler.rb @@ -916,17 +916,6 @@ def visit_else(node) iseq.pop unless last_statement? end - def visit_elsif(node) - visit_if( - IfNode.new( - predicate: node.predicate, - statements: node.statements, - consequent: node.consequent, - location: node.location - ) - ) - end - def visit_ensure(node) end @@ -1054,6 +1043,7 @@ def visit_if(node) def visit_if_op(node) visit_if( IfNode.new( + keyword: Kw.new(value: "if", location: Location.default), predicate: node.predicate, statements: Statements.new(body: [node.truthy], location: Location.default), diff --git a/lib/syntax_tree/yarv/decompiler.rb b/lib/syntax_tree/yarv/decompiler.rb index 4ea99e3a..bf6ffb0a 100644 --- a/lib/syntax_tree/yarv/decompiler.rb +++ b/lib/syntax_tree/yarv/decompiler.rb @@ -77,7 +77,7 @@ def decompile(iseq) Next(Args([])) ] - clause << IfNode(clause.pop, Statements(body), nil) + clause << IfNode(Kw("if"), clause.pop, Statements(body), nil) when Dup clause << clause.last when DupHash diff --git a/test/node_test.rb b/test/node_test.rb index 19fbeed2..6efac6a1 100644 --- a/test/node_test.rb +++ b/test/node_test.rb @@ -452,7 +452,7 @@ def test_elsif SOURCE at = location(lines: 2..4, chars: 9..30) - assert_node(Elsif, source, at: at, &:consequent) + assert_node(IfNode, source, at: at, &:consequent) end def test_embdoc