aboutsummaryrefslogtreecommitdiffstats
path: root/test/newline_test.rb
blob: 5a81f52637ec81bf38065fc0f1ed2b96c0f20ad8 (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
# frozen_string_literal: true

require "yarp_test_helper"

class NewlineTest < Test::Unit::TestCase
  class NewlineVisitor < YARP::Visitor
    attr_reader :source, :newlines

    def initialize(source)
      @source = source
      @newlines = []
    end

    def visit(node)
      newlines << source.line(node.location.start_offset) if node&.newline?
      super(node)
    end
  end

  root = File.dirname(__dir__)
  Dir["{lib,test}/**/*.rb", base: root].each do |relative|
    filepath = File.join(root, relative)

    define_method "test_newline_flags_#{relative}" do
      source = File.read(filepath, binmode: true, external_encoding: Encoding::UTF_8)
      expected = rubyvm_lines(source)

      result = YARP.parse_file(filepath)
      assert_empty result.errors

      result.mark_newlines
      visitor = NewlineVisitor.new(result.source)
      result.value.accept(visitor)
      actual = visitor.newlines

      if relative == "lib/yarp/serialize.rb"
        # while (b = io.getbyte) >= 128 has 2 newline flags
        expected.delete_at actual.index(62)
      elsif relative == "lib/yarp/lex_compat.rb"
        # extra flag for: dedent_next =\n  ((token.event: due to bytecode order
        actual.delete(520)
        # different line for: token =\n  case event: due to bytecode order
        actual.delete(577)
        expected.delete(578)
        # extra flag for: lex_state =\n  if RIPPER: due to bytecode order
        actual.delete(610)
        # extra flag for: (token[2].start_with?("\#$") || token[2].start_with?("\#@"))
        # unclear when ParenthesesNode should allow a second flag on the same line or not
        actual.delete(737)
      end

      assert_equal expected, actual
    end
  end

  private

  def ignore_warnings
    previous_verbosity = $VERBOSE
    $VERBOSE = nil
    yield
  ensure
    $VERBOSE = previous_verbosity
  end

  def rubyvm_lines(source)
    queue = [ignore_warnings { RubyVM::InstructionSequence.compile(source) }]
    lines = []

    while iseq = queue.shift
      lines.concat(iseq.trace_points.filter_map { |line, event| line if event == :line })
      iseq.each_child { |insn| queue << insn unless insn.label.start_with?("ensure in ") }
    end

    lines.sort
  end
end