summaryrefslogtreecommitdiff
path: root/lib/prism/parse_result/newlines.rb
blob: cc1343dfdacc28f6b924a1eee8e6b3ce19e4f876 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# frozen_string_literal: true

module Prism
  class ParseResult < Result
    # The :line tracepoint event gets fired whenever the Ruby VM encounters an
    # expression on a new line. The types of expressions that can trigger this
    # event are:
    #
    # * if statements
    # * unless statements
    # * nodes that are children of statements lists
    #
    # In order to keep track of the newlines, we have a list of offsets that
    # come back from the parser. We assign these offsets to the first nodes that
    # we find in the tree that are on those lines.
    #
    # Note that the logic in this file should be kept in sync with the Java
    # MarkNewlinesVisitor, since that visitor is responsible for marking the
    # newlines for JRuby/TruffleRuby.
    #
    # This file is autoloaded only when `mark_newlines!` is called, so the
    # re-opening of the various nodes in this file will only be performed in
    # that case. We do that to avoid storing the extra `@newline` instance
    # variable on every node if we don't need it.
    class Newlines < Visitor
      # Create a new Newlines visitor with the given newline offsets.
      def initialize(lines)
        @lines = Array.new(1 + lines, false)
      end

      # Permit block/lambda nodes to mark newlines within themselves.
      def visit_block_node(node)
        old_lines = @lines
        @lines = Array.new(old_lines.size, false)

        begin
          super(node)
        ensure
          @lines = old_lines
        end
      end

      alias_method :visit_lambda_node, :visit_block_node

      # Mark if/unless nodes as newlines.
      def visit_if_node(node)
        node.newline!(@lines)
        super(node)
      end

      alias_method :visit_unless_node, :visit_if_node

      # Permit statements lists to mark newlines within themselves.
      def visit_statements_node(node)
        node.body.each do |child|
          child.newline!(@lines)
        end
        super(node)
      end
    end
  end

  class Node
    def newline? # :nodoc:
      @newline ? true : false
    end

    def newline!(lines) # :nodoc:
      line = location.start_line
      unless lines[line]
        lines[line] = true
        @newline = true
      end
    end
  end

  class BeginNode < Node
    def newline!(lines) # :nodoc:
      # Never mark BeginNode with a newline flag, mark children instead.
    end
  end

  class ParenthesesNode < Node
    def newline!(lines) # :nodoc:
      # Never mark ParenthesesNode with a newline flag, mark children instead.
    end
  end

  class IfNode < Node
    def newline!(lines) # :nodoc:
      predicate.newline!(lines)
    end
  end

  class UnlessNode < Node
    def newline!(lines) # :nodoc:
      predicate.newline!(lines)
    end
  end

  class UntilNode < Node
    def newline!(lines) # :nodoc:
      predicate.newline!(lines)
    end
  end

  class WhileNode < Node
    def newline!(lines) # :nodoc:
      predicate.newline!(lines)
    end
  end

  class RescueModifierNode < Node
    def newline!(lines) # :nodoc:
      expression.newline!(lines)
    end
  end

  class InterpolatedMatchLastLineNode < Node
    def newline!(lines) # :nodoc:
      first = parts.first
      first.newline!(lines) if first
    end
  end

  class InterpolatedRegularExpressionNode < Node
    def newline!(lines) # :nodoc:
      first = parts.first
      first.newline!(lines) if first
    end
  end

  class InterpolatedStringNode < Node
    def newline!(lines) # :nodoc:
      first = parts.first
      first.newline!(lines) if first
    end
  end

  class InterpolatedSymbolNode < Node
    def newline!(lines) # :nodoc:
      first = parts.first
      first.newline!(lines) if first
    end
  end

  class InterpolatedXStringNode < Node
    def newline!(lines) # :nodoc:
      first = parts.first
      first.newline!(lines) if first
    end
  end
end