forked from opal/opal
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsource_map_helper.rb
More file actions
190 lines (152 loc) · 7.37 KB
/
source_map_helper.rb
File metadata and controls
190 lines (152 loc) · 7.37 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
module SourceMapHelper
extend self
# Just for debugging purposes
def inspect_source(source)
puts source_with_lines(source)
end
# Just for debugging purposes
def source_with_lines(source)
source.split("\n", -1).map.with_index { |line, index| "#{(index).to_s.rjust(3)} | #{line}" }.join("\n")
end
def fragment(line: nil, column: nil, source_map_name: nil, code: '', sexp_type: nil)
double('Fragment', line: line, column: column, source_map_name: source_map_name, code: code, sexp_type: sexp_type)
end
SourcePosition = Struct.new(:line, :column, :file) do
def inspect
"#<SourcePosition line:#{line} column:#{column} file:#{file.inspect}>"
end
alias_method :to_s, :inspect
def in_source(source)
source.split("\n", -1)[line].to_s[column..-1]
end
def self.absolutize_mappings(relative_mappings)
reference_segment = [0, 0, 0, 0, 0]
relative_mappings.map do |relative_mapping|
# Reference: [generated_column, source_index, original_line, original_column, map_name_index]
reference_segment[0] = 0 # reset the generated_column at each new line
relative_mapping.map do |relative_segment|
segment = []
segment[0] = relative_segment[0].to_int + reference_segment[0].to_int
segment[1] = relative_segment[1].to_int + (reference_segment[1] || 0).to_int if relative_segment[1]
segment[2] = relative_segment[2].to_int + (reference_segment[2] || 0).to_int if relative_segment[2]
segment[3] = relative_segment[3].to_int + (reference_segment[3] || 0).to_int if relative_segment[3]
segment[4] = relative_segment[4].to_int + (reference_segment[4] || 0).to_int if relative_segment[4]
reference_segment = segment
segment
end
end
end
def find_section_in_map_index(index_map)
sections = index_map.to_h[:sections]
section, section_index = sections.each.with_index.find do |map, index|
next_map = sections[index + 1] or next true # if there's no next map the current one is good
(
line > map[:offset][:line] || (line == map[:offset][:line] && column >= map[:offset][:column])
) && (
line < next_map[:offset][:line] || (line == next_map[:offset][:line] && column < map[:offset][:column])
)
end
next_section = sections[section_index + 1]
section or raise "no map found for #{inspect} among available sections: #{sections.map { |s| s[:offset] }}"
end
def mapped_with(map)
case map
when Opal::SourceMap::Index
offset_section = find_section_in_map_index(map)
offset = offset_section[:offset] || raise(offset_section.inspect)
offset_line = line - offset[:line]
offset_column = line.zero? ? (column - offset[:column]) : column
offset_position = self.class.new(offset_line, offset_column, offset_section[:map][:sources].first)
offset_position.mapped_with_file_map(offset_section[:map])
when Opal::SourceMap::File
mapped_with_file_map(map.to_h)
else raise "unknown map type: #{map.inspect}"
end
end
def mapped_with_file_map(map_to_h)
relative_mappings = Opal::SourceMap::VLQ.decode_mappings(map_to_h[:mappings])
absolute_mappings = SourcePosition.absolutize_mappings(relative_mappings)
mappings_line = absolute_mappings[line] or raise(
"can't find a mapping for #{inspect} in the available #{absolute_mappings.size} absolute mappings: #{absolute_mappings.inspect}"
)
# keep all segments with a column from the required position on
code_before_segment_strategy = -> segments {
segments.select do |segment|
segment[0] >= column
end.sort_by do |segment|
segment[0] - column # sort by the distance from the required position
end.first
}
code_after_segment_strategy = -> segments {
segments.select do |segment|
column >= segment[0]
end.sort_by do |segment|
column - segment[0] # sort by the distance from the required position
end.first
}
absolute_segments = absolute_mappings[line] or raise
matched_segment = code_before_segment_strategy[absolute_segments] || code_after_segment_strategy[absolute_segments]
raise "can't find a matching segment for #{inspect} in #{absolute_mappings[line]}" unless matched_segment
source_index = matched_segment[1]
original_source = map_to_h[:sources][source_index]
original_line = matched_segment[2]
original_column = matched_segment[3]
self.class.new(original_line, original_column, original_source)
end
def original_source(map)
map_to_h = map.to_h
matching_source = -> map_data { map_data[:sourcesContent].first if map_data[:sources] == [file] }
(map_to_h[:sections] || [{offset: {}, map: map_to_h}]).map { |section| matching_source[section[:map]] }.compact.first or
raise "can't find a source for #{file.inspect} in #{map_to_h.to_json}"
end
def self.find_code(code, source:)
line_contents, line = source.split("\n", -1).to_enum.with_index.find do |contents, index|
contents.include? code
end
return nil if line_contents.nil?
column = line_contents.index(code)
new(line, column)
end
end
RSpec::Matchers.define :be_at_line_and_column do |line, column, source:|
expected_position = SourcePosition.new(line, column)
match do |code|
actual_position = SourcePosition.find_code(code, source: source)
actual_position == expected_position
end
failure_message do |code|
actual_position = SourcePosition.find_code(code, source: source)
actual_code = expected_position.in_source(source)
"expected #{code.inspect} to be at #{expected_position}, " +
"instead #{actual_code.inspect} was found at the expected position, while code was found at #{actual_position}"
end
end
RSpec::Matchers.define :be_mapped_to_line_and_column do |line, column, source:, map:, file: nil|
expected_position = SourcePosition.new(line, column, file)
match do |code|
actual_position = SourcePosition.find_code(code, source: source).mapped_with(map)
actual_position == expected_position
end
failure_message do |code|
code_position = SourcePosition.find_code(code, source: source)
actual_position = code_position.mapped_with(map)
expected_source = expected_position.original_source(map)
expected_code = expected_position.in_source(expected_source)
actual_source = actual_position.original_source(map)
actual_code = actual_position.in_source(actual_source)
if Opal::SourceMap::Index === map
actual_section = actual_position.find_section_in_map_index(map)
expected_section = expected_position.find_section_in_map_index(map)
map.to_h[:sections].each { |s| p s[:offset] }
actual_offset = " (offset: #{actual_section[:offset]})"
expected_offset = " (offset: #{expected_section[:offset]})"
end
"expected #{code.inspect} at #{code_position.inspect} to be mapped to #{expected_position.inspect}" +
"\n\nEXPECTED #{expected_code.inspect} at #{expected_position}#{expected_offset}:\n"+
source_with_lines(expected_source) +
"\n\nACTUAL #{actual_code.inspect} at #{actual_position}#{actual_offset}:\n"+
source_with_lines(actual_source) +
"\n"
end
end
end