opal 0.3.44 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +0 -1
- data/CHANGELOG.md +52 -0
- data/README.md +3 -3
- data/Rakefile +32 -8
- data/bin/opal +69 -16
- data/config.ru +1 -1
- data/examples/native/app/app.rb +28 -9
- data/examples/rack/app/app.rb +1 -1
- data/lib/opal.rb +0 -1
- data/lib/opal/cli.rb +106 -0
- data/lib/opal/lexer.rb +4 -2
- data/lib/opal/parser.rb +603 -360
- data/lib/opal/processor.rb +20 -8
- data/lib/opal/server.rb +47 -0
- data/lib/opal/source_map.rb +63 -0
- data/lib/opal/sprockets_parser.rb +77 -0
- data/lib/opal/sprockets_source_map_header.rb +21 -0
- data/lib/opal/target_scope.rb +14 -7
- data/lib/opal/version.rb +1 -1
- data/opal.gemspec +2 -0
- data/opal/opal-browser/script_loader.rb +7 -7
- data/opal/opal-parser.js.erb +2 -2
- data/opal/opal-source-maps.js.erb +2 -0
- data/opal/opal.rb +3 -4
- data/opal/opal/array.rb +31 -28
- data/opal/opal/boolean.rb +4 -0
- data/opal/opal/class.rb +14 -5
- data/opal/opal/enumerable.rb +68 -8
- data/opal/opal/error.rb +1 -1
- data/opal/opal/hash.rb +15 -18
- data/opal/opal/kernel.rb +24 -10
- data/opal/opal/native.rb +31 -0
- data/opal/opal/nil_class.rb +7 -2
- data/opal/opal/numeric.rb +10 -1
- data/opal/opal/proc.rb +4 -0
- data/opal/opal/range.rb +1 -1
- data/opal/opal/regexp.rb +13 -3
- data/opal/opal/runtime.js +134 -51
- data/opal/opal/string.rb +45 -22
- data/opal/opal/time.rb +25 -7
- data/opal/source_map.rb +63 -0
- data/opal/source_map/generator.rb +251 -0
- data/opal/source_map/parser.rb +102 -0
- data/opal/source_map/vlq.rb +122 -0
- data/opal/strscan.rb +30 -12
- data/spec/opal/class/_inherited_spec.rb +1 -1
- data/spec/{rubyspec/core → opal}/class/bridge_class_spec.rb +5 -3
- data/spec/{rubyspec/core → opal}/class/extend_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/class/instance_methods_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/class/last_value_spec.rb +0 -1
- data/spec/{rubyspec/core → opal}/json/parse_spec.rb +0 -0
- data/spec/{rubyspec/core/kernel/block_given.rb → opal/kernel/block_given_spec.rb} +0 -0
- data/spec/{rubyspec/core → opal}/kernel/class_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/extend_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/format_spec.rb +0 -0
- data/spec/opal/kernel/freeze_spec.rb +15 -0
- data/spec/{rubyspec/core → opal}/kernel/match_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/method_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/methods_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/nil_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/p_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/printf_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/proc_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/rand_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/respond_to_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/sprintf_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/kernel/to_json_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/module/alias_method_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/module/ancestors_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/module/append_features_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/module/constants_spec.rb +0 -0
- data/spec/{rubyspec/core → opal}/module/module_function_spec.rb +0 -1
- data/spec/opal/native_spec.rb +85 -3
- data/spec/opal/numeric/equal_spec.rb +9 -0
- data/spec/opal/parser/irb_spec.rb +43 -0
- data/spec/{rubyspec/core → opal}/proc/proc_tricks_spec.rb +0 -0
- data/spec/opal/runtime/block_send_spec.rb +28 -0
- data/spec/{rubyspec/core/runtime → opal/runtime2}/call_spec.rb +0 -0
- data/spec/{rubyspec/core/runtime → opal/runtime2}/class_hierarchy_spec.rb +0 -0
- data/spec/{rubyspec/core/runtime → opal/runtime2}/def_spec.rb +0 -0
- data/spec/{rubyspec/core/runtime → opal/runtime2}/defined_spec.rb +0 -0
- data/spec/{rubyspec/core/runtime → opal/runtime2}/super_spec.rb +0 -0
- data/spec/opal/source_map_spec.rb +19 -0
- data/spec/opal/string/freeze_spec.rb +15 -0
- data/spec/{rubyspec/core → opal}/string/to_json_spec.rb +0 -0
- data/spec/ospec/runner.rb +3 -0
- data/spec/parser/str_spec.rb +4 -0
- data/spec/rubyspec/core/enumerable/fixtures/classes.rb +2 -2
- data/spec/rubyspec/core/enumerable/none_spec.rb +68 -0
- data/spec/rubyspec/core/enumerable/sort_by_spec.rb +31 -0
- data/spec/rubyspec/core/hash/size_spec.rb +1 -1
- data/spec/rubyspec/core/hash/to_native_spec.rb +3 -3
- data/spec/rubyspec/core/string/fixtures/classes.rb +49 -0
- data/spec/rubyspec/core/string/index_spec.rb +405 -0
- data/spec/rubyspec/filters/bugs/language/class.rb +0 -2
- data/spec/rubyspec/filters/bugs/language/module.rb +3 -0
- data/spec/rubyspec/language/array_spec.rb +1 -1
- data/spec/rubyspec/language/block_spec.rb +1 -1
- data/spec/rubyspec/language/module_spec.rb +5 -5
- data/spec/rubyspec/language/predefined_spec.rb +1 -2
- data/spec/rubyspec/library/stringscanner/element_reference_spec.rb +29 -0
- data/spec/rubyspec/spec_helper.rb +31 -0
- metadata +130 -76
- data/lib/opal/erb.rb +0 -41
- data/opal/erb.rb +0 -19
- data/spec/opal/erb/erb_spec.rb +0 -31
- data/spec/simple_erb_template.opalerb +0 -1
- data/spec/templates/foo/bar.opalerb +0 -1
- data/spec/templates/prefixed.opalerb +0 -1
data/opal/opal/time.rb
CHANGED
@@ -53,7 +53,9 @@ class Time
|
|
53
53
|
to_f <=> other.to_f
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
def day
|
57
|
+
`#{self}.getDate()`
|
58
|
+
end
|
57
59
|
|
58
60
|
def eql?(other)
|
59
61
|
other.is_a?(Time) && (self <=> other).zero?
|
@@ -63,13 +65,19 @@ class Time
|
|
63
65
|
`#{self}.getDay() === 5`
|
64
66
|
end
|
65
67
|
|
66
|
-
|
68
|
+
def hour
|
69
|
+
`#{self}.getHours()`
|
70
|
+
end
|
67
71
|
|
68
|
-
|
72
|
+
def inspect
|
73
|
+
`#{self}.toString()`
|
74
|
+
end
|
69
75
|
|
70
76
|
alias mday day
|
71
77
|
|
72
|
-
|
78
|
+
def min
|
79
|
+
`#{self}.getMinutes()`
|
80
|
+
end
|
73
81
|
|
74
82
|
def mon
|
75
83
|
`#{self}.getMonth() + 1`
|
@@ -85,7 +93,9 @@ class Time
|
|
85
93
|
`#{self}.getDay() === 6`
|
86
94
|
end
|
87
95
|
|
88
|
-
|
96
|
+
def sec
|
97
|
+
`#{self}.getSeconds()`
|
98
|
+
end
|
89
99
|
|
90
100
|
def strftime(format = '')
|
91
101
|
%x{
|
@@ -127,11 +137,19 @@ class Time
|
|
127
137
|
`#{self}.getDay() === 2`
|
128
138
|
end
|
129
139
|
|
130
|
-
|
140
|
+
def wday
|
141
|
+
`#{self}.getDay()`
|
142
|
+
end
|
131
143
|
|
132
144
|
def wednesday?
|
133
145
|
`#{self}.getDay() === 3`
|
134
146
|
end
|
135
147
|
|
136
|
-
|
148
|
+
def year
|
149
|
+
`#{self}.getFullYear()`
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_n
|
153
|
+
self
|
154
|
+
end
|
137
155
|
end
|
data/opal/source_map.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'opal/json'
|
2
|
+
|
3
|
+
require 'source_map/vlq.rb'
|
4
|
+
require 'source_map/generator.rb'
|
5
|
+
require 'source_map/parser.rb'
|
6
|
+
|
7
|
+
class SourceMap
|
8
|
+
include SourceMap::Generator
|
9
|
+
include SourceMap::Parser
|
10
|
+
|
11
|
+
# Create a new blank SourceMap
|
12
|
+
#
|
13
|
+
# Options may include:
|
14
|
+
#
|
15
|
+
# :file => String # See {#file}
|
16
|
+
# :source_root => String # See {#source_root}
|
17
|
+
# :generated_output => IO # See {#generated_output}
|
18
|
+
#
|
19
|
+
# :sources => Array[String] # See {#sources}
|
20
|
+
# :names => Array[String] # See {#names}
|
21
|
+
#
|
22
|
+
# :version => 3 # Which version of SourceMap to use (only 3 is allowed)
|
23
|
+
#
|
24
|
+
def initialize(opts={})
|
25
|
+
unless (remain = opts.keys - [:generated_output, :file, :source_root, :sources, :names, :version]).empty?
|
26
|
+
raise ArgumentError, "Unsupported options to SourceMap.new: #{remain.inspect}"
|
27
|
+
end
|
28
|
+
self.generated_output = opts[:generated_output]
|
29
|
+
self.file = opts[:file] || ''
|
30
|
+
self.source_root = opts[:source_root] || ''
|
31
|
+
self.version = opts[:version] || 3
|
32
|
+
self.sources = opts[:sources] || []
|
33
|
+
self.names = opts[:names] || []
|
34
|
+
self.mappings = []
|
35
|
+
raise "version #{opts[:version]} not supported" if version != 3
|
36
|
+
end
|
37
|
+
|
38
|
+
# The name of the file containing the code that this SourceMap describes.
|
39
|
+
# (default "")
|
40
|
+
attr_accessor :file
|
41
|
+
|
42
|
+
# The URL/directory that contains the original source files.
|
43
|
+
#
|
44
|
+
# This is prefixed to the entries in ['sources']
|
45
|
+
# (default "")
|
46
|
+
attr_accessor :source_root
|
47
|
+
|
48
|
+
# The version of the SourceMap spec we're using.
|
49
|
+
# (default 3)
|
50
|
+
attr_accessor :version
|
51
|
+
|
52
|
+
# The list of sources (used during parsing/generating)
|
53
|
+
# These are relative to the source_root.
|
54
|
+
# (default [])
|
55
|
+
attr_accessor :sources
|
56
|
+
|
57
|
+
# A list of names (used during parsing/generating)
|
58
|
+
# (default [])
|
59
|
+
attr_accessor :names
|
60
|
+
|
61
|
+
# A list of mapping objects.
|
62
|
+
attr_accessor :mappings
|
63
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
class SourceMap
|
2
|
+
module Generator
|
3
|
+
|
4
|
+
# An object (responding to <<) that will be written to whenever
|
5
|
+
# {add_generated} is called.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# File.open("/var/www/a.js.min"){ |f|
|
10
|
+
# map = SourceMap.new(:generated_output => f)
|
11
|
+
# map.add_generated('function(a,b,c){minified=1}\n', :source => 'a.js')
|
12
|
+
# map.save('/var/www/a.js.map')
|
13
|
+
# }
|
14
|
+
# File.read('/var/www/a.js.min') == 'function(a,b,c){minified=1}\n'
|
15
|
+
#
|
16
|
+
attr_accessor :generated_output
|
17
|
+
|
18
|
+
# Add the mapping for generated code to this source map.
|
19
|
+
#
|
20
|
+
# The first parameter is the generated text that you're going to add to the output, if
|
21
|
+
# it contains multiple lines of code then it will be added to the source map as
|
22
|
+
# several mappings.
|
23
|
+
#
|
24
|
+
# If present, the second parameter represents the original source of the generated
|
25
|
+
# fragment, and may contain:
|
26
|
+
#
|
27
|
+
# :source => String, # The filename of the source fille that contains this fragment.
|
28
|
+
# :source_line => Integer, # The line in that file that contains this fragment
|
29
|
+
# :source_col => Integer, # The column in that line at which this fragment starts
|
30
|
+
# :name => String # The original name for this variable.
|
31
|
+
# :exact_position => Bool # Whether all lines in the generated fragment came from
|
32
|
+
# the same position in the source.
|
33
|
+
#
|
34
|
+
# The :source key is required to set :source_line, :source_col or :name.
|
35
|
+
#
|
36
|
+
# If unset :source_line and :source_col default to 1,0 for the first line of the
|
37
|
+
# generated fragment.
|
38
|
+
#
|
39
|
+
# Normally :source_line is incremented and :source_col reset at every line break in
|
40
|
+
# the generated code (because we assume that you're copying a verbatim fragment from
|
41
|
+
# the source into the generated code). If that is not the case, you can set
|
42
|
+
# :exact_position => true, and then all lines in the generated output will be given
|
43
|
+
# the same :source_line and :source_col.
|
44
|
+
#
|
45
|
+
# The :name property is used if the fragment you are adding contains only a name that
|
46
|
+
# you have renamed in the source transformation.
|
47
|
+
#
|
48
|
+
# If you'd like to ensure that the source map stays in sync with the generated
|
49
|
+
# source, consider calling {source_map.generated_output = StringIO.new} and then
|
50
|
+
# accessing your generated javascript with {source_map.generated_output.string},
|
51
|
+
# otherwise be careful to always write to both.
|
52
|
+
#
|
53
|
+
# NOTE: By long-standing convention, the first line of a file is numbered 1, not 0.
|
54
|
+
#
|
55
|
+
# NOTE: when generating a source map, you should either use this method always, or use
|
56
|
+
# the {#add_mapping} method always.
|
57
|
+
#
|
58
|
+
def add_generated(text, opts={})
|
59
|
+
if !opts[:source] && (opts[:name] || opts[:source_line] || opts[:source_col])
|
60
|
+
raise "mapping must have :source to have :source_line, :source_col or :name"
|
61
|
+
elsif opts[:source_line] && opts[:source_line] < 1
|
62
|
+
raise "files start on line 1 (got :source_line => #{opts[:source_line]})"
|
63
|
+
elsif !(remain = opts.keys - [:source, :source_line, :source_col, :name, :exact_position]).empty?
|
64
|
+
raise "mapping had unexpected keys: #{remain.inspect}"
|
65
|
+
end
|
66
|
+
|
67
|
+
source_line = opts[:source_line] || 1
|
68
|
+
source_col = opts[:source_col] || 0
|
69
|
+
self.generated_line ||= 1
|
70
|
+
self.generated_col ||= 0
|
71
|
+
|
72
|
+
text.split(/(\n)/).each do |line|
|
73
|
+
if line == "\n"
|
74
|
+
self.generated_line += 1
|
75
|
+
self.generated_col = 0
|
76
|
+
unless opts[:exact_position]
|
77
|
+
source_line += 1
|
78
|
+
source_col = 0
|
79
|
+
end
|
80
|
+
elsif line != ""
|
81
|
+
mapping = {
|
82
|
+
:generated_line => generated_line,
|
83
|
+
:generated_col => generated_col,
|
84
|
+
}
|
85
|
+
if opts[:source]
|
86
|
+
mapping[:source] = opts[:source]
|
87
|
+
mapping[:source_line] = source_line
|
88
|
+
mapping[:source_col] = source_col
|
89
|
+
mapping[:name] = opts[:name] if opts[:name]
|
90
|
+
end
|
91
|
+
|
92
|
+
mappings << mapping
|
93
|
+
|
94
|
+
self.generated_col += line.size
|
95
|
+
source_col += line.size unless opts[:exact_position]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
generated_output += text if generated_output
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add a mapping to the list for this object.
|
103
|
+
#
|
104
|
+
# A mapping identifies a fragment of code that has been moved around during
|
105
|
+
# transformation from the source file to the generated file. The fragment should
|
106
|
+
# be contiguous and not contain any line breaks.
|
107
|
+
#
|
108
|
+
# Mappings are Hashes with a valid subset of the following 6 keys:
|
109
|
+
#
|
110
|
+
# :generated_line => Integer, # The line in the generated file that contains this fragment.
|
111
|
+
# :generated_col => Integer, # The column in the generated_line that this mapping starts on
|
112
|
+
# :source => String, # The filename of the source fille that contains this fragment.
|
113
|
+
# :source_line => Integer, # The line in that file that contains this fragment.
|
114
|
+
# :source_col => Integer, # The column in that line at which this fragment starts.
|
115
|
+
# :name => String # The original name for this variable (if applicable).
|
116
|
+
#
|
117
|
+
#
|
118
|
+
# The only 3 valid subsets of keys are:
|
119
|
+
# [:generated_line, :generated_col] To indicate that this is a fragment in the
|
120
|
+
# output file that you don't have the source for.
|
121
|
+
#
|
122
|
+
# [:generated_line, :generated_col, :source, :source_line, :source_col] To indicate
|
123
|
+
# that this is a fragment in the output file that you do have the source for.
|
124
|
+
#
|
125
|
+
# [:generated_line, :generated_col, :source, :source_line, :source_col, :name] To
|
126
|
+
# indicate that this is a particular identifier at a particular location in the original.
|
127
|
+
#
|
128
|
+
# Any other combination of keys would produce an invalid source map.
|
129
|
+
#
|
130
|
+
# NOTE: By long-standing convention, the first line of a file is numbered 1, not 0.
|
131
|
+
#
|
132
|
+
# NOTE: when generating a source map, you should either use this method always,
|
133
|
+
# or use the {#add_generated} method always.
|
134
|
+
#
|
135
|
+
def add_mapping(map)
|
136
|
+
if !map[:generated_line] || !map[:generated_col]
|
137
|
+
raise "mapping must have :generated_line and :generated_col"
|
138
|
+
elsif map[:source] && !(map[:source_line] && map[:source_col])
|
139
|
+
raise "mapping must have :source_line and :source_col if it has :source"
|
140
|
+
elsif !map[:source] && (map[:source_line] || map[:source_col])
|
141
|
+
raise "mapping may not have a :source_line or :source_col without a :source"
|
142
|
+
elsif map[:name] && !map[:source]
|
143
|
+
raise "mapping may not have a :name without a :source"
|
144
|
+
elsif map[:source_line] && map[:source_line] < 1
|
145
|
+
raise "files start on line 1 (got :source_line => #{map[:source_line]})"
|
146
|
+
elsif map[:generated_line] < 1
|
147
|
+
raise "files start on line 1 (got :generated_line => #{map[:generated_line]})"
|
148
|
+
elsif !(remain = map.keys - [:generated_line, :generated_col, :source, :source_line, :source_col, :name]).empty?
|
149
|
+
raise "mapping had unexpected keys: #{remain.inspect}"
|
150
|
+
end
|
151
|
+
|
152
|
+
mappings << map
|
153
|
+
end
|
154
|
+
|
155
|
+
# Convert the map into an object suitable for direct serialisation.
|
156
|
+
def as_json
|
157
|
+
serialized_mappings = serialize_mappings!
|
158
|
+
|
159
|
+
{
|
160
|
+
'version' => version,
|
161
|
+
'file' => file,
|
162
|
+
'sourceRoot' => source_root,
|
163
|
+
'sources' => sources,
|
164
|
+
'names' => names,
|
165
|
+
'mappings' => serialized_mappings
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
# Convert the map to a string.
|
170
|
+
def to_s
|
171
|
+
as_json.to_json
|
172
|
+
end
|
173
|
+
|
174
|
+
# Write this map to a file.
|
175
|
+
def save(file)
|
176
|
+
File.open(file, "w"){ |f| f << to_s }
|
177
|
+
end
|
178
|
+
|
179
|
+
protected
|
180
|
+
|
181
|
+
attr_reader :source_ids, :name_ids
|
182
|
+
attr_accessor :generated_line, :generated_col
|
183
|
+
|
184
|
+
# Get the id for the given file. If we've not
|
185
|
+
# seen this file before, add it to the list.
|
186
|
+
def source_id(file)
|
187
|
+
if (cached = source_ids[file])
|
188
|
+
cached
|
189
|
+
else
|
190
|
+
sources << file
|
191
|
+
source_ids[file] = sources.size - 1
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Get the id for the given name. If we've not
|
196
|
+
# seen this name before, add it to the list.
|
197
|
+
def name_id(name)
|
198
|
+
if (cached = name_ids[file])
|
199
|
+
cached
|
200
|
+
else
|
201
|
+
names << name
|
202
|
+
name_ids[file] = names.size - 1
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Encode a vlq. As each field in the output should be relative to the
|
207
|
+
# previous occurance of that field, we keep track of each one.
|
208
|
+
def vlq(num, type)
|
209
|
+
ret = num - @previous_vlq[type]
|
210
|
+
@previous_vlq[type] = num
|
211
|
+
VLQ.encode(ret)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Serialize the list of mappings into the string of base64 variable length
|
215
|
+
# quanities. As a side-effect, regenerate the sources and names arrays.
|
216
|
+
def serialize_mappings!
|
217
|
+
# clear all internals as we're about to re-generate them.
|
218
|
+
@sources = []
|
219
|
+
@source_ids = {}
|
220
|
+
@names = []
|
221
|
+
@name_ids = {}
|
222
|
+
@previous_vlq = Hash.new{ 0 }
|
223
|
+
|
224
|
+
return "" if mappings.empty?
|
225
|
+
|
226
|
+
by_lines = mappings.group_by{ |x| x[:generated_line] }
|
227
|
+
|
228
|
+
(1..by_lines.keys.max).map do |line|
|
229
|
+
# reset the generated_col on each line as indicated by the VLQ spec.
|
230
|
+
# (the other values continue to be relative)
|
231
|
+
@previous_vlq[:generated_col] = 0
|
232
|
+
|
233
|
+
fragments = (by_lines[line] || []).sort_by{ |x| x[:generated_col] }
|
234
|
+
fragments.map do |map|
|
235
|
+
serialize_mapping(map)
|
236
|
+
end.join(",")
|
237
|
+
end.join(";")
|
238
|
+
end
|
239
|
+
|
240
|
+
def serialize_mapping(map)
|
241
|
+
item = vlq(map[:generated_col], :generated_col)
|
242
|
+
if map[:source]
|
243
|
+
item += vlq(source_id(map[:source]), :source)
|
244
|
+
item += vlq(map[:source_line] - 1, :source_line)
|
245
|
+
item += vlq(map[:source_col], :source_col)
|
246
|
+
item += vlq(name_id(map[:name]), :name) if map[:name]
|
247
|
+
end
|
248
|
+
item
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class SourceMap
|
2
|
+
|
3
|
+
class ParserError < RuntimeError; end
|
4
|
+
|
5
|
+
# Load a SourceMap from a Hash such as might be returned by
|
6
|
+
# {SourceMap#as_json}.
|
7
|
+
#
|
8
|
+
def self.from_json(json)
|
9
|
+
raise ParserError, "Cannot parse version: #{json['version']} of SourceMap" unless json['version'] == 3
|
10
|
+
|
11
|
+
map = new(:file => json['file'],
|
12
|
+
:source_root => json['sourceRoot'],
|
13
|
+
:sources => json['sources'],
|
14
|
+
:names => json['names'])
|
15
|
+
|
16
|
+
map.parse_mappings(json['mappings'] || '')
|
17
|
+
map
|
18
|
+
end
|
19
|
+
|
20
|
+
# Load a SourceMap from a String.
|
21
|
+
def self.from_s(str)
|
22
|
+
from_json JSON.parse(str)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Load a SourceMap from a file.
|
26
|
+
def self.load(filename)
|
27
|
+
from_s File.read(filename)
|
28
|
+
end
|
29
|
+
|
30
|
+
module Parser
|
31
|
+
# Parse the mapping string from a SourceMap.
|
32
|
+
#
|
33
|
+
# The mappings string contains one comma-separated list of segments per line
|
34
|
+
# in the output file, these lists are joined by semi-colons.
|
35
|
+
#
|
36
|
+
def parse_mappings(string)
|
37
|
+
@previous = Hash.new{ 0 }
|
38
|
+
|
39
|
+
string.split(";").each_with_index do |line, line_idx|
|
40
|
+
# The generated_col resets to 0 at the start of every line, though
|
41
|
+
# all the other differences are maintained.
|
42
|
+
@previous[:generated_col] = 0
|
43
|
+
line.split(",").each do |segment|
|
44
|
+
mappings << parse_mapping(segment, line_idx + 1)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
self.mappings = self.mappings.sort_by{ |x| [x[:generated_line], x[:generated_col]] }
|
49
|
+
end
|
50
|
+
|
51
|
+
# All the numbers in SourceMaps are stored as differences from each other,
|
52
|
+
# so we need to remove the difference every time we read a number.
|
53
|
+
def undiff(int, type)
|
54
|
+
@previous[type] += int
|
55
|
+
end
|
56
|
+
|
57
|
+
# Parse an individual mapping.
|
58
|
+
#
|
59
|
+
# This is a list of variable-length-quanitity, with 1, 4 or 5 items. See the spec
|
60
|
+
# https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
|
61
|
+
# for more details.
|
62
|
+
def parse_mapping(segment, line_num)
|
63
|
+
item = VLQ.decode_array(segment)
|
64
|
+
|
65
|
+
unless [1, 4, 5].include?(item.size)
|
66
|
+
raise ParserError, "In map for #{file}:#{line_num}: unparseable item: #{segment}"
|
67
|
+
end
|
68
|
+
|
69
|
+
map = {
|
70
|
+
:generated_line => line_num,
|
71
|
+
:generated_col => undiff(item[0], :generated_col),
|
72
|
+
}
|
73
|
+
|
74
|
+
if item.size >= 4
|
75
|
+
map[:source] = sources[undiff(item[1], :source_id)]
|
76
|
+
map[:source_line] = undiff(item[2], :source_line) + 1 # line numbers are stored starting from 0
|
77
|
+
map[:source_col] = undiff(item[3], :source_col)
|
78
|
+
map[:name] = names[undiff(item[4], :name_id)] if item[4]
|
79
|
+
end
|
80
|
+
|
81
|
+
if map[:generated_col] < 0
|
82
|
+
raise ParserError, "In map for #{file}:#{line_num}: unexpected generated_col: #{map[:generated_col]}"
|
83
|
+
|
84
|
+
elsif map.key?(:source) && (map[:source].nil? || @previous[:source_id] < 0)
|
85
|
+
raise ParserError, "In map for #{file}:#{line_num}: unknown source id: #{@previous[:source_id]}"
|
86
|
+
|
87
|
+
elsif map.key?(:source_line) && map[:source_line] < 1
|
88
|
+
raise ParserError, "In map for #{file}:#{line_num}: unexpected source_line: #{map[:source_line]}"
|
89
|
+
|
90
|
+
elsif map.key?(:source_col) && map[:source_col] < 0
|
91
|
+
raise ParserError, "In map for #{file}:#{line_num}: unexpected source_col: #{map[:source_col]}"
|
92
|
+
|
93
|
+
elsif map.key?(:name) && (map[:name].nil? || @previous[:name_id] < 0)
|
94
|
+
raise ParserError, "In map for #{file}:#{line_num}: unknown name id: #{@previous[:name_id]}"
|
95
|
+
|
96
|
+
else
|
97
|
+
map
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|