opal 0.3.44 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|